【问题标题】:Find winner in a tic tac toe match [closed]在井字游戏中找到赢家[关闭]
【发布时间】:2014-04-24 15:35:18
【问题描述】:

我知道以前有人问过这个问题,但我不明白答案。

假设您在函数中有一个char board[3][3] 作为参数,并且 如果 X 获胜,则需要返回 1,如果 O 获胜,则返回 -1;如果还没有人获胜,则返回 0,或者如果是平局。

int checkforwin(char board[3][3]);

这是函数的声明。

有什么想法可以通过非原始测试来判断对手获胜吗?

【问题讨论】:

  • 只检查所有可能性?
  • 写出某人赢得比赛的所有方法,然后编写一些代码来检查这些可能性。
  • 所有相关问题都有关键字“井字游戏”,这真是太神奇了。
  • 不玩不是唯一能赢的方法吗?
  • 检查所有可能性是一种非常好的方法,即使它是“原始的”。没有必要让它比它需要的更复杂。你所能做的就是让代码更难理解,更容易出错。

标签: c arrays char


【解决方案1】:

有三种方法可以解决在井字游戏中检测获胜者的问题。有蛮力法、算法法和数据驱动法。

蛮力方法由一系列if 语句组成。由于赢得井字游戏只有 8 种方式,因此只需要 8 个if 语句来确定获胜者。因此,蛮力方法与其他方法相比表现良好,因为它具有相对较少的代码行数、简单易读的代码、相对较小的可执行文件大小和较快的执行速度。蛮力法是最好的方法,因为井字游戏是微不足道的。稍微复杂一点的游戏,比如四连线,可能需要更高级的编码技术,但井字游戏最好用简单的if 语句来解决。

算法方法已在此问题的其他回复中得到证明。 David Syzdek 的算法是迄今为止最好的算法,但在某种程度上是一种混合解决方案,因为它通过使用for 循环将八个if 语句减少到四个if 语句。请注意,他的算法仍然有散布在整个代码中的硬编码索引。

数据驱动的方法使用一个初始化的数据集来抽象问题,这样代码是完全通用的,所有的杂乱无章都被收集到数据集中。以下是使用数据驱动解决方案的代码。

typedef struct
{
    int valid;
    int rowA, colA;
    int rowB, colB;
    int rowC, colC;
}
    stPath;

static stPath paths[] =
{
    { TRUE,   0, 0,   0, 1,   0, 2 },   // top row
    { TRUE,   1, 0,   1, 1,   1, 2 },   // middle row
    { TRUE,   2, 0,   2, 1,   2, 2 },   // bottom row

    { TRUE,   0, 0,   1, 0,   2, 0 },   // left column
    { TRUE,   0, 1,   1, 1,   2, 1 },   // middle column
    { TRUE,   0, 2,   1, 2,   2, 2 },   // right column

    { TRUE,   0, 0,   1, 1,   2, 2 },   // TL to BR diagonal
    { TRUE,   0, 2,   1, 1,   2, 0 },   // TR to BL diagonal

    { FALSE,  0, 0,   0, 0,   0, 0 }    // end of list
};

int checkforwin( char board[3][3] )
{
    int a, b, c;
    stPath *pptr;

    // assumes that board array uses 'X' 'O' and <sp> to mark each position

    for ( pptr = paths; pptr->valid; pptr++ )
    {
        a = board[pptr->rowA][pptr->colA];
        b = board[pptr->rowB][pptr->colB];
        c = board[pptr->rowC][pptr->colC];

        if ( a == b && b == c && a != ' ' )
            return( (a == 'X') ? 1 : -1 );
    }

    return( 0 );    // no winner yet
}

请注意,我并不是在提倡使用数据驱动的解决方案来解决这个特定问题,因为问题本身太琐碎,无法保证数据驱动的解决方案。

【讨论】:

    【解决方案2】:

    这个对每一行/列/对角线进行计数。

    如果计数达到3,则表示一行三个Xs。
    同样,-3 表示一行中的三个Os。
    任何其他值都表示没有赢家。

    代码中有4个块:

    • 检查行
    • 检查列
    • 检查一个对角线
    • 检查另一个对角线。

    这里有很多重复代码,违反了DRY好代码的原则。
    但这很容易理解。

    int CheckTicTacToe(char board[3][3])
    {
        int count = 0;
        int row, col;
    
        // Check each of 3 rows:
        for(row = 0; row < 3; ++row)
        {
            count = 0;
            for(col=0; col < 3; ++col)
            {
                count += (board[row][col] == 'X')?  1 :
                         (board[row][col] == 'O')? -1 : 0;
            }
            if (count == 3 || count == -3)
            {
                return count / abs(count); // Return either 1 or -1
            }
        }
    
        // Check each of 3 columns.
        for(col = 0; col < 3; ++col)
        {
            count = 0;
            for(row=0; row < 3; ++row)
            {
                count += (board[row][col] == 'X')?  1 :
                         (board[row][col] == 'O')? -1 : 0;
            }
            if (count == 3 || count == -3)
            {
                return count / abs(count); // Return either 1 or -1
            }
        }
    
        // Check Left-to-Right downward Diagonal:
        count = 0;
        for(col = 0; col < 3; ++col)
        {
            count += (board[col][col] == 'X')?  1 :
                     (board[col][col] == 'O')? -1 : 0;
        }
        if (count == 3 || count == -3)
        {
            return count / abs(count); // Return either 1 or -1
        }
    
        // Check Left-to-Right upward Diagonal
        count = 0;
        for(col = 0; col < 3; ++col)
        {
            count += (board[col][2-col] == 'X')?  1 :
                     (board[col][2-col] == 'O')? -1 : 0;
        }
        if (count == 3 || count == -3)
        {
            return count / abs(count); // Return either 1 or -1
        }
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      我可能会做类似以下的事情。您可以使用不带 for 循环的 if 语句,但这会减少源代码的大小。

      // board[3][3]:
      //    [0][0] | [1][0] | [2][0]
      //    -------+--------+-------
      //    [0][1] | [1][1] | [2][1]
      //    -------+--------+-------
      //    [0][2] | [1][2] | [2][2]
      //
      int checkforwin(char board[3][3])
      {
          int x;
      
          for(x = 0; x < 3; x++)
          {
            // check vertical lines
            if ((board[x][0] != '\0') && 
                (board[x][0] == board[x][1]) && 
                (board[x][0] == board[x][2]))
               return(board[x][0] == 'O' ? -1 : 1);
      
            // check horizontal lines
            if ((board[0][x] != '\0') &&
                (board[0][x] == board[1][x]) && 
                (board[0][x] == board[2][x]))
               return(board[0][x] == 'O' ? -1 : 1);
          };
      
          // check top left to bottom right diagonal line
          if ((board[0][0] != '\0') && 
             (board[0][0] == board[1][1]) && 
             (board[0][0] == board[2][2]))
            return(board[0][0] == 'O' ? -1 : 1);
      
          // check bottom left to top right diagonal line
          if ((board[2][0] != '\0') && 
             (board[2][0] == board[1][1]) &&
             (board[0][0] == board[0][2]))
            return(board[2][0] == 'O' ? -1 : 1);
      
          // no winner
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        If 语句是使用这种数据结构的方法。您可以使用位掩码,但对于带有字符串元素的给定数据结构 ('X' 'O'),位掩码将非常难看,只会使事情变得不必要地复杂。进行测试的最短和最清晰的方法是使用 If 语句。

        如果您使用不同的数据结构,您也许可以让事情变得更简单。例如,如果板子被存储为一个位数组'011011001',那么位掩码就干净多了。

        【讨论】:

        • 但是板上的每个位置可以有三种状态,'x''o'或者空白,所以你需要18位来表示板子所有可能的状态。
        • @user3386109 您使用第二个位图来指示一个正方形是否被占用。
        • 听起来不错,但我很想知道生成的代码是什么样的,以及它是否实际上比八个 if 语句解决方案更简单和/或更干净。
        猜你喜欢
        • 1970-01-01
        • 2022-12-16
        • 2017-02-16
        • 2012-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多