Tic-Tac-Toe可能棋局搜索的实现(python)
创始人
2024-05-03 18:23:10

目录

1. 前言

2. 算法流程

3. 代码实现

3.1 终局及胜负判定方法

3.2 搜索邻节点

3.3 打印棋盘状态

3.4 代码

4. 小结


1. 前言

        Tic-Tac-Toe中文常译作井字棋,即在3 x 3的棋盘上,双方轮流落子,先将3枚棋子连成一线的一方获得胜利。Tic-Tac-Toe变化简单,可能的局面和棋局数(注意区分局面与棋局!)都很有限(相比中国象棋、日本象棋、围棋等来说连九牛一毛都不到!具体有多少可能的局面以及可能的棋局数,本系列完成以后就可以给出答案了),因此常成为博弈论和游戏树搜寻的教学例子,同时也是人工智能的一道好题目。

        本系列考虑实现一个Tic-Tac-Toe AI,以由浅入深循序渐进的方式来逐步完成这个实现。途中将会介绍minimax、alpha-beta pruning、MCTS等等算法,最终实现一个不可战胜的Tic-Tac-Toe AI,并以此为基础考虑更复杂的博弈游戏AI的实现。

        Tic-Tac-Toe棋盘为3x3的正方形,共9个格子(tile or grid)。棋盘状态可以用一个含9个元素的一维数组表示,或者一个3x3的二维数组表示。这里假定用9个元素的一维数组board[9]表示,其中[0],[1],[2]表示棋盘的最上一行从左到右三个格子;[3],[4],[5]表示棋盘的中间一行从左到右三个格子;[6],[7],[8]表示棋盘的中间一行从左到右三个格子。

        每个格子有三种状态,分别用0,1,2表示如下:

        0: empty tile

        1: X tile,假定总是X先走。

        2: O tile

        考虑以树的形式来表达所有可能的游戏过程(或者说棋局)。其中每个盘面状态表示一个节点,树的根节点就是棋盘的初始状态。一个游戏过程(或者说棋局)表示一局可能的Tic-Tac-Toe游戏的按顺序构成的盘面状态序列。比如说,第1个状态肯定是初始状态;第2个状态是由player1下了一手棋后所到达的状态;第3个状态是由player2下了一手棋后所到达的状态;。。。以下以此类推。

       本文首先考虑搜索一种可能的游戏过程,也就是从树的根节点到达某个可能的终局状态的路径。这个可以用常见的深度优先搜索或者广度优先搜索的方式来实现。

        初始状态显然为s0 = [0,0,0, 0,0,0, 0,0,0].

        终局可以分为以下几种可能:

  1. 决出胜负的局面(双方总共手数可能等于9手也可能小于9手)
  2. 平局(双方手数必然等于9手)

2. 算法流程

        用深度(或广度)优先算法解决这一搜索问题的伪代码描述如下:

将S0入栈或队列(深度优先搜索是使用栈,广度优先搜索使用队列)

将S0加入visited(通常用字典实现)

While (1):

       从栈顶取出一个节点s

       If s is end-of-game state:

              记录结果并退出

       Else:

       For each neighbor of s:

              If neighbor is not yet visited:

                     将neighbor入栈

                     将neighbor加入visited

        Visit用python set实现,这样可以利用python set的高效的查询效率。考虑到python set的元素必须是hashable类型,所以棋盘状态用tuple表示。 

3. 代码实现

3.1 终局及胜负判定方法

        参见is_endofgame()

        判定当前局面是否终局。当前用了个比较呆笨的办法。用win_comb预存了所有可能的胜利局面,然后针对当前局面,遍历是否某一个玩家的棋子满足win_comb中的某个组合。注意,由于在搜索过程中,是按照每人一手交替的方式前进的,不可能存在两个棋手的棋子都满足胜局条件的情况。

        本函数给出的结果包括:是否终局;如果是的话,则进一步给出胜负结果(是否平局,不是的话赢家是谁)。

3.2 搜索邻节点

        参见find_neighbor()

        针对当前棋盘状态,给出可能的下一个棋盘状态的列表。

        首先根据当前棋盘状态确定接下来该谁下(whose turn),然后遍历所有空的棋盘格(tile or grid)给出所有可能的下一个棋盘状态的列表。

        

3.3 打印棋盘状态

        参见print_board()

        将用一维数组表示的棋盘状态用二维的方式描绘出来以方便查看。

        注意,如前所述,总是假定“X”先走。一个可能的终局状态如下所示:

 

3.4 代码

 

# -*- coding: utf-8 -*-
"""
Created on Sat Dec 31 12:53:10 2022@author: chenxy
"""import random
from collections import dequewin_comb=((0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6))def is_endofgame(s):end_flag = Falsewinner   = 0 # draw or tie.for comb in win_comb:if s[comb[0]]==1 and s[comb[1]]==1 and s[comb[2]]==1:winner = 1end_flag = Truebreakelif s[comb[0]]==2 and s[comb[1]]==2 and s[comb[2]]==2:winner = 2end_flag = Truebreakelse:continueif (not end_flag) and s.count(0)==0:end_flag = Truereturn end_flag, winner        def find_neighbor(s):neighbor_list = []# decides whose turnif s.count(1) == s.count(2):turn = 1elif s.count(1) == s.count(2) + 1:turn = 2else:print('Invalid input state: ', s)return Nonefor k in range(len(s)):if s[k] == 0:s_next = list(s)s_next[k] = turnneighbor_list.append(tuple(s_next))return neighbor_list[::-1]def print_board(s):print('----------')for k in range(len(s)):if k%3 == 2:end = ' \n----------\n'        else:end = ' | 'if s[k] == 1: char = "X"elif s[k] == 2: char = "O"else:char=' 'print(char,end=end)# Initialization
s0   = tuple([0] * 9)
path = []
q    = deque()
visited = set()# Put initial state s0 into stack/queue
q.append(s0)
visited.add(s0)while 1:s = q.pop() # DFS: Depth First Search# s = q.popleft() # BFS: Breadth First Searchpath.append(s)print(s)end_flag, winner = is_endofgame(s)if end_flag:s_end = s breakneighbors = find_neighbor(s)# random.shuffle(neighbors)for neighbor in neighbors:if neighbor not in visited:q.append(neighbor)visited.add(neighbor)for s in path:print_board(s)        

        运行结果如下: 

 (0, 0, 0, 0, 0, 0, 0, 0, 0)
(1, 0, 0, 0, 0, 0, 0, 0, 0)
(1, 2, 0, 0, 0, 0, 0, 0, 0)
(1, 2, 1, 0, 0, 0, 0, 0, 0)
(1, 2, 1, 2, 0, 0, 0, 0, 0)
(1, 2, 1, 2, 1, 0, 0, 0, 0)
(1, 2, 1, 2, 1, 2, 0, 0, 0)
(1, 2, 1, 2, 1, 2, 1, 0, 0)
----------
  |   |   
----------
  |   |   
----------
  |   |   
----------
----------
X |   |   
----------
  |   |   
----------
  |   |   
----------
----------
X | O |   
----------
  |   |   
----------
  |   |   
----------
----------
X | O | X 
----------
  |   |   
----------
  |   |   
----------
----------
X | O | X 
----------
O |   |   
----------
  |   |   
----------
----------
X | O | X 
----------
O | X |   
----------
  |   |   
----------
----------
X | O | X 
----------
O | X | O 
----------
  |   |   
----------
----------
X | O | X 
----------
O | X | O 
----------
X |   |   
----------

4. 小结

        以上实现只是沿着节点树的某一条确定性的分支走到底。由于只是寻找某(任意)一种游戏进行过程的路径,沿着任何一条分支都可以到达终局状态(可能分胜负,也可能是平局)。所以以上程序搜索得到的结果非常平凡。

        如何让它变得更有趣一些呢?追加一点随机性。如下所示追加将邻节点列表随机打乱顺序的处理:

        这样就可以在每次运行时得到一个不同的棋局。

        下一篇将考虑Tic-Tac-Toe(3x3)所有可能的游戏过程,也即所有可能的(从初始状态到达终局的)路径。并由此可以统计出所有可能的盘面状态总数。

 

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...