Крестики-нолики на языке C

Я новичок в программировании и только начал изучать язык C. Затем я решаю сделать свою первую программу «Крестики-нолики».

Краткое описание «Крестики-нолики»: пользователь может выбрать, играть ли ему вдвоем или против компьютера. Для компьютерного плеера я использую Минимаксный алгоритм поэтому пользователю непросто выиграть, независимо от того, выберет ли он X или O.

Можете ли вы просмотреть мой код? Достаточно ли он чист и хорош, или, может быть, я могу что-то сделать лучше? Спасибо!

#include <ctype.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef struct
{
    char name[30];
    char symbol;
}
player;

// GRID TOTAL BOX
const int N = 3;

char board[3][3] = {
    {'1', '2', '3'},
    {'4', '5', '6'},
    {'7', '8', '9'}
};

player players[2];

char COMPUTER_AI;

int numOfplayer, winner;

void delay(int seconds);
void information();
void draw_board();
void draw_line();
void two_player_gameplay();
int check_win();
int check_minimax_win();
void show_the_winner(int result);
void human_move();
void computer_move();
int minimax(int depth, bool isMaximizing);

int main(void)
{
    printf("Welcome to Tic-Tac-Toe game!nn");

    information(); 
    
    if (numOfplayer == 2)
    { 
        two_player_gameplay();
    }
    else
    {
        // IF user choose to be X, move first
        if (players[0].symbol == 'X')
        {
            human_move();
        }
        else
        {
            computer_move();
        }
    }

    return 0;
}

// FUNCTION TO HANDLE HUMAN MOVE
void human_move()
{
    int choice, found, result;
    char mark, char_choice;

    draw_board();

    // Keep prompt user get the valid board number
    do
    {
        printf("tttt");
        printf("%s : ", players[0].name);
        scanf("%i", &choice);
    
        // Get player symbol
        mark = players[0].symbol;
            
        // Always reset found to 0
        found = 0;

        for (int row = 0; row < N; row++)
        {
            for (int col = 0; col < N; col++)
            {
                // Convert user choice from int to char to compare with board array
                char_choice = choice + '0';
                    
                // Check if number user choose is still exist in board, then it's legal to change it with user symbol
                if (board[row][col] == char_choice)
                {
                    // After change it, increment found if the board valid
                    board[row][col] = mark;
                    found++;
                    
                    // Break the loop
                    row = col = N;
                    break;
                }
            }
        }
    
    }
    while ((choice < 1 || choice > 9) || found == 0);
    
    // Check if there's a winner
    // IF It's 2, game still running, computer turn to move
    // Otherwise, call show the winner function
    result = check_win();
    if (result == 2)
    {
        computer_move();
    }
    else
    {
        show_the_winner(result);
    }
}

// FUNCTION TO HANDEL COMPUTER MOVE
void computer_move()
{
    char board_number;
    int move_i, move_j;

    // Set best score to minus INFINITY
    float bestScore = -INFINITY;

    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            // CHECK IF BOARD IS EMPTY
            if (board[i][j] != 'X' && board[i][j] != 'O')
            {
                // Get the real number on board
                board_number = board[i][j];

                // Change the board temporary
                // So we can call minimax to calculate it
                board[i][j] = COMPUTER_AI;    
                float score = minimax(0, false);

                // Reset to default board number
                board[i][j] = board_number;

                // AS maximizing player, IF score GREATER Than best score,
                // Change the best score, store current board position to be placed
                if (score > bestScore)
                {
                    bestScore = score;
                    move_i = i;
                    move_j = j;
                }
            }           
        }
    }
    // Placed the best position
    board[move_i][move_j] = COMPUTER_AI;
    
    // Check win, if it's still running, human turn to move
    int result = check_win();
    if (result == 2)
    {
        human_move();
    }
    else
    {
        show_the_winner(result);
    }
}

// MINIMAX FUNCTION
int minimax(int depth, bool isMaximizing)
{
    // Base case
    // Return static evaluation after get the winner
    int result = check_minimax_win();
    if (result != 2)
    {
        return result;
    }

    // IF Maximizing player turn to analyze
    if (isMaximizing)
    {
        // SET best score to minus infinity
        float bestScore = -INFINITY;

        // ITERATE THROUGH BOARD
        for (int row = 0; row < N; row++)
        {
            for (int col = 0; col < N; col++)
            {
                // Check if the positon in board is empty
                if (board[row][col] != 'X' && board[row][col] != 'O')
                {
                    // Get the real number on board
                    char board_number = board[row][col];

                    // Change the current position in board as MAXIMIZING (COMPUTER) symbol
                    // Call minimax recursively, minimizing turn
                    board[row][col] = COMPUTER_AI;
                    float score = minimax(depth + 1, false);

                    // Reset to default board number
                    board[row][col] = board_number;
                    
                    // AS maximizing player, find the MAX score, store into best score
                    if (score > bestScore)
                    {
                        bestScore = score;
                    }
                }
            }
        }
        return bestScore;
    }
    // OTHERWISE, MINIMIZING TURN to analyze
    else
    {
        // SET best score to +INFINITY
        float bestScore = INFINITY;

        for (int row = 0; row < N; row++)
        {
            for (int col = 0; col < N; col++)
            {
                if (board[row][col] != 'X' && board[row][col] != 'O')
                {
                    char board_number = board[row][col];

                    // Change the current position in board as MAXIMIZING (COMPUTER) symbol, temporary
                    // Call minimax recursively, maximizing turn
                    board[row][col] = players[0].symbol;
                    float score = minimax(depth + 1, true);
                    
                    // Reset to default board number
                    board[row][col] = board_number;

                    // AS Minimizing, get the Minimum score, store it to best score
                    if (score < bestScore)
                    {
                        bestScore = score;
                    }
                }
            }
        }
        return bestScore;
    }
}

// TWO PLAYER GAMEPLAY
void two_player_gameplay() 
{
    int player = 0;
    int choice, found, result;
    char mark, char_choice;
 
    do 
    {
        draw_board();

        // PROMPT current player to move
        printf("tttt");
        printf("%s : ", players[player % 2].name);
        scanf("%i", &choice);

        // Get player symbol
        mark = players[player % 2].symbol;

        // Iterate through board to check and replace the board with user symbol
        for (int row = 0; row < N; row++) 
        {
            for (int col = 0; col < N; col++)
            {
                // Convert user choice from int to char to compare with board array
                char_choice = choice + '0';
                
                // Check if number user choose is still exist in board, then it's legal to change it with user symbol
                if (board[row][col] == char_choice)
                {
                    // After change it, increment player to the next
                    board[row][col] = mark;
                    player++;

                    // Break the loop
                    row = col = 3;
                    break;
                }
            }
        }

        // Call function to check if there's already winner
        result = check_win();
    } 
    while ((choice < 1 || choice > 9) || result == 2);

    draw_board();

    show_the_winner(result);
}

// FUNCTION TO ASK USER BEFORE STARTING GAME
void information()
{
    // KEEP prompt user until enter valid number of player
    do
    {
        printf("How many player will play (MAX is 2): ");
        scanf("%i", &numOfplayer);
    }
    while (numOfplayer > 2);
    printf("n");

    // If there's only one player
    if(numOfplayer < 2)
    {
        printf("Enter your name: ");
        scanf("%s", players[0].name);
        getchar();

        // Keep prompt user until enter valid symbol
        do
        {
            printf("X or O: ");
            scanf("%c", &players[0].symbol);
            getchar();
        }
        while (players[0].symbol != 'X' && players[0].symbol != 'O');    

        // Assign symbol to user and computer
        players[0].symbol = toupper(players[0].symbol);
        COMPUTER_AI = (players[0].symbol == 'X' ? 'O' : 'X');
    }
    // Otherwise, two player
    else
    {
        for (int i = 0; i < 2; i++)
        {
            // Get user name
            printf("Enter Player %i name: ", i + 1);
            scanf("%s", players[i].name);
            getchar();
            
            printf("n");
        
        }

        // First player get X symbol
        // Second player get O symbol    
        players[0].symbol = toupper('X');
        players[1].symbol = ((toupper(players[0].symbol) == 'X') ? 'O' : 'X');
    }

    printf("nn");
}

// FUNCTION TO CHECK IF THERE IS WINNER 
/*
    Return 1 means X is winning.
    Return -1 means O is winning.
    Return 0 means game is draw.
    Return 2 means game still running.
*/
int check_win()
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 1; j <= 1; j++)
        {
            // CHECK THREE in a row on HORIZONTAL Board
            if (board[i][j - 1] == board[i][j] && board[i][j] == board[i][j + 1])
            {
                if (board[i][j] == 'X')
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            // CHECK THREE in a row on VERTICAL Board
            else if (board[j - 1][i] == board[j][i] && board[j][i] == board[j + 1][i])
            {
                if (board[j][i] == 'X')
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            // CHECK THREE in a row on SLASH Board || 1 - 5- 9 or 3 - 5 - 7
            else if (board[j - 1][j - 1] == board[j][j] && board[j][j] == board[j + 1][j + 1])
            {
                if (board[j][j] == 'X')
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            else if (board[j - 1][j + 1] == board[j][j] && board[j][j] == board[j + 1][j - 1])
            {
                if (board[j][j] == 'X')
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            // CHECK IF BOARD ALREADY FULL, AND NO ONES WIN (DRAW)
            else if (board[j - 1][j - 1] != '1' && board[j - 1][j] != '2' && board[j - 1][j + 1] != '3' && 
                     board[j][j - 1] != '4' && board[j][j] != '5' && board[j][j + 1] != '6' &&
                     board[j + 1][j - 1] != '7' && board[j + 1][j] != '8' && board[j + 1][j + 1] != '9')
                     {
                         return 0;
                     }
        }
    }
    // IF there's no three symbol in a row yet, game still running
    return 2;
}

// FUNCTION TO CHECK THE WINNER || ONLY SPECIFY FOR MINIMAX FUNCTION
// HANDLE DIFFERENT RETURN BETWEEN X and O
/*
    When user decide to play as X: O is computer as maximizing player
    So, it always return 1 for best score
    When user decide to play as O: X is computer as maximizing player
    So, it ALSO always return 1 for best score

    -1 is ONLY score for minimizing player: Human.
*/ 
int check_minimax_win()
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 1; j <= 1; j++)
        {
            // CHECK THREE in a row on HORIZONTAL Board
            if (board[i][j - 1] == board[i][j] && board[i][j] == board[i][j + 1])
            {
                // IF user decide to play as O
                if (players[0].symbol == 'O')
                {   
                    // Computer as maximizing player, X return 1 as best score
                    // O return -1 as minimizing score 
                    if (board[i][j] == 'X')
                    {
                        return 1;
                    }
                    else
                    {
                        return -1;
                    }
                }

                // IF user decide to play as X
                if (players[0].symbol == 'X')
                {
                    // REVERSE THE RETURN VALUE (NOT CONDITION)
                    // Computer as maximizing player, X return -1 as minimizing score
                    // O return 1 as maximizing score 
                    if (board[i][j] == 'X')
                    {
                        // X as minimizing
                        return -1;
                    }
                    else
                    {
                        // O as maximizing
                        return 1;
                    }
                }    
            }
            // CHECK THREE in a row on VERTICAL Board
            else if (board[j - 1][i] == board[j][i] && board[j][i] == board[j + 1][i])
            {
                if (players[0].symbol == 'O')
                {
                    // Computer as maximizing player, X return 1 as best score
                    // O return -1 as minimizing score 
                    if (board[j][i] == 'X')
                    {
                        return 1;
                    }
                    else
                    {
                        return -1;
                    }
                }

                if (players[0].symbol == 'X')
                {
                    // REVERSE THE RETURN VALUE (NOT CONDITION)
                    // Computer as maximizing player, X return -1 as minimizing score
                    // O return 1 as maximizing score
                    if (board[j][i] == 'X')
                    {
                        return -1;
                    }
                    else
                    {
                        return 1;
                    }
                }    
            }
            // CHECK THREE in a row on SLASH Board || 1 - 5- 9 or 3 - 5 - 7
            else if (board[j - 1][j - 1] == board[j][j] && board[j][j] == board[j + 1][j + 1])
            {
                if (players[0].symbol == 'O')
                {
                    // Computer as maximizing player, X return 1 as best score
                    // O return -1 as minimizing score 
                    if (board[j][j] == 'X')
                    {
                        return 1;
                    }
                    else
                    {
                        return -1;
                    }
                }

                if (players[0].symbol == 'X')
                {
                    // REVERSE THE RETURN VALUE (NOT CONDITION)
                    // Computer as maximizing player, X return -1 as minimizing score
                    // O return 1 as maximizing score
                    if (board[j][j] == 'X')
                    {
                        return -1;
                    }
                    else
                    {
                        return 1;
                    }
                }
            }
            else if (board[j - 1][j + 1] == board[j][j] && board[j][j] == board[j + 1][j - 1])
            {
                if (players[0].symbol == 'O')
                {
                    // Computer as maximizing player, X return 1 as best score
                    // O return -1 as minimizing score
                    if (board[j][j] == 'X')
                    {
                        return 1;
                    }
                    else
                    {
                        return -1;
                    }
                }

                if (players[0].symbol == 'X')
                {
                    // REVERSE THE RETURN VALUE (NOT CONDITION)
                    // Computer as maximizing player, X return -1 as minimizing score
                    // O return 1 as maximizing score
                    if (board[j][j] == 'X')
                    {
                        return -1;
                    }
                    else
                    {
                        return 1;
                    }
                }    
            }
            // CHECK IF BOARD ALREADY FULL, AND NO ONES WIN (DRAW)
            else if (board[j - 1][j - 1] != '1' && board[j - 1][j] != '2' && board[j - 1][j + 1] != '3' && 
                     board[j][j - 1] != '4' && board[j][j] != '5' && board[j][j + 1] != '6' &&
                     board[j + 1][j - 1] != '7' && board[j + 1][j] != '8' && board[j + 1][j + 1] != '9')
                     {
                         return 0;
                     }
        }
    }
    // IF there's no three symbol in a row yet, game still running
    return 2;
}

// FUNCTION TO SHOW THE WINNER
void show_the_winner(int result)
{
    // AS one player only
    if (numOfplayer < 2)
    {
        // CHECK IF the user symbol match with the result
        if ((players[0].symbol == 'X' && result == 1) || (players[0].symbol == 'O' && result == -1))
        {
            draw_board();
            printf("nttttt    ---THE WINNER IS %s---nnn", players[0].name);
        }
        // MAKE SURE COMPUTER PLAY AS X(1) OR O (-1)
        else if (result == -1 || result == 1)
        {
            draw_board();
            printf("ntttttt ---YOU LOSE---nnn");
        }
        // TIES
        else 
        {
            draw_board();
            printf("nttttt     ---THE GAME IS DRAW---nnn");
        }
    }
    // TWO PLAYER GAMEPLAY
    else
    {
        // The end of game conditions
        for (int i = 0; i < 2; i++)
        {
            if (result == 1) 
            {
                if (players[i].symbol == 'X')
                {
                    printf("nttttt    ---THE WINNER IS %s---nnn", players[i].name);
                    break;
                }
            }
            if (result == -1) 
            {
                if (players[i].symbol == 'O')
                {
                    printf("nttttt    ---THE WINNER IS %s---nnn", players[i].name);
                    break;
                }
            }
            if (result == 0)
            {
                printf("nttttt     ---THE GAME IS DRAW---nnn");
                break;
            }
        }
    }
}

// FUNCTION TO DRAW TICTACTOE BOARD
void draw_board()
{
    system("clear");

    printf("nn");
    printf("ttttEnter the number on board to choose your place!nnn");

    if (numOfplayer < 2)
    {
        printf("tttt");
        printf("%s : %c", players[0].name, toupper(players[0].symbol));
    }
    else
    {
        printf("tttt");
        printf("(Player 1) %s : %cn", players[0].name, toupper(players[0].symbol));
        printf("tttt");
        printf("(Player 2) %s : %cn", players[1].name, toupper(players[1].symbol));
    }
    printf("nn");

    for (int row = 0; row < N; row++)
    {
        for (int col = 0; col < N; col++)
        {
            // Print extra vertical bar on top only when for first column
            if (col == 0)
            {
                printf("tttttt");
                printf("     |     |     ");
                printf("n");
                printf("tttttt");
            }
            
            // Print the number on each board column--Horizontally
            printf("  %c  ", board[row][col]);

            // Print vertical bar at the end of number || EXCEPT for the last number
            if (col != 2)
            {
                printf("|");
            }
            
        }
        printf("n");

        // Print horizontal bar only for 2 column
        if (row != 2)
        {
            printf("tttttt");
            printf("_____|_____|_____");
            printf("n");
        }

        // Print extra vertical bar on below
        if (row == 2)
        {
            printf("tttttt");
            printf("     |     |     ");
            printf("n");  
        }
    }
    printf("nn");
}

1 ответ
1

относительно размещенного кода:

Запуск его через компилятор приводит к:

gcc -ggdb3 -Wall -Wextra -Wconversion -pedantic -std=gnu11 -c "untitled2.c" -o "untitled2.o"

untitled2.c: In function ‘human_move’:

untitled2.c:95:31: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
   95 |                 char_choice = choice + '0';
      |                               ^~~~~~

untitled2.c: In function ‘computer_move’:

untitled2.c:150:31: warning: conversion from ‘int’ to ‘float’ may change value [-Wconversion]
  150 |                 float score = minimax(0, false);
      |                               ^~~~~~~

untitled2.c: In function ‘minimax’:

untitled2.c:212:35: warning: conversion from ‘int’ to ‘float’ may change value [-Wconversion]
  212 |                     float score = minimax(depth + 1, false);
      |                                   ^~~~~~~

untitled2.c:225:16: warning: conversion from ‘float’ to ‘int’ may change value [-Wfloat-conversion]
  225 |         return bestScore;
      |                ^~~~~~~~~

untitled2.c:244:35: warning: conversion from ‘int’ to ‘float’ may change value [-Wconversion]
  244 |                     float score = minimax(depth + 1, true);
      |                                   ^~~~~~~

untitled2.c:257:16: warning: conversion from ‘float’ to ‘int’ may change value [-Wfloat-conversion]
  257 |         return bestScore;
      |                ^~~~~~~~~

untitled2.c: In function ‘two_player_gameplay’:

untitled2.c:286:31: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
  286 |                 char_choice = choice + '0';
      |                               ^~~~~~

untitled2.c:265:17: warning: unused variable ‘found’ [-Wunused-variable]
  265 |     int choice, found, result;
      |                 ^~~~~

untitled2.c: In function ‘information’:

untitled2.c:341:29: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
  341 |         players[0].symbol = toupper(players[0].symbol);
      |                             ^~~~~~~

untitled2.c:360:29: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
  360 |         players[0].symbol = toupper('X');
      |                             ^~~~~~~

Compilation finished successfully.

касательно: Компиляция успешно завершена.

поскольку есть предупреждения, это означает лишь то, что компилятор предположил, что вы хотите, а не то, что созданный код будет делать то, что вы хотите.

при размещении кода для обзора, пожалуйста, разместите код, который компилируется чисто.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *