五子棋是一个老少咸宜,复杂度相对较低游戏,本篇教程便是使用传统的博弈树的知识和一些棋类AI的经典算法来实现一个有一定算力AI。AI可以使用QT编写,在6核的家用笔记本在分支因子(搜索窗口的宽度)为10的情况下,6层深度可以在1s内搜索完成,8层深度可以在4s内完成。这里优先给出一个其中使用到的关键技术,
搜索的速度取决于基础数据结构和搜索算法,本文在尝试了多种基础的数据结构,最终选择了数组+棋盘位表示方式。尝试了方式中包括vector+无位棋盘,vector+位棋盘以及现在的数组+位棋盘的形式。对于这三种方式,每一个改变对于搜索速度来说都是质的提升[有10倍左右]。
搜索算法需要对搜索状态进行唯一标识。zobrist Hash不仅可以使用最小的代价(一次uint64数的位运算)得到下一次搜索状态的散列值,还可以有效的减少hash冲突。zobrist散列技术广泛应用于各种棋类算法,不限于象棋,五子棋,黑白棋。
极大极小搜索是博弈树搜索最基础的算法,本文会简单介绍。这里使用是负极大搜索算法,可以避免在最大层/最小层决策出现不一致的情况。
静态棋盘评估算法同样是AI算力的核心,但是本文疏于这部分的讨论,常见的有五元组评估和匹配算法。本文使用的匹配算法,通过扫描棋盘匹配到每个棋子的基础棋型(例如眠三,活四等),利用预先给定基础棋型分数表计算整个棋盘的得分。这种方案缺陷也是显而易见的,无法对子势精准衡量。相同棋盘得分,白子可能抱团,很好组织起攻势,黑子可能只有较散的眠三。
极大极小算法搜索效率跟节点排序直接相关。节点排序越优,越容易产生剪枝,搜索效率也就越高。
这里就是最简单对于当前节点棋盘分数/分数的增长量进行排序。得分越高,出现越靠前。
置换表保存了搜索过程中最优价值的节点信息,如果发现当前搜索状态在置换表,那么这一状态应该率先被搜索。
杀手表保存了搜索过程中某一指定搜索深度下各个节点的剪枝信息。搜索过程中,在因为某节点产生的剪枝次数越多,这个值也就越大。
类似于杀手表,但是历史表忽略了搜索深度信息。只要在搜索过程中因为某一节点产生了剪枝,这个节点的价值就会变大,
在棋局的中期有这大量的构成活三或者冲四点,这一块就是使用简化的极小极大搜索实现算杀模块。在搜索过程中博弈双方只考虑活三或者冲四/活四进攻,如果一方防守失败,即代表搜索到了一条VCX(VCT/VCF)路径。
并行化搜索极大加快搜索[2倍左右,取决于本地性能]。这一部分只需要通过线程间的通讯,告诉搜索分支何时剪枝,何时退出。
开局库不在本文讨论的重点中,本文使用经典26种开局,但是只是使用了前三手。后面利用爬虫爬取一些公开的开局库目前502了,然后通过旋转的方式进行增广。
强烈推荐一下几篇关于基于博弈树搜索的五子棋AI的博文。
第一篇是最经典的使用C++象棋AI的博文,内容涵盖面广。象棋百科全书
第二篇是比较火使用纯JS编写的五子棋AI。lihongxun945/gobang
第三篇是比较经典使用VB编写的五子棋AI。清月连珠