How many different chess games and board states are there in Tic-Tac-Toe (python implementation)

Table of contents

1 Introduction

2. How to deduplicate?

3. Code implementation

3.1 Symmetric equivalence judgment

3.2 find_neighbor() modification

3.3 Main program and running results

4. Extended thinking


1 Introduction

        In the first two blogs, a python program that traverses and searches all Tic-Tac-Toe chess games is implemented.

Realization of Tic-Tac-Toe possible game search (python) The side with 3 pieces in a line wins. Tic-Tac-Toe changes are simple, and the number of possible positions and games is very limited (compared to Chinese chess, Japanese chess, Go, etc., it is not even a drop in the bucket! How many possible positions and possible numbers of games are there? You can give the answer after you finish it), so it often becomes a teaching example for searching and searching, and it is also a good topic for you. This series considers implementing a Tic-Tac-Toe AI, and completes this implementation step by step from shallow to deep. https://blog.csdn.net/chenxy_bwave/article/details/128506352 Implementation of Tic-Tac-Toe possible game traversal (python) - A python program for a game of Tac-Toe game. The next question is, how many total possible positions are there in the game of Tic-Tac-Toe? Note that a chess game refers to the sequence of board state changes caused by two players playing chess alternately until the final game. Therefore, even if the set of board states contained is exactly the same, if the order of appearance of the board states is different, it is also a different game. Based on the previous article, this paper further realizes the realization of searching all possible chess positions of the Tic-Tac-Toe game. https://blog.csdn.net/chenxy_bwave/article/details/128513299

        According to the implementation of the previous article (searching all possible Tic-Tac-Toe chess games), the results show that there are 255168 chess games and 5478 board states. But is this result correct?

        Strictly speaking it is not true.

        Because the above implementation does not take into account the symmetry of the board of the Tic-Tac-Toe game. For example, the first hand is essentially the same in any of the four corners. After taking into account the repetition caused by symmetry, the total number of possible games and board states will be greatly reduced.

        The symmetry of the chessboard includes the following 4 types of symmetry:

  1. rotational symmetry
  2. diagonal symmetry
  3. Up and down symmetrical
  4. bilateral symmetry

        Examples of the above various symmetries are shown in the figure below:

2. How to deduplicate?

        How to take the symmetry into account for repetition removal to get the result of the real number of different chess games and the number of board states?

        The idea given in this paper is: when calculating all the adjacent nodes of a certain node during the chess game traversal process, add deduplication judgment, regard the states satisfying the above-mentioned various symmetries as the same state, keep only one of them and eliminate the rest of. In this way, the result of the final traversal represents an essentially different chess game. Here are two examples.

        Example 1: From the initial board state, player1 plays the first move.

         Although the player should be able to make a move in any of the 9 positions, after considering the symmetry equivalence, there are actually only 3 possibilities left. The graphs marked with a red "X" in the figure are the positions excluded due to symmetric equivalence.

        Example 2: After player1 makes a move, the possible positions of player2 playing chess

       Finally, when counting all the different board states based on the chess game, it is also necessary to de-duplicate and eliminate duplicates, and finally get all possible different board states.

3. Code implementation

3.1 Symmetric equivalence judgment

        The implementation of symmetric equivalence judgment is as follows. Here is the most direct and simple implementation. There should be a more efficient implementation.

def is_rot_symmetry(s1,s2):
    rot90_symm = \
        s1[0] == s2[6] and s1[1] == s2[3] and s1[2] == s2[0] and \
        s1[3] == s2[7] and s1[4] == s2[4] and s1[5] == s2[1] and \
        s1[6] == s2[8] and s1[7] == s2[5] and s1[8] == s2[2]

    rot180_symm = \
        s1[0] == s2[8] and s1[1] == s2[7] and s1[2] == s2[6] and \
        s1[3] == s2[5] and s1[4] == s2[4] and s1[5] == s2[3] and \
        s1[6] == s2[2] and s1[7] == s2[1] and s1[8] == s2[0]

    rot270_symm = \
        s1[0] == s2[2] and s1[1] == s2[5] and s1[2] == s2[8] and \
        s1[3] == s2[1] and s1[4] == s2[4] and s1[5] == s2[7] and \
        s1[6] == s2[0] and s1[7] == s2[3] and s1[8] == s2[6]
        
    return rot90_symm or rot180_symm or rot270_symm
    
    
def is_diagonal_symmetry(s1,s2):
    symm1 = \
        s1[0] == s2[0] and s1[4] == s2[4] and s1[8] == s2[8] and \
        s1[1] == s2[3] and s1[3] == s2[1] and \
        s1[2] == s2[6] and s1[6] == s2[2] and \
        s1[5] == s2[7] and s1[7] == s2[5]

    symm2 = \
        s1[2] == s2[2] and s1[4] == s2[4] and s1[6] == s2[6] and \
        s1[1] == s2[5] and s1[5] == s2[1] and \
        s1[0] == s2[8] and s1[8] == s2[0] and \
        s1[3] == s2[7] and s1[7] == s2[3]
    
    return symm1 or symm2
    
def is_updown_symmetry(s1,s2):
    return \
        s1[0] == s2[6] and s1[1] == s2[7] and s1[2] == s2[8] and \
        s1[3] == s2[3] and s1[4] == s2[4] and s1[5] == s2[5] and \
        s1[6] == s2[0] and s1[7] == s2[1] and s1[8] == s2[2] 
    
def is_leftright_symmetry(s1,s2):
    return \
        s1[0] == s2[2] and s1[3] == s2[5] and s1[6] == s2[8] and \
        s1[1] == s2[1] and s1[4] == s2[4] and s1[7] == s2[7] and \
        s1[2] == s2[0] and s1[5] == s2[3] and s1[8] == s2[6] 
    
def is_symmetry(s1,s2):
    return is_rot_symmetry(s1,s2) or       \
            is_diagonal_symmetry(s1,s2) or \
            is_updown_symmetry(s1,s2) or   \
            is_leftright_symmetry(s1,s2)

3.2 find_neighbor() modification

        On the basis of the original find_neighbor(), a symmetrical equivalence judgment is added to remove the duplicate disk status, and the obtained code is as follows:

def find_neighbor(s):
    neighbor_list = []
    # decides whose turn
    if s.count(1) == s.count(2):
        turn = 1
    elif s.count(1) == s.count(2) + 1:
        turn = 2
    else:
        print('Invalid input state: ', s)
        return None
    
    for k in range(len(s)):
        if s[k] == 0:
            s_next = list(s)
            s_next[k] = turn
            neighbor_list.append(tuple(s_next))

        
    neighbor_list2 = []
    
    for k in range(0,len(neighbor_list)):
        is_unique = True
        for s in neighbor_list2:
            if is_symmetry(neighbor_list[k], s):
                is_unique = False
                break
        if is_unique:
            neighbor_list2.append(neighbor_list[k])            
            
    return neighbor_list2

3.3 Main program and running results

        Other functions have not been modified (please refer to the previous article).

        In the main program, when all possible disk states are counted at the end, deduplication processing based on symmetric judgment is also added. code show as below:

if __name__ == '__main__':

    # Initialization
    s0     = tuple([0] * 9)
    path   = [s0]
    tStart = time.time()
    dfs(path)
    tStop  = time.time()
    
    state_set = set() # set() remove the repeated items automatically
    for k in range(len(path_list)):
        path = path_list[k]
        
        for s in path:
            state_set.add(s)
                 
    # Counting the different board status, with symmetrically repeated ones
    # states = state_set
    states = []
    for s1 in state_set:
        is_unique = True
        for s2 in states:
            if is_symmetry(s1, s2):
                is_unique = False
                break
        if is_unique:
            states.append(s1)                        
    
    print('Totally there are {0} games'.format(len(path_list)))
    print('Totally there are {0} board states'.format(len(states)))
    print('Time cost:  {0:6.2f} seconds'.format(tStop-tStart))

        The result of the operation is as follows:

Totally there are 26830 games
Totally there are 765 board states
Time cost:    0.34 seconds

        That is to say, there are only 26830 different chess games, and only 765 different board states!

4. Extended thinking

        Then do not calculate the simulation, but calculate the above results by manual calculation?

        It should be a difficult combinatorial counting problem. . .

        Next, we will consider: (1) the realization of the basic human-computer interaction Tic-Tac-Toe game program (2) the realization of Tic-Tac-Toe AI based on the minimax algorithm and the aplha-beta pruning algorithm. . .

        Next:

        https://blog.csdn.net/chenxy_bwave/article/details/128555550

Guess you like

Origin blog.csdn.net/chenxy_bwave/article/details/128521062