JavaScript中国象棋程序(4) - 极大极小搜索算法
“JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序。这是教程的第4节。
上一节的程序,电脑是在随机走棋,这样太没劲了。这一节我们的程序中加入极大极小搜索算法,这样程序会稍微有点智商。不过棋力不高,也就是普通小学生的水平吧。
4.1、局面评估
局面评估,就是判断局面对红方(或黑方)的优势,并把优势量化。棋子价值可用以下不等式表达:
帅 > 车 > 马、炮 > 仕、相 > 兵
棋子价值可以简单量化为:
兵 |
仕 |
相 |
炮 |
马 |
车 |
帅 |
10 |
20 |
20 |
40 |
45 |
90 |
1000 |
但是棋子价值是跟位置有关系的,比如兵在过河前价值很小,过河后价值大涨。在我们的程序中,兵的位置价值数组如下:
[ // 兵
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 9, 9, 9, 11, 13, 11, 9, 9, 9, 0, 0, 0, 0,
0, 0, 0, 19, 24, 34, 42, 44, 42, 34, 24, 19, 0, 0, 0, 0,
0, 0, 0, 19, 24, 32, 37, 37, 37, 32, 24, 19, 0, 0, 0, 0,
0, 0, 0, 19, 23, 27, 29, 30, 29, 27, 23, 19, 0, 0, 0, 0,
0, 0, 0, 14, 18, 20, 27, 29, 27, 20, 18, 14, 0, 0, 0, 0,
0, 0, 0, 7, 0, 13, 0, 16, 0, 13, 0, 7, 0, 0, 0, 0,
0, 0, 0, 7, 0, 7, 0, 15, 0, 7, 0, 7, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
在初始位置,中兵价值15,其他四个位置价值都是7。位于九宫的中心位置时,价值达到最高的44。这个数组肯定不是凭空想象出来的,应该是象棋百科全书网的前辈,经过无数次的试验得到的。
帅的位置价值数组如下:
[ // 帅
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 11, 15, 11, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
帅是无价的。没有了帅,游戏是要结束的。数组里的1、2、11、15仅仅表示了帅的位置分值,并不是说帅只值这个数。由于兵、帅的位置没有重合,这两个数组可以合并。
如此一来,每种棋子就都会有一个与绝对位置相关的价值数组,因此我们的程序里有一个常量数组PIECE_VALUE[7][256]。该数组只提供了红方的位置分值。想获得黑方的位置分值,使用前面介绍过的函数SQUARE_FLIP(sq)翻转一下位置即可。此外,我们在Position对象中定义vlWhite和vlBlack两个属性,分别表示红方和黑方棋子价值。每次调用addPiece(sq, pc, bDel)增删棋子时,都会更新vlWhite和vlBlack。获取红方优势的局面评估函数:
Position.prototype.evaluate = function() {
var vl = this.vlWhite - this.vlBlack;
return vl;
}
4.2、简单的两层搜索
圆形节点为红方走棋的局面,方形节点为黑方走棋局面,红色数字为局面估值(也就是红方的优势)。深度优先遍历这个搜索树。
(1)、初始局面为A,该红方走棋。红方有B1、B2、B3三种走法。
(2)、假设红方选择第一种走法,走到了局面B1。
(3)、在局面B1,该黑方走棋。黑方有C1、C2、C3三种走法。
(4)、C1、C2、C3是叶子节点,调用局面评估函数,算得局面估值(也就是红方优势)分别是8、10、15。
(5)、回到局面B1,此时该黑方走棋。黑方后完后,红方优势越小,对黑方越有利。黑方自然会走到对自己最有利的C1局面。此时我们认为,B1局面的估值是8。
(6)、同样的方法,B2、B3的估值分别是5、10。
(7)、回到初始局面A,红方走棋。现在已经知道,选择第一种走法,最终估值为8;选择第二种走法,最终估值为5;选择第三种走法,最终估值为10。估值就是红方的优势,红方自然会选择对自己优势最大的走法,也就是走到局面B3。
(8)、可知行棋路线为A -> B3 -> C7。
A点选择估值最大的局面,称为极大点;B1、B2、B3选择估值最小的局面,称为极小点。
4.3、极大点搜索算法
在程序中,定义一个全局变量,表示搜索深度:
var MINMAXDEPTH = 3;
也就是说,程序会搜索3层。当然你也可以改为4,这样电脑的棋力会更强。当搜索深度超过4时,电脑会走得很慢很慢。根节点的层次是MINMAXDEPTH,每往下搜索一层,层次减1。当层次为0时,不再向下搜索,而是调用评估函数计算估值。
伪代码如下:
// 极大点搜索
Search.prototype.maxSearch = function(depth) {
如果depth等于0,调用评估函数并返回分值 var vlBest = 负无穷; // 初始最优值
var mvs = 当前局面全部走法; // 生成当前局面的所有走法
var value = 0;
for (var i = 0; i < mvs.length; i ++) {
执行招法mvs[i]
调用极小点搜索算法,深度设为depth-1,并将返回值赋给value
撤销招法mvs[i] if (value > vlBest) { // 寻找最大估值
vlBest = value;
if (depth == MINMAXDEPTH) { // 如果回到了根节点,需要记录根节点的最佳走法
记录根节点的最佳走法
}
}
}
return vlBest; // 返回当前节点的最优值
}
4.4、极小点搜索算法
极小点搜索这与极大点搜索很相似,但有3处不同:
(1)、初始最优值为正无穷。
(2)、递归调用的是极大点搜索算法。(而极大点搜索算法会递归调用极小点搜索算法)
(3)、寻找的是最小估值。
4.5、极大极小搜索算法
if (该红方走棋) {
调用极大点搜索算法,深度为MINMAXDEPTH
} else {
调用极小点搜索算法,深度为MINMAXDEPTH
}
4.6、核心代码说明
本节的代码可以在 Github 下载,也可以直接clone
git clone -b step-4 https://github.com/Royhoo/write-a-chinesechess-program
Board中新增或修改的主要属性和方法:
(1)、result
对局结果,有4种状态。
0表示结果未知(正在战斗中,这有在这一状态下,程序才响应用户的点击事件)
1表示你赢了(也就是电脑输了)
2表示和棋
3表示你输了(也就是电脑赢了)
Position中新增或修改的主要属性和方法:
(1)、vlWhite
红方所有棋子的价值
(2)、vlBlack
黑方所有棋子的价值
(3)、addPiece(sq, pc, bDel)
此方法增加了一项功能,就是在增减棋子时,更新vlWhite和vlBlack。
(4)、checked()
判断老将是否被对方攻击。具有攻击性的棋子是车、马、炮、兵。我们要判断对方的这四类棋子是否攻击到了己方老将,以及是否将帅对脸。算法如下:
1、假设帅(将)是车,判断它是否能吃到对方的车和将(帅)。如果能吃到对方的车,说明己方帅(将)被对方车攻击;如果能吃到将(帅),说明存在将帅对脸。
2、假设帅(将)是炮,判断它是否能吃到对方的炮。
3、假设帅(将)是马,判断它是否能吃到对方的马,需要注意的是,帅(将)的马腿用的数组是ADVISOR_DELTA,而不是KING_DELTA。
4、假设帅(将)是过河的兵(卒),判断它是否能吃到对方的卒(兵)。
(5)、makeMove()
改方法做了一些改进。如果移动棋子后,发现老将被对方攻击,也就是说这步棋是去送死的,那么就要撤销对棋子的移动,并返回false。
Search中新增或修改的主要属性和方法:
(1)、mvResult
这是搜索算法找到的最佳走法,随后电脑就会执行这步棋。
(2)、maxSearch()
极大点搜索算法
(3)、minSearch()
极小点搜索算法
(4)、maxMinSearch()
极大极小搜索算法
JavaScript中国象棋程序(4) - 极大极小搜索算法的更多相关文章
- JavaScript中国象棋程序(0) - 前言
“JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.希望通过这个系列,我们对博弈程序的算法有一定的了解.同时,我们也将构建出一个不错的中国象棋程序 ...
- JavaScript中国象棋程序(1) - 界面设计
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第1节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(2) - 校验棋子走法
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第2节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(3) - 电脑自动走棋
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第3节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(5) - Alpha-Beta搜索
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第5节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(6) - 克服水平线效应、检查重复局面
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第6节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(7) - 置换表
"JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第2节. 这一系列共有9个部分: 0.JavaScript中国象 ...
- JavaScript中国象棋程序(8) - 进一步优化
在这最后一节,我们的主要工作是使用开局库.对根节点的搜索分离出来.以及引入PVS(Principal Variation Search,)主要变例搜索. 8.1.开局库 这一节我们引入book.js文 ...
- 中国象棋程序的设计与实现(六)--N皇后问题的算法设计与实现(源码+注释+截图)
八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题. 该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列 ...
随机推荐
- 使用Cookie记住用户名和密码
Login.jsp <form name = "f1" method="get" action="servlet/LoginServlet&qu ...
- [转载] centos6.x x64 安装python2.7
本文转载自: http://www.centoscn.com/image-text/install/2016/0323/6906.html CentOS的设计理念中有一点是:持久可用.要达到这个目的, ...
- IOS开发-UI学习-沙盒机制&文件操作
苹果为软件的运行提供了一个沙盒机制 每个沙盒含有3个文件夹:Documents, Library 和 tmp.因为应用的沙盒机制,应用只能在几个目录下读写文件 Documents:苹果建议将程序中 ...
- UVa 11110 - Equidivisions
题目大意:给一个n*n的矩阵,其中放置n个数字,判断四连通的相同数字的个数是否等于n. Flood fill,本来没什么,用dfs判断一下就可以了,可是用scanf读取输入时TLE了,然后看到别人说要 ...
- python获取绑定的IP,并动态指定出口IP
在做采集器的过程中,经常会遇到IP限制的情况,这时候可以通过切换IP能继续访问. 如果是多IP的服务器,那么可以通过切换出口Ip来实现. 1.首先是如何获取服务器绑定的IP import netifa ...
- PHP使用JPG生成GIF动画图片,基于php_imagick_st-Q8.dll
PHP使用php_imagick_st-Q8.dll类库,把JPG图片连接生成GIF动画图片,需要事先下载好php_imagick_st-Q8.dll,文件,并配置php.ini文件,启用php_im ...
- 1.1.1.持久化存储协调器(Core Data 应用程序实践指南)
持久化存储协调器(persistent store coordinator)里面包含一份持久化存储区,而存储区里又含有数据表里的若干行数据. 与原子存储不同,SQLite数据库会在用户提交变更日志时进 ...
- Java Des加解密方法(c#加密Java解密)
最近我们用Java把一个用.net编写的老系统重新做了翻版,但是登录还是用.net的登录.这样就会遇到一个比较棘手的问题,我们登录用的cookie信息都是.net用des加密的,但我们不得不用Java ...
- 使用原始XML资源——定义原始XML资源
原始XML资源一般保存在/res/xml路径下——当使用ADT创建Android应用时,/res/目录下并没有包含该目录,开发者应该自行手动创建xml目录. 接下来Android应用对原始XML资源没 ...
- Delphi 获取内存及CPU信息的函数
Uses MemoryCpuUtils;//首先引用该单元 //声明下列变量用来存储读取的数值 Var iTotalPhysics, iTotalVirtual, iTotalPageFile, iC ...