Tic-Tac-Toe可能棋局搜索的实现(python)

news2024/9/28 13:16:45

目录

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 deque

win_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 = False
    winner   = 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 = 1
            end_flag = True
            break
        elif s[comb[0]]==2 and s[comb[1]]==2 and s[comb[2]]==2:
            winner = 2
            end_flag = True
            break
        else:
            continue
    
    if (not end_flag) and s.count(0)==0:
        end_flag = True
            
    return end_flag, winner        

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))
    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 Search
    path.append(s)
    print(s)
    end_flag, winner = is_endofgame(s)
    if end_flag:
        s_end = s 
        break
    
    neighbors = 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)所有可能的游戏过程,也即所有可能的(从初始状态到达终局的)路径。并由此可以统计出所有可能的盘面状态总数。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

✿✿✿JavaScript --- jQuery框架一

目 录 1.jQuery的介绍和在线学习网址以及下载网址 2.jQuery的功能和优势 3.引用jQuery库和第一个案例 4.jQuery代码格式和注释 5.jQuery如何达到获取原生的DOM对象 6.jQuery选择器(CSS对比版) (1)常见选择器 (2)高级选择器以及方法 (3)属性选择…

单片机基础知识

目录 一、单片机基本认知 二、IO口输入和输出 三、点亮一个LED 1、编程实现LED闪烁 2、按下按键点亮灯 3、按键的消抖 4、记录状态位来控制LED 一、单片机基本认知 单片机和PC电脑相比的话,相当于电脑的主板 单片机是一种集成电路芯片。单片机又称单片微控…

S32DS_Optimization优化选项

S32DS_Optimization优化选项 S32DS3.4的选项, 右击工程后出现的选项 char is signed 让char类型为有符号, 类似signed char bitfield is unsigned 当声明不使用signed/unsigned时, 控制位字段是否无符号; 默认signed(因为基本整形(int等)也是signed) Function sections 默认情…

C语言—动态内存管理和柔性数组

目录 1. C/C的内存开辟 2. 为什么存在动态内存分配 3. 动态内存函数介绍 3.1 malloc 和 free 3.2 calloc() 3.3 realloc() 4. 常见的动态内存错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟的空间越界访问 4.3 对非动态内存开辟的空间进行free(&…

外贸谈判前需要注意的4p

01Past了解客户的过去 当一个客户找到我们的时候,作为业务,我们需要第一时间回复客户所问到的问题。让客户感受到,他需要的产品我们公司可以提供。于此同时,在客户沟通意愿度较高的时候,我们不妨多跟客户沟通一下几个问…

Java学习笔记 --- MySQL-索引和事务

一、索引 索引的原理 1、没有索引会全表扫描,从而查找速度会很慢 2、使用索引会形成一个索引的数据结构,比如二叉树 3、索引的代价 磁盘占用 对 dml(update、delete、insert)语句的效率影响 索引的类型 1、主键索引&#xff…

2022年圣诞节 | 用代码实现简单圣诞树

2022年圣诞节到来啦,很高兴这次我们又能一起度过~ 一、前言 本文我们用 Python 来画一棵带背景音乐效果的雪夜圣诞树以及使用 HTMLCSSJS 在页面渲染出动态圣诞树,所涉及到的源码均来自GitHub开源站点。 二、效果展示 Python HTMLCSSJS 三、编码实现 …

(六)汇编语言——包含多个段的程序

目录 使用数据 使用栈 代码 总结 使用数据 首先,我们来看一个问题,就是编程计算0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H的和,结果存在ax寄存器…

《剑指offer》每日三题

这里使用的是题库: https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 07. 重建二叉树剑指 Offer 14- I. 剪绳子剑指 Offer 14- II. 剪绳子 II剑指 Offer 07. 重建二叉树 递归思想: 代码 class Solution {int pPre0;//用于遍历preorde…

深入理解HashMap

HashMap集合 1. HashMap集合简介 HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。hashMap的实现不是同步的,这就意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。 JDK1.8之前…

短视频上热门技巧总结,这样做你也可以快速上热门。

最近开始做短视频,找了很多短视频运营创作技巧,但能上热门的只有那么几个,经过近一周的分析,结合了我赢上短视频运营创作技巧,得到了以下几个经典技巧合集:学会一个就值了。 首先说一下:什么样的…

团簇生长过程-Ovito渲染

文章目录一、选择出团簇原子和非团簇原子1. 选择团簇原子2. 删除非团簇原子二、选择出团簇原子和非团簇原子1. 团簇分析2. 团簇具体信息三、渲染团簇1、 对团簇进行选择2、 获得团簇渲染后的结果四、渲染结果五、 案例dump下载博文《根据近邻列表法识别团簇—冷凝成核 MatlabOv…

java06-面向对象1

一:面向对象学习内容: 1.java 类及成员:属性、方法、构造器、代码块、内部类 2.面向对象三大特征:封装、继承、多态 3.其他关键字:this、super static、final、abstact、interface 、package、import 二&#xff…

iPhone/iPad上值得推荐的5个免费PDF转Word

PDF 文件是在不同平台上传输数据的最便捷方式,可确保保持高端信息质量。处理将不同文件格式转换为 PDF 的任务通常很麻烦,尤其是在 iOS 设备上。为了解决这个问题,这里讨论了您可以轻松依赖的前 5 个iPhone PDF 转换器工具。 适用于 iPhone 和…

多传感器融合定位六-惯性导航原理及误差分析

多传感器融合定位六-惯性导航原理及误差分析1. 惯性技术简介1.1 惯性技术发展历史1.2 惯性器件1.2.1 机械陀螺(几乎没人用了)1.2.2 激光陀螺1.2.3 光纤陀螺1.2.4 MEMS陀螺(常用)1.2.5 加速度计2. 惯性器件误差分析2.1 信号误差组成2.2 Allan方差分析3. 惯性器件内参标定3.1 惯性…

十六、状态管理——Vuex(1)

本章概要 简单的状态管理安装 Vuex基本用法 Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。他采用集中式存储来管理应用程序中所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 也被集成到了 Vue 官方调试工具 vue-devtools 中…

跨年夜,想请你看一场烟花秀!

代码分享地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Cu_lKYfAlMBDttSzhVXPuQ 提取码&#xff1a;2ocd 代码效果展示&#xff1a; 源代码分享如下&#xff1a; <!--* Author: Xiao Wang* Date: 2022-12-30 14:26* Description: --> <!DOCTYPE html …

剑指 Offer 20. 表示数值的字符串

题目 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格 一个 小数 或者 整数 &#xff08;可选&#xff09;一个 ‘e’ 或 ‘E’ &#xff0c;后面跟着一个…

canopen11-sdo-2b写入命令

源码 1、SDO介绍 就对象而言,主机要访问节点词典的数据,因此主机是client客户端,节点是server服务器。上传与下载是对服务器来说的(这点和常识有点不太一样)。因此,上传指的是服务器发送数据给客户端,下载是客户端给服务器数据。 我们这里要用主机访问节点服务器2000位…

Go 语言从入门到实战

《Go 语言从入门到实战》 的学习笔记&#xff0c;欢迎阅读斧正。感觉该专栏整体来说对有些后端编程经验的来说比无后端编程经验的人更友好。 数据类型 运算符 算数运算符 比较运算符 用 比较数组 相同维数切含有相同个数元素的数组才可以比较&#xff0c;每个元素都相同的才…