首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单人Tic-Tac-Toe游戏

单人Tic-Tac-Toe游戏
EN

Code Review用户
提问于 2017-02-06 10:54:59
回答 1查看 2K关注 0票数 6

我尝试为Tic-Tac-Toe游戏制作一个单一的玩家模式.我不知道这是否是正确的做法,并希望得到第二种意见。

代码语言:javascript
复制
    // keeps track of all the valid positions.
bool validPos[10] = {false, true, true, true, true, true, true, true, true, true};
char user = 'O', computer = 'X';

int main(void)
{
    // A two-dimensional array depicting the game.
    char game[3][3] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
    char choice;

    // Asks user for a valid input.
    do
    {
        printf("X or O ?\n");
        scanf(" %c", &choice);

    }while(choice != 'X' && choice != 'O' && choice != 'x' && choice != 'o');

    // assigns X or O to the player.
    if(choice == 'x' || choice == 'X')
    {
        user = 'X';
        computer = 'O';
    }

    display(game);

    printf("You - %c\n", user);
    printf("Computer - %c\n", computer);

    startGame(game, user);
}

函数startGame

代码语言:javascript
复制
void startGame(char game[3][3], const char turn)
{
    int i, j;
    int pos;

    srand((unsigned)time(NULL));
    if(turn == user)
    {
        // asks user for a valid input.
        do
        {
            printf("Your turn : ");
            scanf("%d", &pos);

        }while((pos > 9 || pos < 1) || !validPos[pos]);
    }

    else
    {
        do
        {
            // calculates a good and valid move for the computer.
            pos = rand() % 10;
        }while((pos > 9 || pos < 1) || !validPos[pos]);
        pos = calcMove(game, pos);
    }

    // makes the position chosen invalid.
    validPos[pos] = false;

    // Assigns 'X' or 'O' to the chosen position.
    for( i = 0; i < 3; i++)
        for( j = 0; j < 3; j++)
            if(game[i][j] - '0' == pos)
                game[i][j] = turn;

    // prints the game onto the screen.
    display(game);

    // checks if the player or computer has won or not.
    if(gameStatus(game))
    {
        if(turn == user)
            printf("You Win !\n");

        else
            printf("Computer Wins !\n");

        return;
    }

    // checks if there are any further moves possible.
    // also acts as the base case for the game to end.
    if(isComplete(game))
    {
        printf("Game Over!\nNo More Possible Moves\n");
        return;
    }

    // recursive call to continue the game.
    if(turn == user)
        startGame(game, computer);

    if(turn == computer)
        startGame(game, user);
}

函数calcMove这是我不确定我的方法计算一个好的移动计算机,因为我觉得这是多余的。

代码语言:javascript
复制
int calcMove(char game[][3], int pos)
{
    int i, j;
    int move = 0;

    // If there is any move along a row that is a winning move for the computer
    // then take it or blocks the winning move of the user along a row.
    for(i = 0; i < 3; i++)
    {
        if(game[i][0] == game[i][1] && validPos[game[i][2] - '0'])
        {
            move = game[i][2] - '0';
            if(game[i][0] == computer)
                return move;
        }

        if(game[i][0] == game[i][2] && validPos[game[i][1] - '0'])
        {
            move = game[i][1] - '0';
            if(game[i][0] == computer)
                return move;
        }

        if(game[i][1] == game[i][2] && validPos[game[i][0] - '0'])
        {
            move = game[i][0] - '0';
            if(game[i][1] == computer)
                return move;
        }
    }

    // If there is any move along a coloumn that is a winning move for the computer
    // then take it or blocks the winning move of the user along a coloumn.
    for(j = 0; j < 3; j++)
    {
        if(game[0][j] == game[1][j] && validPos[game[2][j] - '0'])
        {
            move = game[2][j] - '0';
            if(game[0][j] == computer)
                return move;
        }

        if(game[1][j] == game[2][j] && validPos[game[0][j] - '0'])
        {
            move = game[0][j] - '0';
            if(game[1][j] == computer)
                return move;
        }

        if(game[0][j] == game[2][j] && validPos[game[1][j] - '0'])
        {
            move = game[1][j] - '0';
            if(game[0][j] == computer)
                return move;
        }
    }

    // checks for the winning move along the right diagonal.
    // preferance is given to winning move for computer.
    if(game[0][0] == game[1][1] && validPos[game[2][2] - '0'])
    {
        move = game[2][2] - '0';
        if(game[0][0] == computer)
            return move;
    }

    if(game[0][0] == game[2][2] && validPos[game[1][1] - '0'])
    {
        move = game[1][1] - '0';
        if(game[0][0] == computer)
            return move;
    }

    if(game[1][1] == game[2][2] && validPos[game[0][0] - '0'])
    {
        move = game[0][0] - '0';
        if(game[1][1] == computer)
            return move;
    }

    // checks for the winning move along the left diagonal.
    // preferance is given to winning move for computer.
    if(game[0][2] == game[1][1] && validPos[game[2][0] - '0'])
    {
        move = game[2][0] - '0';
        if(game[0][2] == computer)
            return move;
    }

    if(game[0][2] == game[2][0] && validPos[game[1][1] - '0'])
    {
        move = game[1][1] - '0';
        if(game[0][2] == computer)
            return move;
    }

    if(game[1][1] == game[2][0] && validPos[game[0][2] - '0'])
    {
        move = game[0][2] - '0';
        if(game[1][1] == computer)
            return move;
    }

    // if there was any such move in which either the player or
    // the computer would win, then return it.
    if(move != 0)
        return move;

    // if there was no such move, then select any of the corners
    // or the center randomly.
    switch(rand() % 4)
    {

        case 1 : if(validPos[7] && validPos[3])
                    return 7;

        case 2 : if(validPos[3] && validPos[7])
                    return 3;

        case 3 : if(validPos[1] && validPos[9])
                    return 1;

        case 4 : if(validPos[5])
                    return 5;

        default : return pos;
    }
}

我还没有包括函数disply()isComplete()gameStatus()的代码片段,因为我认为它们很简单,可能没有什么可回顾的。

EN

回答 1

Code Review用户

回答已采纳

发布于 2017-02-06 16:53:59

以下是一些可以帮助您改进代码的事情。

匹配声明

的初始化

变量game当前声明并初始化如下:

代码语言:javascript
复制
char game[3][3] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};

但这些并不完全吻合。相反,我建议将这一行改为:

代码语言:javascript
复制
char game[3][3] = {{'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'}};

消除了实际

中的全局变量

拥有依赖于全局变量的例程使得理解逻辑变得更加困难,并引入了许多错误的机会。在实用的地方消除全局变量总是一个好主意,无论是为桌面机器编程还是为嵌入式系统编程。在这段代码中,我建议所有游戏状态变量,包括板、当前回合和使用的令牌都可以包装在一个结构中,然后可以将指向该结构的指针传递给每个相关函数。这使得它更容易理解和维护。

消除冗余变量

考虑到将validPos数组传递给每个函数,实际上并不需要game数组。一个位置在这个游戏意义上是“有效”的,如果它还没有被占据的话,那么代替这个:

代码语言:javascript
复制
if(game[i][0] == game[i][1] && validPos[game[i][2] - '0'])

你可以写这个:

代码语言:javascript
复制
if(game[i][0] == game[i][1] && isdigit(game[i][2]))

不要将随机数生成器重发一次,

该程序目前在每次转弯前都调用srand。这是不必要的,也是不可取的。相反,只需在程序开始时调用它一次,然后继续使用rand()获取随机数。

理解rand()如何工作

rand()函数返回一个介于0和RAND_MAX之间的数字。为了简单起见,假设RAND_MAX是15 (实际上要大得多)。一个随机数生成器(或psuedo-随机数生成器)应该以相同的频率生成每个可能的数字,因此我们会有0发生的次数是1/16次,1次发生的次数是1/16次,等等。到目前为止还好,但是让我们看看当我们使用这样的构造时会发生什么:

代码语言:javascript
复制
pos = rand() % 10;

这是概率表:

代码语言:javascript
复制
rand    pos     probability
0       0           1/16
1       1           1/16
2       2           1/16
3       3           1/16
4       4           1/16
5       5           1/16
6       6           1/16
7       7           1/16
8       8           1/16
9       9           1/16
10      0           1/16
11      1           1/16
12      2           1/16
13      3           1/16
14      4           1/16
15      5           1/16

因此,产生pos的概率是:

代码语言:javascript
复制
pos     probability
0           2/16
1           2/16
2           2/16
3           2/16
4           2/16
5           2/16
6           1/16
7           1/16
8           1/16
9           1/16

由于10没有均匀地划分为RAND_MAX,所以在数据范围的低端比在范围末尾的数字更有可能被选择。对于这样一个简单的游戏,它可能并不重要,但这是一个重要的概念,了解。为了使您有一个均匀的分布,您可以这样做:

代码语言:javascript
复制
int pos = rand() / (RAND_MAX / N + 1);

其中,N被定义为范围的上限(本例中为10)。

独立输入、输出和计算

从实际的角度来看,通常将输入、输出和计算分离为这样的程序是很好的做法。通过将它们放在单独的函数中,它将平台的特定I/O (该平台或操作系统可能是唯一的)与程序的逻辑(不依赖于底层操作系统)隔离开来。用户提示以及用户移动的获取和验证可以从主游戏逻辑中分离出来,保持I/O和游戏逻辑之间的清晰分离。

使用更好的命名

函数isComplete并不坏,因为很明显,返回的true值意味着游戏已经完成。(虽然我可能更喜欢isFull来区分那个状态和一个玩家获胜的状态。)然而,gameStatus函数并没有这么简单的解释。因此,我可能会将该函数命名为isWon

不需要时不要使用递归,

实际上并不需要对startGame的递归调用。在这种情况下,没有太大的危害,但在有更多的调用的情况下,它可能会炸毁堆栈,并导致您的程序崩溃。更好的方法是隔离游戏逻辑,这样我们就可以将函数命名为startGame,而不是playGame,它看起来可能如下所示:

代码语言:javascript
复制
bool playGame (void) {
    char game[3][3] = {{'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'}};
    bool humanTurn = true;
    while (!isWon(game) && !isFull(game))
    {
        display(game);
        if (humanTurn) {
            humanMove(game);
        } else {
            computerMove(game);
        }
        humanTurn = !humanTurn; 
    }
    return showResults(game, !humanTurn);
}

现在很清楚游戏是如何构造的,如果这个应用程序是基于文本的还是基于GUI的,那么基本的游戏逻辑就可以被使用,没有改变。

检查错误的返回值

scanf的调用可能失败。您必须检查返回值以使其没有,否则当给定格式错误的输入时,您的程序可能会崩溃(或更糟)。严格的错误处理是大多数工作软件和无错误软件之间的区别。你应该为后者而奋斗。

简化了代码

重构超长calcMove()函数的一种方法是为每一个可能的移动计算一个分数,然后选择最高的值。例如,您可以有一个3×3的整数网格,初始化为0。然后为您在注释中已经确定的每个条件添加点数:

代码语言:javascript
复制
1. the move is valid and would win
2. the move is valid and would block a user win
3. the move is valid and is a corner or the middle
4. the move is valid and is an edge

当然,第一类比最后一类更有价值,所以你应该适当地加权这些点。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/154568

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档