Table of contents
3.1 Symmetric equivalence judgment
3.2 find_neighbor() modification
3.3 Main program and running results
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:
- rotational symmetry
- diagonal symmetry
- Up and down symmetrical
- 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