“JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序。这是教程的第2节。

这一系列共有9个部分:

0、JavaScript中国象棋程序(0)- 前言

这一节主要介绍置换表。正如象棋百科全书网所说的,没有置换表,就称不上是完整的计算机博弈程序。

7.1、置换表

上图所示的搜索树中,局面A出现了3次,程序也搜索了3次,这浪费了很多时间。何不把A的搜索结果保存在表里,后面再搜索到A时直接查表取值,避免重复搜索呢?保存搜索结果的表,就是置换表。由于哈希表的读写速度很快,通常置换表就由哈希表来实现。

7.1.1、哈希表

哈希表是用散列方法存储的线性表。它以节点的关键字K为自变量,通过一个确定的函数关系h,计算出对应的函数值h(K),然后把这个值解释为节点的存储地址,将节点存入h(K)所指的存储位置上。在查找时,根据要查找的关键字用同一函数h计算出地址,再到相应的单元里查找要找的节点。函数h(K)称为散列函数或哈希函数。

如:11个元素的关键字分别为18,27,1,20,22,6,10,13,41,15,25。用11个连续存储空间来存放,选取关键字与元素位置间的函数为h(K) = key mod 11。(mod是取余运算)

7.1.2、存储

在我们的程序中,关键字K就是上节已经用到的Zobrist校验码。哈希函数同样是取余运算:

h(K) = Zobrist % TableSize

其中,TableSize是哈希表的长度。

但是这个函数在速度上有个瓶颈,因为“电脑一做除法就成了傻瓜”。因此,TableSize最好是2的整数次幂,这样就能把“取余运算”转化为“按位与运算”。比如取TableSize = 16,那么有:

7 mod 16 = 7,  17 mod 16 = 1,  25 mod 16 = 9

转换为按位与运算就有:

7 & 15 = 7,  17 & 15 = 1,  25 & 15 = 9

也就是说,和15进行与运算,就是对16取余。(可参考这篇文章

因此,h(K) = Zobrist & (TableSize - 1),其中TableSize是2的整数次幂。

每个哈希表项存储的内容包括:深度(depth)、节点类型(flag)、分值(vl)、最佳走法(mv)、Zobrist Lock校验码。后面会介绍这些字段的作用。

7.1.3、冲突处理

以TableSize = 16为例,

7 mod 16 = 7

23 mod 16 = 7

因此,通过哈希函数算得的位置可能存在冲突。我们处理冲突的方法很简单——深度优先的替换。如果新节点深度高于旧节点,直接用新节点替换旧结点;否则,保留旧节点不变。

7.1.4、查找

由于Zobrist校验码不足以保证局面的唯一性,我们对每个局面都生成一个Zobrist Lock校验码(生成方式与Zobrist校验码一样),并保存在哈希表表项中。

查找置换表时,首先根据Zobrist校验码计算出哈希表中的地址,然后再比较Zobrist Lock校验码是否一致,一致的话才能说明是同一局面。

7.2、节点类型

(a)alpha节点                               (b)beta节点                                    (c)PV节点

如上图所示,(10,20)是指搜索到C节点时,alpha为10,beta为20。

在图(a)中,所有子节点的值均小于alpha值10,最后C点取值为9,C点称为alpha点。

在图(b)中,节点C通过走法1到达D1取值为24,超过了beta值20,剪去了D2和D3两个分支,同时C点取值24。24并不是C点的真实值,我们只知道C点的值不比24小。这样的C点称为beta节点。

在图 (c)中,C点的取值为14,它是所有子节点中的最大值,反映了C点的真是情况,所以C点称为PV节点(Principal Variation)。

如果从哈希表中是一个PV节点,那么当前所搜索节点的值,就是哈希表中存储的值。

如果哈希表中是个beta节点,值为value。由于beta节点会发生剪枝,value不是一个准确值。只能说明当前搜索节点的值,不小于value。

如果哈希表中是个alpha节点,值为value。对于不超出边界的Alpha-Beta搜索,value显然不是个准确值,只能说明当前搜索节点的值不大于value。对于超出边界的Alpha-Beta搜索,例如上面的图(a),我个人觉得,9就是节点C的真实情况吧。让我不解的是,在程序中,使用的正是超出边界的搜索,却没有把alpha节点的值作为准确值来处理。

7.3、杀棋分数调整

在第5节,我们已经考虑了杀棋的分值,把输棋的分值与搜索层数结合起来:

输棋分值 = -MATE_VALUE + 搜索层数

赢棋分值 = MATE_VALUE - 搜索层数

现在使用置换表之后,由于相同的局面,可能位于不同的层数,所以不能再把这个调整后的分值存入置换表。我们的做法是,存入置换表时,存储与层数无关的分值(比如-MATE_VALUE);读取置换表时,再调整为与层数相关的分值(-MATE_VALUE + 当前搜索层数)。

7.4、杀手走法

第5节我们介绍了历史表,将一些好的走法(beta节点引发剪枝的走法、PV节点估值最好的走法)保存到历史表。根据国家象棋的经验,一个节点好的走法,在它兄弟节点也很可能就是好的走法。但是兄弟节点的走法,在当前节点下未必能走,所以在尝试杀手走法以前先要对它进行走法合理性的判断。

如何保存和获取“兄弟节点中产生截断的走法”呢?我们可以把这个问题简单化——距离根节点步数(distance)同样多的节点,彼此都称为“兄弟”节点,换句话说,亲兄弟、堂表兄弟以及关系更疏远的兄弟都称为“兄弟”。

我们可以把距离根节点的步数(distance)作为索引值,构造一个杀手走法表。我们的程序每个杀手走法表项存有两个杀手走法,走法一比走法二优先:存一个走法时,走法二被走法一替换,走法一被新走法替换;取走法时,先取走法一,后取走法二。

7.5、优化走法顺序

利用各种信息渠道(如置换表、杀手走法、历史表等)来优化走法顺序的手段称为“启发”。之前我们只用历史表作启发,但从这个版本开始,我们采用了多种启发方式:

1、如果置换表中有过该局面的数据,但无法完全利用,那么多数情况下它是浅一层搜索中产生截断的走法,我们可以首先尝试它;

2、然后是两个杀手走法(如果其中某个杀手走法与置换表走法一样,那么可以跳过);

3、然后生成全部走法,按历史表排序,再依次搜索(可以排除置换表走法和两个杀手走法)。

7.6、核心代码说明

本节的代码可以在 Github 下载,也可以直接clone

git clone -b step-7 https://github.com/Royhoo/write-a-chinesechess-program

Search中新增或修改的主要属性和方法:

(1)、hashMask

hashMask = 哈希表长度 - 1

用于将哈希函数的“取余运算”,转化为“按位与运算”。

(2)、recordHash(flag, vl, depth, mv)

记录哈希表

(3)、probeHash(vlAlpha, vlBeta, depth, mv)

查询哈希表

(4)、setBestMove(mv, depth)

该函数之前只更新历史表,目前要同时更新杀手走法表。

MoveSort中修改的方法:

(1)、next()

该方法是获取排序后的一个走法,目前加入置换表走法、杀手走法、历史表走法三种启发。

JavaScript中国象棋程序(7) - 置换表的更多相关文章

  1. JavaScript中国象棋程序(0) - 前言

    “JavaScript中国象棋程序” 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.希望通过这个系列,我们对博弈程序的算法有一定的了解.同时,我们也将构建出一个不错的中国象棋程序 ...

  2. JavaScript中国象棋程序(1) - 界面设计

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第1节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  3. JavaScript中国象棋程序(2) - 校验棋子走法

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第2节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  4. JavaScript中国象棋程序(3) - 电脑自动走棋

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第3节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  5. JavaScript中国象棋程序(4) - 极大极小搜索算法

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第4节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  6. JavaScript中国象棋程序(5) - Alpha-Beta搜索

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第5节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  7. JavaScript中国象棋程序(6) - 克服水平线效应、检查重复局面

    "JavaScript中国象棋程序" 这一系列教程将带你从头使用JavaScript编写一个中国象棋程序.这是教程的第6节. 这一系列共有9个部分: 0.JavaScript中国象 ...

  8. JavaScript中国象棋程序(8) - 进一步优化

    在这最后一节,我们的主要工作是使用开局库.对根节点的搜索分离出来.以及引入PVS(Principal Variation Search,)主要变例搜索. 8.1.开局库 这一节我们引入book.js文 ...

  9. 中国象棋程序的设计与实现(六)--N皇后问题的算法设计与实现(源码+注释+截图)

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题. 该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列 ...

随机推荐

  1. 网络请求工具--AFNetworking 分类: ios技术 2015-02-03 08:17 76人阅读 评论(0) 收藏

    在我们开发过程中,网络请求是必不可少的,对于网络框架,现在主流的大概只有三类:ASI框架: HTTP终结者(已经停止更新了),MKNetworkKit ,AFN.今天我就来浅谈一下这个AFN AFNe ...

  2. python 错误AttributeError: 'module' object has no attribute 'AF_INET'

    写了一个简单的python socket的程序.运行时,报错如下 原因:文件的命名与Python的function的命名冲突 修改名称后,发现还是无法运行,检查目录下面是否有 这样子的一个文件,删除即 ...

  3. JQuery基础知识(2)

    JQuery基础知识(2) JQuery滑动效果 1. JQuery slideDown(); 语法: $(selector).slideDown(speed,callback); 可选的 speed ...

  4. C#、C++用GDAL读shp文件(转载)

    C#.C++用GDAL读shp文件 C#用GDAL读shp文件 (2012-08-14 17:09:45) 标签: 杂谈 分类: c#方面的总结 1.目前使用开发环境为VS2008+GDAL1.81 ...

  5. Bootstrap每天必学之导航条

    http://www.jb51.net/article/75534.htm Bootstrap每天必学之导航条,本文向大家讲解了多种多样的导航条,以及导航条中元素的实现方法,感兴趣的小伙伴们可以参考一 ...

  6. Bootstrap入门(十)组件4:按钮组与下拉菜单结合

    Bootstrap入门(十)组件4:按钮组与下拉菜单结合   先引入本地的CSS文件和JS文件(注:1.bootstrap是需要jQuery支持的.2.需要在<body>当中添加) < ...

  7. js制作ppt阅读文档类型

    前几天开发中,客户要求在页面中展示一个类似ppt文档的一个东西,用轮播展示,但是有缩略图,和展示的大图,本想找些插件来做,后来想想,自己试下吧.. 有些简陋,但是能用,毕竟一行一行写的,留下来当笔记用 ...

  8. 2017年学习的三个CSS新特性

    这是翻译的一篇文章,原文是:3 New CSS Features to Learn in 2017,翻译的不是很好,如有疑问欢迎指出. 新的一年,我们有一系列新的东西要学习.尽管CSS有很多新的特性, ...

  9. 【原创】梵高油画用深度卷积神经网络迭代十万次是什么效果? A neural style of convolutional neural networks

    作为一个脱离了低级趣味的码农,春节假期闲来无事,决定做一些有意思的事情打发时间,碰巧看到这篇论文: A neural style of convolutional neural networks,译作 ...

  10. Fourier分析基础(一)——Fourier级数

    前言 傅立叶分析的作用是把一个函数变成一堆三角函数的和的形式,也就是分解.首先引入的是傅立叶级数,Fourier级数的作用是把函数变为可数无限个三角函数的和,而且这些三角函数的频率都是某个基频的整数倍 ...