-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
五子棋AI教程第二版七:Zobrist 置换表 #17
Labels
Comments
Open
Zobrist算法的维基链接…… |
@lance6716 已更正 |
在negamax.js中,var Cache = {},var c = Cache[board.zobrist.code],c.deep >= deep。有这样的代码,第一句我知道,是建立一个空对象。这个问题可能有点蠢,但博主能不能解释下第二句什么意思呀。没接触过JS,基本都看懂了,但这块一直看不懂。拜托🙏 |
@SuperSocks 没看懂你的问题,你是说 |
我不懂的是,Cache是一个空对象,Cache[board.zobrist.code] 取的是什么值,谢谢博主 |
抱歉抱歉我懂了博主,之前有些特性不了解 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Zobrist 算法
Zobrist 是一个快速Hash算法,非常适合用在各种棋类游戏中(事实上也是在各种棋类游戏中有大量应用)。
我们前面讲了负极大值搜索,其实很多时候会有重复的搜索,比如这种:
其实它和下面这种的走法只是顺序不同 ,最终走出来的局面是一样的:
对于大部分棋类来说,并不关心你是如何到达这个局面的,只要当前局面上的棋子一样,局势就是一样的。
那么如果我们搜索中碰到了上面两种情况,我们会对两种情况都进行一次打分,而其实有了第一次的打分,完全可以缓存起来,第二次就不用打分直接使用缓存数据了。除了这种情况,其实以前的搜索结果也可以存下来,可以用在启发式搜索中。
那么现在的问题就是,我们应该怎么表示一种局面呢?显然需要通过一种哈希算法,而且这个算法不能太慢,不然可能反而会降低搜索速度。而 Zobrist 就是一种满足我们需求的快速数组哈希算法。关于Zobrist算法请参考 https://en.wikipedia.org/wiki/Zobrist_hashing
Zobrist 效率非常高,每下一步棋,只需要进行一次
异或
操作,相对于对每一步棋的打分来说,这一次异或操作带来的性能消耗可以忽略不计。Zobrist具体实现如下:Zobrist[M][M]
的二维数组,其中M是五子棋的棋盘宽度。当然也可以是Zobrist[M*M]
的一维数组。设置两个是为了一个表示黑棋,一个表示白旗。对应的JS代码如下:
zobrist.js
源码在
zobrist.js
文件里。注意每次走棋都要进行一次zobrist操作。千万不要自行设计哈希函数,除非你能保证你的哈希函数比一次64位整数的异或操作更简单,并且同时证明冲突的概率很低。Zobrist数组中的随机数的
质量
很重要,因此我并没有采用JS内置的Math.random()
函数,而是使用了一个第三方的高质量随机函数库。有了这个快速hash算法,我们就可以通过一个64位的整数来表示一个棋局。那么我们应该存储哪些信息,以及合适取出来用呢?
集成 Zobrist
我们在
negamax
搜索的过程中,会把搜索结果存储在 置换表中,存储的key就是 zobrist的key。那么我们需要进行两部分操作:首先对于第一条,比较简单,只要在落子和删除的时候调用
zobrist
即可:这样每当我们的棋盘有变动的时候,
zobrist
模块会自动更新自己的 key然后我们需要在搜索的时候能存储和使用结果,基本思路是:
1, 每当搜索完成一个节点,我们就把结果存储在置换表中
2, 当开始搜索一个节点的时候,尝试从置换表取出结果,取到了就直接使用
但是有一点要注意,就是搜索深度的问题,如果我们搜索点 p,需要一个往下搜 4 层的结果,但是我们存储的是 2 层搜索的结果,那么显然不满足我们的要求,因为搜索深度不够,结果不够准确。因此我们需要考虑搜索深度:
1, 每当搜索完成一个节点,我们就把结果存储在置换表中,同时把搜索深度也存进去
2, 当开始搜索一个节点的时候,尝试从置换表取出结果,取到了就比较搜索深度,大于等于当前深度我们就是使用,否则就放弃。、
实现代码如下:
下一篇:五子棋AI设计教程第二版八:算杀
The text was updated successfully, but these errors were encountered: