【问题标题】:Optimal algorithm for seeing who won a tick tac toe game查看谁赢得井字游戏的最佳算法
【发布时间】:2012-03-19 16:05:50
【问题描述】:

我有一个完整的井字游戏板。它是 3 x 3。我并不是真的要代码(尽管这会有所帮助),但是什么算法最适合查看谁赢了?换一种说法是,我应该研究哪些算法才能知道谁赢了?

唯一真正想到的是蛮力。只是测试所有可能性,但我知道必须有更好的方法。

【问题讨论】:

  • 遍历行,遍历列,然后检查对角线。
  • 你问的是人工智能吗?像在最小-最大树上进行 alpha beta 修剪?还是井字游戏矩阵中的模式搜索?
  • 更像是静态搜索。游戏已经完成。它在一个文本文件中。我在文本文件中阅读,需要确定谁赢了。但它必须尽可能优化。所以我不需要确定最好的下一步是什么或类似的事情。
  • 请记住,标准算法执行 24 次操作。动态解决问题可能会导致内存分配延迟,从而阻止理论时间复杂度在实验上更有效地处理小型数据集(例如,合并排序)。我们真的在争论井字游戏的优化技术吗?
  • 是什么让你说有更好的方法?

标签: java algorithm tic-tac-toe


【解决方案1】:

我最近(重新)学到的重要一课:当搜索空间足够小时,只需使用蛮力。

在 3x3 棋盘上,有八种可能的获胜顺序(行、列和对角线)。这为您提供了 24 次比较,以验证一个人在所有 it 单元格中是否具有相同的玩家标记。即使在速度非常慢的计算机上,24 次比较也完全不需要时间。

【讨论】:

  • ...而且:“小”通常出乎意料地大。
【解决方案2】:

这里是最好的聪明最优的算法:(这是一个众所周知的技巧,所以我不吹嘘,只是赞美算法)

定义:单元格命名如下:

A31  A32  A33
A21  A22  A23
A11  A12  A13

这些棋子是 W(hite) 或 B(lack)。有 8 种获胜组合:[A11,A12,A13]、[A11,A21,A31]、[A13,A22,A31] 等。给每个组合起一个名字:C1..C8.:

C1 =def= [A11,A12,A13]
C2 =def= [A21,A22,A23]
C3 =def= [A31,A32,A33]
C4 =def= [A11,A21,A31]
C5 =def= [A12,A22,A32]
C6 =def= [A13,A23,A33]
C7 =def= [A11,A22,A33]
C8 =def= [A13,A22,A31]

定义从单元格到一组获胜组合的映射:

A11 --> C1,C4,C7
A12 --> C1, C5
A22 --> C2, C5, C7, C8

等等

所以每个单元格 A 都指向其中包含 A 的那些组合。

为双方玩家保留一组可能的获胜组合。一开始,两位玩家都有所有 8 种组合。

Poss_W = C1, C2, C3, C4, C5, C6, C7, C8
Poss_B = C1, C2, C3, C4, C5, C6, C7, C8

当 W 在单元格 A 中下棋时,从 B 中删除相应的获胜组合。例如,当白棋下 A12 时,从黑方可能的获胜组合列表中删除 C1、C5。

游戏结束后,具有非空可能获胜组合集合的玩家获胜。如果 Poss_W 和 Poss_B 都为空,则游戏为平局。

【讨论】:

  • 算法不依赖于移动的顺序;如果你愿意,你可以等待游戏结束。如果你愿意,你可以先“玩”所有的黑棋,然后再“玩”所有的白棋。您可以拿起完成的棋盘并以任何顺序读取单元格值(B 或 W),并根据需要应用移动。该算法适用于正在进行的游戏已完成的游戏。
  • 哼,这和我的做法差别不大。
  • 也许算法并没有很亮眼,因为井字棋是一个简单的游戏。然而,这是一个非常强大的想法(明确列举“解决方案”并将元素映射到解决方案集),也可以用于更复杂的游戏和谜题。让我们玩一个游戏,我们将俄罗斯方块放在棋盘上,第一个无法移动的就输了。可以修改算法以快速查看移动列表是否导致终端板位置。 (元素:单件放置;组合:所有元素的集合;即每个放置都使其他一些放置成为不可能)
【解决方案3】:

只需使用地图diagonal -> number of checks in that diagonal

当其中一个条目等于三时,您就有了获胜者。

【讨论】:

    【解决方案4】:

    简单的问题最好用简单的代码来解决。

    这是一个蛮力和直接的 javascript ES6 解决方案

    const tictactoeBoard = ['x', 'o', 'x',
                            'o', 'x', 'o',
                             '', 'o', 'x'];
    
    const winningCombinations = [
        /// horizontal
        [0, 1, 2],
        [3, 5, 6],
        [6, 7, 8],
        /// vertical
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        /// diagonal
        [0, 4, 8],
        [6, 4, 2],
    ];
    
    const checkForWinner = () => {
        for (const c of winningCombinations) {
            const fieldsToCheck = [
                tictactoeBoard[c[0]],
                tictactoeBoard[c[1]],
                tictactoeBoard[c[2]]
            ];
            /// check if every fields holds a trueish value and
            /// is the same value across the array aka won the game
            if (
                fieldsToCheck.every(f => f && f === fieldsToCheck[0])
            ) {
                return fieldsToCheck[0]
            }
        }
        return
    }
    
    console.log(checkForWinner());

    【讨论】:

      【解决方案5】:

      井字游戏的轻微优化来自于知道在第 5 步之前不可能获胜。因此,如果您的游戏保持移动计数,您可以减少检查整个棋盘状态的次数。

      另一个优化是知道,如果您的算法找到任何包含 X 和 O 的行、列或对角线,那么它永远不会成为赢家。您无需再次检查。你可以从你的搜索空间中剔除那些无法获胜的东西。

      上述优化的一个副作用是,在搜索空间变空的情况下,它可以让您在等待棋盘填满之前更快地检测到平局。

      【讨论】:

        【解决方案6】:

        如果每一步都需要检查游戏是否结束,可以缓存临时结果。

        对于每一行、每一列和对角线,存储每个玩家的标记数。在每一步之后增加适当的值。如果数字是 3,那么您就有中奖了。

        【讨论】:

          【解决方案7】:

          如果不检查整个棋盘状态,就无法确定获胜者。如果您想在每回合结束时执行检查,请遍历每一行、每一列和两条对角线,检查是否相等(例如:board[0][0] == board[1][0] == board[2][0] 等)。如果您想在玩井字游戏时跟踪棋盘状态,您可以使用dynamic programming,尽管这有点矫枉过正。如果您使用异常大的板,否则动态方法将很有用,否则需要大量步骤才能找到赢家。还值得注意的是,标准井字游戏足够小,高效的算法不会对性能产生丝毫影响。

          【讨论】:

          • 请提出建设性的批评?
          猜你喜欢
          • 2011-07-10
          • 1970-01-01
          • 2022-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-13
          • 2017-02-16
          • 1970-01-01
          相关资源
          最近更新 更多