生成所有独特的Tic Tac Toe板的列表

时间:2011-09-19 04:23:05

标签: algorithm tic-tac-toe

我想生成一个文本文件,其中包含0 = Blank,1 = X和2 = O结构中的所有19,683 Tic-Tac-Toe电路板布局。不幸的是数学并不是我的强项我似乎无法在任何地方找到任何这方面的例子。

这不是我向你保证的作业。我打算通过Minimax计算器运行此数据,以生成包含RGB值的图像,RGB值表示基于电路板设置的最佳移动。我正在开发一个不支持函数的平台Tic-Tac-Toe(它是事件驱动的)所以我会将我的电路板转换为游戏中的数字,然后在图像中查找像素的RGB,以指示最佳移动是。这是一个厚颜无耻的解决方法,但不需要比145x145像素图像更多的RAM(145x145 = 21,025,因此每个像素代表基于电路板的推荐移动有效)。这也意味着我不必咀嚼CPU时间,这是另一个好处。

7 个答案:

答案 0 :(得分:4)

有9个位置和一个带3个字母的字母(X,O,空)。可能的组合总数为3 ^ 9 = 19683。

for(int i = 0; i < 19683; ++i)
{
    int c = i;
    for (int j = 0; j < 9; ++j)
    {
        cout << (c % 3) << " ";
        c /= 3;
    }

    cout << endl;
}

答案 1 :(得分:4)

我的Minimax for Tic Tac Toe实现生成一个5477个节点的树。每个节点都包含一个Tic Tac Toe板状态,并满足以下条件:

  • 根据Tic Tac Toe规则,棋盘状态有效,玩家必须轮流放置X和Os。即没有董事会职位,如:

    XXX
    XXX
    XXO

  • 树的所有叶子包含根据Tic Tac Toe规则考虑最终游戏状态的棋盘状态(玩家1获胜,玩家2获胜或抽奖)。即没有树的分支,如:

    XOX
    OXO
    X
     |
     |
    XOX
    OXO&lt; - 没有点这个节点,因为它的父节点有一个结束位置(X赢) XO

  • 给定的树节点可能有多个父节点(多个树节点可能具有相同的子节点)。

    即。由于给定的板状态可以通过多个不同的移动序列获得,当创建树节点时,如果已经有一个节点包含我即将(重新)生成的板状态,我重用(重新连接)现有节点。这样,当我从底部到顶部对树节点进行评分时(根据Minimax理论),我不必为树枝的某个子集多次计算相同的分数(如果我不重用,这将是相同的现有节点)。

I've also found a book which mentions the 5477 unique, distinct, valid Tic Tac Toe board states.

  

Tic-Tac-Toe有5477个有效状态,不包括空位

答案 2 :(得分:3)

Generating all possible game boards (depth first search works best) and excluding duplicates under rotation and mirroring results in 765 boards. 626 are mid game, 91 games X has won, 44 games O has won and 3 games are a draw.

If you are only intresetd in the optimal moves you can simply use https://xkcd.com/832/ as reference. Makes a nice poster.

But all the fun in tic-tac-toe is in implementing it. So I leaves that to the reader. Just a few tips:

  1. Every tile on the board has 3 states so you can encode the board as number in base 3. For simpler math I use base 4 (2 bit per tile so I only need to shift). I then have a hash function that generates that number for a board under all possible rotations and mirroring (8 cases) and returns the minimum value. By that I can lookup if I played that board already.

  2. Starting with an empty board place a mark on the board in every possible position, check if the board was already played, mark it played, check if the game is over and count the board, otherwise recurse alternating players.

  3. The first X can only be set in 3 places (considering rotation and mirroring) and all later moves have at most 8 choices. Instead of encoding the absolute tile to be played you can only count empty tiles and encode that in 3 bit.

  4. Using the above hash function gives us 626 boards where we have to make a move (you just have to reverse the rotation/mirroring to get the real move out of the data). There is probably a not much larger relative prime number so that each board fits in a hash table without collisions. Lets say that number is 696 (I know, not relative prime). At 3 bits per board that would only need 261 byte of data to store the best move for every possible game.

  5. Since you are playing perfect the number of reachable boards goes way down again. Build a data set for playing X and one for playing O and you can cut that way down again.

  6. Want to make it even smaller? Just program in a few basic rules like: The first O should be in the middle if free. With 2 "my color" in a row complete the row. With 2 "other color" in a row block the row and so on. Wikipedia has a list of 8 rules but I think I had less when I did it that way.

  7. A perfect tic-tac-toe opponent is boring. You can never win. Why not make the game learn from failure? Keep track of all 626 boards and their possible moves. When a move results in a loss remove that move from the board. If the board has no more moves left remove from all boards leading to this one the move that causes it (recursively if that removes the last move there). Your game will never loose the same way twice. Similary for moves resulting in a win you remove the opponents move from the list of possible ones and if there are none left you mark the previous move as a sure win. That way if you can force a win you will always force it from now on. Playing X can you get it to loose 91 ways? Playing O can you get it to loose all 44 ways?

答案 3 :(得分:2)

由于您需要电路板布局,因此只有少数电路板布局(19683)。

你可以蛮力生成所有这些。每个盒子只有3种可能性。并且有9个盒子,只是贯穿所有盒子。

编辑:

int c = 0;
while (c < 262144){
    bool valid = (c & 3) < 3;
    valid &= ((c >>  2) & 3) < 3;
    valid &= ((c >>  4) & 3) < 3;
    valid &= ((c >>  6) & 3) < 3;
    valid &= ((c >>  8) & 3) < 3;
    valid &= ((c >> 10) & 3) < 3;
    valid &= ((c >> 12) & 3) < 3;
    valid &= ((c >> 14) & 3) < 3;
    valid &= ((c >> 16) & 3) < 3;

    if (valid){
        int i = c;
        int j = 0;
        while (j < 9){
            cout << (i & 3) << " ";
            i >>= 2;
            j++;
        }
        cout << endl;
    }

    c++;
}

这将打印出所有19,683个电路板布局。我不确定你想要什么格式,但从输出中提取它应该相当容易。

答案 4 :(得分:2)

你可以简单地蛮力通过。每个方块都是0,1或2所以......:

for (int i1 = 0; i1 <= 2; i++) {
    for (int i2 = 0; i2 <= 2; i++) {
        // ...
        // lot's of nested for loops
        // ...
    }
}

或者,如果你不能为此烦恼;)那么你可以为它写一个递归函数:

int square[9];
void place(int square_num) {
    if (square_num == 9) {
        output the current configuration
    }

    for (int i = 0; i <= 2; i++) {
        square[square_num] = i;
        place(square_num+1);
    }
}

然后就这样做:

place(0);

并且会发生魔法。

顺便说一下,这是用c ++编写的。

答案 5 :(得分:1)

与早期的解决方案类似,但在Python中更容易阅读。

for i in range(3**9):
     c = i
     for j in range(9):
         if j % 3 == 0:
             print("")
         print(str(c % 3) + " ", end='')
         c //= 3
     print("")

答案 6 :(得分:1)

总共可能的移动不是3 ^ 9,因为它包括井字游戏中许多不允许的移动。 (X-O)或(O-X)必须始终等于1。 正如https://stackoverflow.com/a/25358690/13557570所提到的,总共可能移动5477

使用numpy并将状态减少到5814的Python代码:

import numpy as np
StatesMatrix = np.zeros((3**9,9))
for i in range(3**9):
     c = i
     for j in range(9):
       StatesMatrix[i][j] = c % 3
       c //= 3
StatesMatrix1 = np.zeros((5814,9))
k = 0
for i in range(0,StatesMatrix.shape[0]):
  if (np. count_nonzero(StatesMatrix[i] == 1) - np. count_nonzero(StatesMatrix[i] == 2)) == 1 or (np. count_nonzero(StatesMatrix[i] == 2) - np. count_nonzero(StatesMatrix[i] == 1))== 1:
    StatesMatrix1[k] = StatesMatrix[i]
    k = k + 1
    
print(StatesMatrix1)
print(k)