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

最近我用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。

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

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

相关文章

java switch case怎么判断范围_【转】Java期末复习攻略!

期末19年就这样要过去了,终于到了小时候作文里的未来呢!然而,期末考试也随之来临了。不知大家“预习”的怎么样呢? 期末复习资料的放送快接近尾声了下面康康学长学姐们怎么教你们打java这个boss(下面是java大佬给大家的复习建议以…

java list 去重复元素_java List去掉重复元素的几种方式

使用LinkedHashSet删除arraylist中的重复数据(有序)List words Arrays.asList("a","b","b","c","c","d");HashSet setnew LinkedHashSet<>(words);for(String word:set){System.out.println(word);}使用Has…

spring aop示例_Spring JpaRepository示例(内存中)

spring aop示例这篇文章描述了一个使用内存中HSQL数据库的简单Spring JpaRepository示例。 该代码示例可从GitHub的Spring-JpaRepository目录中获得。 它基于带有注释的Spring-MVC-示例和此处提供的信息 。 JPA资料库 在此示例中&#xff0c;我们实现了一个虚拟bean&#xff1…

python人工智能入门优达视频_机器学习:优达教你搭建Python 环境的正确姿势

原标题&#xff1a;机器学习:优达教你搭建Python 环境的正确姿势为机器学习搭建好 Python 环境听起来简单&#xff0c;但有时候坑还不少。如果此前没有配置过类似的环境&#xff0c;很可能会苦苦折腾各种命令好几个小时。可是我明明只是想马上搞起来我的机器学习&#xff01; 在…

java web登录状态保持_java web用于保持状态的4种方法

方法一&#xff1a;网址重写通过在url地址后面添加若干的token作为查询字符串来实现。token的值一般为 键值url?key1value1&key2value2&...&keynvaluenurl与token之间需要用?分开&#xff0c;两个token之间则是需要用一个&符号隔开。此方法适用于token不需要…

python天天向上续2_2019/2/12 Python今日收获

Python day12——025&#xff0c;026字典&#xff1a;当索引不好用时 1.字典&#xff1a;python唯一的一个映射类型。用键值对存储数据&#xff0c;他的标志是大括号。一个键值组合叫一个项。键的类型既可以是字符串类型也可以是整形也可以是浮点型。 如&#xff1a;dict{1:one…

python生成矩阵_如何在Python中生成矩阵?

你的问题的答案取决于你的学习目标是什么。如果您试图让矩阵“点击”以便以后使用它们&#xff0c;我建议您查看一个Numpyarray&#xff0c;而不是一个列表列表。这将使您可以轻松地分割行、列和子集。只要试着从列表中获取一个列&#xff0c;你就会感到沮丧。 使用列表列表作为…

java ee cdi_Java EE CDI ConversationScoped示例

java ee cdi在本教程中&#xff0c;我们将向您展示如何在Web应用程序中创建和使用ConversationScoped Bean。 在CDI中&#xff0c;bean是定义应用程序状态和/或逻辑的上下文对象的源。 如果容器可以根据CDI规范中定义的生命周期上下文模型来管理其实例的生命周期&#xff0c;则…

js input 自动换行_深入Slate.js - 拯救 ContentEditble

我们是钉钉的文档协同团队&#xff0c;我们在做一些很有意义的事情&#xff0c;其中之一就是自研的文字编辑器。为了把自研文字编辑器做好&#xff0c;我们调研了开源社区各种优秀编辑器&#xff0c;Slate.js 是其中之一&#xff08;实际上&#xff0c;自研文字编辑器前&#x…

java main 如何不退出_为什么java main主线程退出了子线程还能运行;golang main结束所有协程都被结束了...

最近看golang main函数结束&#xff0c;所有协程都被结束了结论是这样&#xff1a;A不是main程的情况下&#xff0c;在A程里开启B程&#xff0c;A程执行完&#xff0c;A程return之后&#xff0c;B程不受影响&#xff0c;不会挂掉。所有子协程与main程同级的&#xff0c;与main程…

安全点

安全点 Java应用程序中有两个逻辑线程组&#xff1a; 应用程序线程执行应用程序逻辑 执行GC的线程 在执行诸如堆压缩之类的操作时&#xff0c;GC线程会四处移动一些对象&#xff0c;并且这些对象不能被任何应用程序线程使用&#xff0c;因为它们的物理位置可能会发生变化。 …

printf 地址_C程序显示主机名和IP地址

查找本地计算机的主机名和IP地址的方法有很多。这是使用C程序查找主机名和IP地址的简单方法。我们将使用以下功能&#xff1a;gethostname() &#xff1a;gethostname函数检索本地计算机的标准主机名。gethostbyname() &#xff1a;gethostbyname函数从主机数据库中检索与主机名…

java 定义变量时 赋值与不赋值_探究Java中基本类型和部分包装类在声明变量时不赋值的情况下java给他们的默认赋值...

探究Java中基本类型和部分包装类在声明变量时不赋值的情况下java给他们的默认赋值当基本数据类型作为普通变量(八大基本类型&#xff1a; byte,char,boolean,short,int,long,float,double)只有开发人员对其进行初始化&#xff0c;java不会对其进行初始化&#xff0c;如果不初始…

python开发的系统有哪些_Python web开发=几个模板系统的性能对比

Python web 开发&#xff1a;几个模板系统的性能对比 对比目标&#xff0c; jinja2 &#xff0c; cheetah &#xff0c; mako &#xff0c; webpy &#xff0c; bottle &#xff0c; tornado &#xff0c; django 的性能。 方法&#xff0c; 随机生成一个二维数组&#xff0c; …

java 字符串 移位_使用位运算、值交换等方式反转java字符串-共四种方法

在本文中&#xff0c;我们将向您展示几种在Java中将String类型的字符串字母倒序的几种方法。StringBuilder(str).reverse()char[]循环与值交换byte循环与值交换apache-commons-lang3如果是为了进行开发&#xff0c;请选择StringBuilder(str).reverse()API。出于学习的目的&…

xstream xml模板_XStream – XStreamely使用Java中的XML数据的简便方法

xstream xml模板有时候&#xff0c;我们不得不处理XML数据。 而且大多数时候&#xff0c;这不是我们一生中最快乐的一天。 甚至有一个术语“ XML地狱”描述了程序员必须处理许多难以理解的XML配置文件时的情况。 但是&#xff0c;不管喜欢与否&#xff0c;有时我们别无选择&…

python知识点智能问答_基于知识图谱的智能问答机器人

研究背景及意义 智能问答是计算机与人类以自然语言的形式进行交流的一种方式&#xff0c;是人工智能研究的一个分支。 知识图谱本质上是一种语义网络&#xff0c;其结点代表实体&#xff08;entity&#xff09;或者概念&#xff08;concept&#xff09;&#xff0c;边代表实体/…

java会了还学什么_java都学哪些内容?学完之后可以做哪些工作?

展开全部阶段一&#xff1a;揭开企业开发神秘面纱 (4周32313133353236313431303231363533e78988e69d8331333431336163)1) Web开发基础&#xff1a;HTML语言、JavaScript、CSS、DOM等2) Oracle数据库基础&#xff1a;安装、配置Oracle数据库&#xff0c;熟练掌握SQL语句3) 操作系…

Java中的RAII

资源获取即初始化&#xff08; RAII &#xff09;是Bjarne Stroustrup用C 引入的一种用于异常安全资源管理的设计思想。 感谢垃圾回收&#xff0c;Java 没有此功能&#xff0c;但是我们可以使用try-with-resources实现类似的功能。 约翰哈德斯&#xff08;John Huddles&#x…

java去掉字符串中前后空格函数_Java去除字符串中的空格

1. String.trim()trim()是去掉首尾空格2.str.replace(" ", ""); 去掉所有空格&#xff0c;包括首尾、中间String str " hell o ";String str2 str.replaceAll(" ", "");System.out.println(str2);3.或者replaceAll("…