最近我用Python做了一个国际象棋程序并把代码发布在Github上了。这个代码不到1000行,大概20%用来实现AI。在这篇文章中我会介绍这个AI如何工作,每一个部分做什么,它为什么能那样工作起来。你可以直接通读本文,或者去下载代码,边读边看代码。虽然去看看其他文件中有什么AI依赖的类也可能有帮助,但是AI部分全都在AI.py文件中。

AI 部分总述

AI在做出决策前经过三个不同的步骤。首先,他找到所有规则允许的棋步(通常在开局时会有20-30种,随后会降低到几种)。其次,它生成一个棋步树用来随后决定最佳决策。虽然树的大小随深度指数增长,但是树的深度可以是任意的。假设每次决策有平均20个可选的棋步,那深度为1对应20棋步,深度为2对应400棋步,深度为3对应8000棋步。最后,它遍历这个树,采取x步后结果最佳的那个棋步,x是我们选择的树的深度。后面的文章为了简单起见,我会假设树深为2。

生成棋步树

棋步树是这个AI的核心。构成这个树的类是MoveNode.py文件中的MoveNode。他的初始化方法如下:

def __init__(self, move, children, parent) :

self.move = move

self.children = children

self.parent = parent

pointAdvantage = None

depth = 1

这个类有五个属性。首先是move,即它包含的棋步,它是个Move类,在这不是很重要,只需要知道它是一个告诉一个起子往哪走的棋步,可以吃什么子,等等。然后是children,它也是个MoveNode类。第三个属性是parent,所以通过它可以知道上一层有哪些MoveNode。pointAdvantage属性是AI用来决定这一棋步是好是坏用的。depth属性指明这一结点在第几层,也就是说该节点上面有多少节点。生成棋步树的代码如下:

def generateMoveTree(self) :

moveTree = []

for move in self.board.getAllMovesLegal(self.side) :

moveTree.append(MoveNode(move, [], None))

for node in moveTree :

self.board.makeMove(node.move)

self.populateNodeChildren(node)

self.board.undoLastMove()

return moveTree

变量moveTree一开始是个空list,随后它装入MoveNode类的实例。第一个循环后,它只是一个拥有没有父结点、子结点的MoveNode的数组,也就是一些根节点。第二个循环遍历moveTree,用populateNodeChildren函数给每个节点添加子节点:

def populateNodeChildren(self, node) :

node.pointAdvantage = self.board.getPointAdvantageOfSide(self.side)

node.depth = node.getDepth()

if node.depth == self.depth :

return

side = self.board.currentSide

legalMoves = self.board.getAllMovesLegal(side)

if not legalMoves :

if self.board.isCheckmate() :

node.move.checkmate = True

return

elif self.board.isStalemate() :

node.move.stalemate = True

node.pointAdvantage = 0

return

for move in legalMoves :

node.children.append(MoveNode(move, [], node))

self.board.makeMove(move)

self.populateNodeChildren(node.children[-1])

self.board.undoLastMove()

这个函数是递归的,并且它有点难用图像表达出来。一开始给它传递了个MoveNode对象。这个MoveNode对象会有为1的深度,因为它没有父节点。我们还是假设这个AI被设定为深度为2。因此率先传给这个函数的结点会跳过第一个if语句。

然后,决定出所有规则允许的棋步。不过这在这篇文章讨论的范围之外,如果你想看的话代码都在Github上。下一个if语句检查是否有符合规则的棋步。如果一个都没有,要么被将死了,要么和棋了。如果是被将死了,由于没有其他可以走的棋步,把node.move.checkmate属性设为True并return。和棋也是相似的,不过由于哪一方都没有优势,我们把node.pointAdvantage设为0。

如果不是将死或者和棋,那么legalMoves变量中的所有棋步都被加入当前结点的子节点中作为MoveNode,然后函数被调用来给这些子节点添加他们自己的MoveNode。

当结点的深度等于self.depth(这个例子中是2)时,什么也不做,当前节点的子节点保留为空数组。

遍历树

假设/我们有了一个MoveNode的树,我们需要遍历他,找到最佳棋步。这个逻辑有些微妙,需要花一点时间想明白它(在明白这是个很好的算法之前,我应该更多地去用Google)。所以我会尽可能充分解释它。比方说这是我们的棋步树:

如果这个AI很笨,只有深度1,他会选择拿“象”吃“车”,导致它得到5分并且总优势为+7。然后下一步“兵”会吃掉它的“后”,现在优势从+7变为-2,因为它没有提前想到下一步。

在假设它的深度为2。将会看到它用“后”吃“马”导致分数-4,移动“后”导致分数+1,“象”吃“车”导致分数-2。因此,他选择移动后。这是设计AI时的通用技巧,你可以在这找到更多资料(极小化极大算法)。

所以我们轮到AI时让它选择最佳棋步,并且假设AI的对手会选择对AI来说最不利的棋步。下面展示这一点是如何实现的:

def getOptimalPointAdvantageForNode(self, node) :

if node.children:

for child in node.children :

child.pointAdvantage = self.getOptimalPointAdvantageForNode(child)

#If the depth is divisible by 2, it's a move for the AI's side, so return max

if node.children[0].depth % 2 == 1 :

return(max(node.children).pointAdvantage)

else :

return(min(node.children).pointAdvantage)

else :

return node.pointAdvantage

这也是个递归函数,所以一眼很难看出它在干什么。有两种情况:当前结点有子节点或者没有子节点。假设棋步树正好是前面图中的样子(实际中每个树枝上会有更多结点)。

第一种情况中,当前节点有子节点。拿第一步举例,Q吃掉N。它子节点的深度为2,所以2除2取余不是1。这意味着子节点包含对手的一步棋,所以返回最小步数(假设对手会走出对AI最不利的棋步)。

该节点的子节点不会有他们自己的节点,因为我们假设深度为2。因此,他们但会他们真实的分值(-4和+5)。他们中最小的是-4,所以第一步,Q吃N,被给为分值-4。

其他两步也重复这个步骤,移动“后”的分数给为+1,“象”吃“车”的分数给为-2。

选择最佳棋步

最难的部分已经完成了,现在这个AI要做的事就是从最高分值的棋步中做选择。

def bestMovesWithMoveTree(self, moveTree) :

bestMoveNodes = []

for moveNode in moveTree :

moveNode.pointAdvantage = self.getOptimalPointAdvantageForNode(moveNode)

if not bestMoveNodes :

bestMoveNodes.append(moveNode)

elif moveNode > bestMoveNodes[0] :

bestMoveNodes = []

bestMoveNodes.append(moveNode)

elif moveNode == bestMoveNodes[0] :

bestMoveNodes.append(moveNode)

return [node.move for node in bestMoveNodes]

此时有三种情况。如果变量bestMoveNodes为空,那么moveNode的值是多少,都添加到这个list中。如果moveNode的值高于bestMoveNodes的第一个元素,清空这个list然后添加该moveNode。如果moveNode的值是一样的,那么添加到list中。

最后一步是从最佳棋步中随机选择一个(AI能被预测是很糟糕的)

bestMoves = self.bestMovesWithMoveTree(moveTree)

randomBestMove = random.choice(bestMoves)

这就是所有的内容。AI生成一个树,用子节点填充到任意深度,遍历这个树找到每个棋步的分值,然后随机选择最好的。这有各种可以优化的地方,剪枝,剃刀,静止搜索等等,但是希望这篇文章很好地解释了基础的暴力算法的象棋AI是如何工作的。

本文由 伯乐在线 - 许世豪 翻译自 mbuffett。

python国际象棋ai程序_用Python编写一个国际象棋AI程序相关推荐

  1. java 加法程序_使用JAVAEE编写简单的加法程序

    软件152  罗俊 首先选择菜单file-new-maven project,勾选"Create a &simple project (skip archetype selectio ...

  2. 用python模拟评委打分_用vb 编写一个评委打分的程序1. 编写一个评委打分的程序,实现以下功能:a) 单击“评委给分”按钮时弹出InputBo...

    共回答了20个问题采纳率:80% Dim a(10) As Integer Private Sub Command1_Click() For i = 1 To 10 a(i) = InputBox(& ...

  3. 送女朋友的java小程序_用C编写一个送给女朋友的情人节小程序 可爱!

    本文实例为大家分享了C编写送给女朋友的小程序,供大家参考,具体内容如下 #include #include #include #include #include using namespace std ...

  4. java体重指数计算器程序_用Java编写一个简单的计算器程序

    展开全部 import java.awt.*; import java.awt.event.*; public class CalcAppDemo extends Frame{ private Tex ...

  5. 用Java 编写菜单价格和的程序_使用JAVA 编写一个程序,显示5中商品价格,用户可以选择多种商品并在其后的文本框输入购买的数量。...

    展开全部 用java Swing做的?给你个代码,还有运行结果图 package my.test.main; import java.awt.BorderLayout; import java.awt ...

  6. java员工编号程序_用JAVA编写一个employee类 为员工自动产生员工号

    差不多就是这个感觉.. class Employee { /** * @param args */ private int number; private String name; private S ...

  7. c语言创建空顺序表的程序,用C语言编写一个完整的程序,实现顺序表的建立、插入、删除、输出等基本运算。...

    #include #include #define maxsize 30 typedef int datatype; typedef struct seqlist{ datatype data[max ...

  8. python代码变成运行程序_用Python脚本转换成windows的可执行程序

    下载并运行与你所安装的Python对应的py2exe版本的 installer,这将安装py2exe和相应的例子:这些例子被安装在lib\site-packages\py2exe\samples目录下 ...

  9. python温度转换代码_用python编写一个名为“convert_temp”的温度转换函数

    编写一个名为"convert_temp"的温度转换函数.它应该能够处理华氏到摄氏的转换以及摄氏到华氏的转换. 它必须接受并读取传递给它的两个参数:第一,原始温度的温标(只应使用&q ...

  10. python登录代码思路_用python登录Dr.com思路以及代码分享

    用python登录Dr.com思路以及代码分享 发布于 2014-08-28 22:31:52 | 192 次阅读 | 评论: 0 | 来源: 网友投递 Python编程语言Python 是一种面向对 ...

最新文章

  1. 拜托,别再问我贪心算法了!
  2. R语言构建文本分类模型:文本数据预处理、构建词袋模型(bag of words)、构建xgboost文本分类模型、xgboost模型预测推理并使用混淆矩阵评估模型、可视化模型预测的概率分布
  3. win10 检测不到显卡
  4. linux 网络7层模型,Linux网络编程——OSI七层模型、TCP/IP模型
  5. C++ getline() 和 get()
  6. 专科学会计还是计算机应用技术好,专科毕业想要学习会计专业,我专科是学计算机应用,但是毕业后家里让我学会计这门专业,具体是该怎么办呢...
  7. 谷歌浏览器调用本地exe_无需修改前端和后端代码本地跨域开发设置
  8. 东风畅行java_东风畅行载货车为何可以口碑很好吗?是配置高?或者另有原因?...
  9. 计算机网络—PPP协议和HDLC协议
  10. 用线程安全随机数解决Random在多线程中随机性重复的问题
  11. nvm npm exit status 1:乱码
  12. 2012成都之行----幺祖祖
  13. 计算机考证上传照片说明
  14. cdr文件太大怎么转成小内存 CDR文件太大打不开怎么办
  15. IOS学习路线(2014-05-08)
  16. Redis string和hash数据类型
  17. 你到底要一台什么样的笔记本
  18. WebAssembly运行时库(WASM runtime:wasmer 或 wasmtime)\将rust官方demo猜数字编译为WASI目标并使用Wasmer运行
  19. error: insufficient permission for adding an object to repository database .git/objects
  20. [pwn][堆利用]house of spirit[例题:lctf2016_pwn200]

热门文章

  1. Spring 3.1缓存抽象教程
  2. java plus方法_Java.math.BigDecimal.plus()方法实例
  3. axure元件库 文件上传_手把手教你打造一套属于产品经理自己的元件库
  4. python创建虚拟环境命令_Python创建虚拟环境报错
  5. workbench拓扑优化教程_workbenchds拓扑优化分析.ppt
  6. pytorch load state dict_PyTorch 学习笔记(五):Finetune和各层定制学习率
  7. _用WSL,MobaXterm,Cmder配置linux开发环境
  8. matlab中欠定方程组超定方程组_《数值天气预报》:球坐标系中的基本方程组
  9. 16.04编译android 7.0,ubuntu16.04 编译Android5.1报错
  10. 微信小程序 全局变量异步函数_微信小程序【生命周期】