翻棋斗兽棋:搜索量增至512倍,仍有23%的对局落败
我做了 MistyFlipJungle,一个接近完美的翻棋斗兽棋引擎。翻棋斗兽棋是4x4的斗兽棋,开局时每枚棋子都背面朝下。你可以 在 Mistboard 上与 MistyFlipJungle 对弈。 目的不只是造出一个强棋手。引擎本身就是测量仪器:搜索量为另一方512倍的副本,仍会输掉23%的对局。
所以这是另一类引擎文章。暗棋问的是:当存在机会节点时该测量什么。斗兽棋问的是:在完全信息的动物棋里经典搜索能走多远。翻棋斗兽棋问的是:当棋子一开始就是隐藏的,还有多少棋力能留存下来。比感觉上的要少。
| 指标 | 结果 |
|---|---|
| 搜索阶梯,1k 到 512k 节点 | 512倍算力跨度上相差242 Elo |
| 搜索量提升512倍 | 输掉23%的对局 |
| 搜索量提升8倍 | 输掉37%到40% |
| 任意搜索对随机走子 | 约99%(随机方800局全负) |
| 残局(≤5子)对照精确残局库 | 99%到100%为最优着 |
| 中局(5到6子)对照精确求解器 | 200着中200着最优 |
这个游戏
翻棋斗兽棋在4x4棋盘上进行,共十六只动物,每方八只。所有动物开局时都背面朝下。轮到你时,可以翻开一枚棋子,随机揭示一只动物,或者把一只已翻开的动物走一格。吃光对方全部棋子即获胜。
吃子按等级:强者吃弱者。有两个重要的例外。鼠吃象,最弱克最强。同级的两只动物则同归于尽,双双离场。
吃子顺序,由强到弱

象
狮
虎
豹
狼
狗
猫
鼠每只动物可吃其右侧的动物。鼠另可吃象,同级的两只动物同归于尽,双双离场。
完整规则与可试下的棋盘:mistboard.com/rules/jungle-flip。
标准斗兽棋,也就是上一篇里的完全信息版本,是纯粹的计算:整盘局面一览无余。翻棋斗兽棋把棋子藏了起来,而翻开的棋子身份是随机的。方差正来自这些翻子。
这个引擎
Rust,alpha-beta 搜索,手工评估函数,没有神经网络。带置换表的 negamax、迭代加深、吃子静态搜索,以及 killer 和 history 着法排序。评估函数是子力加机动性,外加一个厌和项。棋力以节点预算而非时钟衡量,因此同一局面在任何机器上都返回同样的着法,测出的差距属于引擎而不是硬件。
翻子是机会节点。在翻子处,其值是对袋中可能揭示的所有棋子取平均,并用 star-minimax(机会节点版本的 alpha-beta)剪枝。着法排序物有所值:killer 和 history 排序把达到给定深度所需的节点数减半,并与未剪枝搜索核对确认返回值完全一致,因此在同样预算下大约多买到一层深度,而不改变搜索算出的结果。
残局单独处理,用精确残局库。翻棋斗兽棋的残局可以用逆向分析求解:从每个已结束的局面倒推,记录双方最佳着法下的结果。我把表存成带完美索引的扁平数组(无键),两位存结果加一字节存到达该结果的距离,并在多核上并行构建。并行化中的陷阱是:表通过把每个局面映射到颜色规范形式而减半,而一步安静着会改变轮到谁走,于是一个局面的子节点可能落进与朴素分桶所设想的不同的桶里。求解过程是单调的,一个局面只会从未知变为固定结果,这正是并行构建安全的原因。
如何测量
自我对弈衡量的是两个版本中哪个更强,而不是两者是否下得好:同一引擎的两个副本共享相同的盲点,若两者都误判某个局面,它们之间的比分对此毫无说明。着法质量改用残局库来评分。
在可求解的残局(至多五子)上,引擎在99%到100%的场合走出最优着,漏掉的那些是搜索深度问题,加深搜索即可解决。基于残局库构建的前向求解器能再往前一点,覆盖仍有棋子背面朝下的五子和六子中局;在其中200个局面上,它每次都与最优着一致。
这种覆盖止步于精确计算能力的边界,也就是接近终局处。开局,也就是翻子发生的地方,规模太大无法求解,因此这里没有任何东西能验证引擎的开局表现。「接近完美」指的是在我能检验的局面里接近完美。
棋力天花板
为了在没有精确答案的情况下衡量开局,我比较了不同强度的引擎。四个分别搜索1k、8k、64k和512k节点的副本进行循环赛,每组配对进行成对的换先后手对局,各120局,再由结果拟合等级分:
1k nodes +0
8k nodes +89
64k nodes +153
512k nodes +242
搜索量增至512倍大约值242 Elo。在国际象棋里,这么多算力值好几百分。各档间大致维持在每8倍64到89 Elo,到顶端趋平:320k到640k节点只值6 Elo。
爆冷率比等级分暗示的要高。强8倍的引擎会输掉37%到40%的对局;强512倍的输掉23%。翻子本身就决定了足够多的对局,多搜索也补不上这个缺口。
有一级跳跃很大,在最底端。1k节点的搜索约99%的场合能击败随机走子方(各档合计800局中随机方一局未胜),差距接近850 Elo。从随机到完美的距离中,约80%就在这一步里,即从随机走子到开始搜索。它之上的阶梯很平缓。
调整评估函数毫无作用。更好的子力价值、鼠象关系项、厌和项,在成对自我对弈中结果都是持平。在一个如此依赖运气的游戏里,评估值的差异会被冲淡。
西洋双陆棋是常被提到的运气压缩型游戏;翻棋斗兽棋压缩得更狠,棋盘更小、残局已解、开局完全随机。
完美对完美
这个阶梯是对开局的估计,并没有把它解出来。足够小的游戏可以彻底求解。我把完整的迷你版本从头到尾解了出来,包含开局翻子:两子和四子,开局时每枚棋子都背面朝下,按完整规则下到终局。
在双方最佳着法下,它们都是和棋。精确值为零,最优变化每次都和棋。两个完美棋手无法击败彼此。
从开局而非单个残局局面去求解一个六子游戏,就已经跨过4000万个不同局面:正是这堵墙让完整游戏遥不可及。所以这对小型版本是精确的,对完整十六子游戏则未经证明。
但它仍然确定了双方最佳着法值多少。完美对完美是和棋。完美对远弱的对手几乎必胜:随机走子方800局全负。强512倍的引擎丢掉的那23%处在中间:两个水平接近的棋手,差距被方差淹没,由翻子决定胜负。这些败局来自棋手之间的差距,而非来自双方最佳着法本身。
求解进度
逐层来看,这个游戏在精确计算面前的状况。这些表不计时钟(忽略40着无进展规则),覆盖每个子力数下所有完全揭示的局面;每条记录存双方最佳着法下的结果以及到达该结果的距离。
| 子数 | 局面数 | 最长必胜路径 | 构建 |
|---|---|---|---|
| 2 | 28,800 | 9着 | 瞬时 |
| 3 | 1.9M | 21着 | 2 秒 |
| 4 | 79M | 41着 | 200 秒 |
| 5 | 2.3B | 63着 | 笔记本上4小时,2.8 GB |
| 6 | 46B | 未构建 | 数天,约140 GB 内存 |
六子在租用的服务器上可以构建,但对引擎毫无价值:五子的覆盖已经能给所有进入表中的残局评分,其余部分搜索也走得接近最优。再往上,求解完整游戏就意味着求解开局,而开局就是翻子树:十六枚不同棋子以随机顺序揭示,可达状态量级在10^11或更高。上一节的迷你版本是连同那棵树一起从头到尾解出来的;十六子的游戏没有,而且我看不到通往它的路径。
距离那一列是我第一次做错的地方。最初的表只存胜、和、负,别无其他,这足以给着法评分,却不足以把棋下完。下面观棋器里的第5局就是这种情形:从第24回合起,黑方一虎一鼠对红方一只孤象,在表中是必胜(鼠在三着内困住象,因为象不能吃鼠)。黑方从未把胜势送掉,也从未把它兑现。每一步获胜着法得分都相同,机动性作为平局决胜项在其中挑选,劣势方乐得来回挪子,重复规则最终判和。连续八个回合,引擎握着一个必胜局面,走出表中评为最优的着法,却毫无进展。
存下距离就解决了:3着取胜现在优于9着取胜,于是引擎选择最短的必胜路线,并把必败拖长。从残局库中的必胜局面出发,它现在恰好在表中记录的步数内结束,而第5局的残局以鼠三着吃象收场。国际象棋几十年前就撞上过同样的问题,这正是 Syzygy 表在胜/和/负旁边附带距离度量的原因。有些教训你会在4x4的棋盘上重新发现一遍。
看它下棋
引擎以全力自我对弈。五局,各自一块棋盘。用箭头逐着回放,从全部背面朝下到终局。第5局就是距离那一节剖析的和棋:旧表,没有距离度量,一个引擎连握八个回合却下不完的必胜残局。它留在这里作为那个缺陷的展品。用上带距离的表后,同样的局面它能赢下来,鼠三着吃象。
现状
这里棋力是真实存在的。242 Elo 的阶梯是真实差距,长赛程下更强的引擎会赢。天花板在单局上:发牌与翻子决定了任一单局中很大一部分的结果,所以即使强得多的棋手也常会输,而一局结果几乎说明不了谁更强。棋力体现在平均值里,而不是某一局里。
在我能检验的地方,这个引擎接近完美;在我不能检验的地方,也就是开局,则未经验证。我唯一缺的测量,是对强人类棋手的等级分,这是仅剩的、不依赖引擎或其求解器、也不会继承它们盲点的检验。
资源:
- 在 Mistboard 上与 MistyFlipJungle 对弈,或阅读规则。
- misty-flip-jungle:引擎,Rust 实现。
- 引擎系列的更多内容:打造一个斗兽棋引擎,那是完全信息的同门版本,同样的动物但没有暗子;以及打造一个暗棋引擎,另一个暗子引擎,关于中国暗棋。