Python开发AI应用-国际象棋应用
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是如何工作的。
注意:部门内容参考于互联网
Python开发AI应用-国际象棋应用的更多相关文章
- vim配置python开发环境
vim配置python开发环境 一.安装vim sudo apt-get install vim 二.vim基础配置 #Centos6.5 /usr/share/vim/vim72 vi /etc/v ...
- python 开发之路 - 入门
一. python 介绍 Python是著名的"龟叔"Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言.1991年 发布Python ...
- python 开发环境配置
上篇文章配置了虚机基础环境,本篇文章介绍配置python开发环境 配置YUM源 使用国内yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos ...
- Python开发GUI工具介绍,实战:将图片转化为素描画!【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- 使用Python创建AI比你想象的轻松
使用 Python 创建 AI 比你想象的轻松 可能对AI领域,主要开发阶段,成就,结果和产品使用感兴趣.有数百个免费源和教程描述使用Python的AI.但是,没有必要浪费你的时间看他们.这里是一个详 ...
- 2020年一线大厂月薪35K的Python开发要求
为什么程序员要在2020年学习Python? 如果你正在考虑学习 Python,但又不确定为什么要这样做的话,那么你可以看看以下的内容: 马蜂窝裁员竟然达到了40%, 前段时间猪厂.菊厂裁员被推到了风 ...
- 使用Python开发鸿蒙设备程序(0-初体验)
到目前为止,鸿蒙设备开发的"官方指定语言"还是C语言! 这看起来是一件正常的事,毕竟鸿蒙设备开发还是属于嵌入式开发的范畴,而在嵌入式开发中C语言又是当之无愧的首选,所以,大家也都接 ...
- 用鸿蒙开发AI应用(八)JS框架访问内核层
目录:前言JS应用开发框架原理内置模块实现ace模块开发界面程序 前言上回说到,用C++来写UI界面的开发效率不如JS+HTML来的高,但设备开发又免不了要通过内核态来操作硬件,这里我们就要先打通从J ...
- python开发环境搭建
虽然网上有很多python开发环境搭建的文章,不过重复造轮子还是要的,记录一下过程,方便自己以后配置,也方便正在学习中的同事配置他们的环境. 1.准备好安装包 1)上python官网下载python运 ...
随机推荐
- 百度AI身份证识别demo,使用js提交图片数据
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...
- hbase 安装(集群模式)
环境:jdk 1.8 + hadoop2.7.6+zookeeper3.4.9+centos7 一.安装zookeeper(集群模式) 0.安装机器 ip ...
- 【layui】获取layui弹窗的index并关闭
var index = parent.layer.getFrameIndex(window.name); //获取窗口索引 parent.layer.close(index); // 关闭当前laye ...
- Git 删除所有历史提交记录方法
Git 删除所有历史提交记录方法 切换分支 git checkout --orphan latest_branch 添加所有文件 git add -A 提交更改 git commit -am &quo ...
- Spark之RDD依赖关系及DAG逻辑视图
RDD依赖关系为成两种:窄依赖(Narrow Dependency).宽依赖(Shuffle Dependency).窄依赖表示每个父RDD中的Partition最多被子RDD的一个Partition ...
- Django中ORM过滤时objects.filter()无法对月份过滤
django中的filter日期查询属性有:year.month.day.week_day.hour.minute.second 在做复习博客项目时,我把项目从linux移到了windows,然后博客 ...
- c# 基于WebApi的快速开发框架FastFramework
一.框架简介 此框架是针对于webapi进行开发,项目分层是基于ABP框架的分层,更好的抽离业务逻辑关系,ABP是基于EF做数据访问层,本人个人比较喜欢Dapper,就把数据访问层封装成了Dapper ...
- ServiceStack JWT 准备
ServiceStack JWT设置 ServcieStack 自带的验证授权模块使用 sql server存储,所以我们第一步需要配置数据库的一些选项 container.Register<I ...
- Mycat使用--分库分表和读写分离
Mycat分库分表读写分离 1. 模拟多数据库节点 2. 配置文件 具体操作参看: https://blog.csdn.net/vbirdbest/article/details/83448757 写 ...
- ThreadLocal简解
ThreadLocal特点 ThreadLocal实现了线程间数据隔离,ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量.简单来说 ...