Benedict Chess 没有吃子。你移动一个棋子后,它在新格上所攻击到的每一个敌方棋子都会变色并加入你一方。把对方的王转化过来即为胜。W. D. Troyka 于 2002 年发表了它,名字取自倒戈的 Benedict Arnold。

就我能查到的范围,还没有人把这条规则放到象棋棋盘上。Chess Variant Pages 收录了 7,383 种游戏,其中 43 个用象棋棋盘的条目和十几个带转化机制的条目是两个互不相交的集合;Ludii 的 2,330 种游戏和 Fairy-Stockfish 的变体列表也是同样的情况。那些擦肩而过的例子值得一提,因为大多数只差一个关键点。Desertion Chess 靠走到棋子旁边来转化,而不是靠攻击它,并且保留吃子。Andernach 翻转的是吃子方而不是被吃方。Cleopatra Chess 就是 Troyka 的规则,但只限于一种棋子。而象棋自己的翻子玩法,暗棋和揭棋,翻开棋子是为了揭示它是什么,这和改变它属于谁完全是两回事。

象棋有隔着炮架吃子的炮,有锁在九宫九个点里的将/帅(黑/红),还有过不了楚河汉界的象/相,所以这个移植从来不可能是机械照搬。它逼出了四个没有默认答案的设计决定。

然后我写了一个引擎,来看看结果是否公平,答案是不公平。先行方赢下 78% 的对局,而普通象棋大约是 55%。这里面几乎全部来自开局第一着可以走出的一步炮,这比一个整体就失衡的游戏要好修得多。

规则

走一个子。它落点处攻击到的每一个敌方棋子都归你。

红方行棋楚 河 漢 界
1 / 2

有一点要早点注意:原地的攻击不产生任何作用。只有移动的那个棋子才会转化,所以一辆已经瞄着某个目标的车必须再走一步才能把它拿下。威胁只能靠走子来制造,等待永远没用。

试着下一下

两方都由你操作,背后没有引擎。它跑的是本文每一个图示都要校验的同一套规则内核,所以它不可能允许一个本文说是非法的着法。

楚 河 漢 界

棋盘下方的计数就是本文后面反复回到的那个数字。它从零开始,而且不会一直是零。

这对象棋意味着什么

永远没有残局。全部 32 个子整局都留在棋盘上。没有简化,也没有兑子,而且残局库在原理上就不可能存在,因为状态空间永远不会缩小。象棋的残局理论,也就是象棋理论的主体部分,完全无法迁移过来。

炮不再走弱。象棋的炮需要炮架才能吃子,所以棋盘越空它越弱。这里棋盘永远不会空,一个炮在第 80 着和第 3 着一样危险。拟合出的数值也印证了这点:炮算出来是 657,而象棋的子力价值表通常把它放在 450 附近,已经接近车的 900,足以改变你使用它的方式。

士/仕和象/相不再防守任何东西。它们存在的意义是防吃子,而这里没有吃子。它们仍然能封线、能当炮架,所以并非全无用处,但它们被设计出来要做的事已经全部消失。

对局很短,而且没有人和棋。中位数 29 着,而象棋是 80 到 100 着。在最强设置下的 54 局里没有一局和棋,在 6,000 局完全随机走子中也没有出现一局,这是更强的版本:不是说强手在这里会避开和棋,而是局面除了走向分晓无处可去。一个没有子可兑、也没有残局可磨的游戏不可能后劲耗尽。

大约 28% 的合法着法会立即输掉。这是下面那些对局共 1,696 着的平均值,值得说清楚,因为平均值并不代表典型局面:中位局面只有 10% 的着法是输着,而均值是被少数极其锋利的局面拉起来的。均值还会攀升,从一局前五分之一的 24% 升到最后五分之一的 37%。而不会出现的,是一个可以安心出子的平静开局阶段。就连前五分之一也平均有四分之一的着法立即败北。

设计这个游戏

两个将/帅(黑/红)仍然不得照面。象棋禁止双方的将帅处在同一条纵线上且中间无子,我保留了这一条。另一种做法是把白脸将设为取胜条件,听起来更好但会崩:在 Benedict 规则下只有移动的那个子算数,所以把这条线让通并不算胜,结果就是两将对面而对局继续,直到有人把将沿着这条线挪一步来兑现胜利。

保留这条禁令不花任何代价,还带来了一个象棋没有的东西。被牵在将帅同一线上的棋子无法被强行拿掉,因为这里没有任何东西能拿掉任何东西,转化它也不能解放它,因为转化是原地换色。你可以赢下这个子,却仍然动不了它。只有将/帅(黑/红)离开这条线才能解放它,所以这个牵制持续的时间,正好等于牵制方把自己的将留在那里的时间。

士/仕和象/相绑定的是它们所处的区域,而不是所有者的区域。一个被转化的士/仕会处在敌方九宫里,若按所有者绑定的规则,它就永远被冻在那里,这是一个死子,也是一条无聊的规则。绑定它所处的九宫,它就还能继续走。从起始阵形看,两种读法完全一致,因为每个士/仕本来就在自己的九宫里,所以这个推广不花任何代价,还把一个死子变成了攻击手。

卒/兵绑定的是所有者,这是相反的判断。卒/兵的前进方向必须跟随当前的所有者,否则被转化的卒/兵就会永远朝着自己一方相反的方向行军。把这两条搞反,就是那种会悄无声息地毁掉整个游戏的 bug。

和棋需要一个进展计时器。由于没有吃子,几乎每一着都是可逆的。单靠三次重复局面不够,所以计时器只在两类不可逆事件上重置:一次转化,或者一步卒/兵的移动。

测试它

现有的引擎都下不了这个。Pikafish、Fairy-Stockfish 以及我自己的迷雾引擎都把吃子规则写进了代码,而 Fairy-Stockfish 唯一的转化机制(flipEnclosedPieces)翻转的是被一次落子围住的棋子,黑白棋那种做法,是另一条规则。

于是:一个 Rust 写的 alpha-beta 引擎,带置换表、主要变例搜索、后期着法削减,以及针对转化的静态搜索。我没有直接搬象棋的子力价值,而是用 2,500 局自对弈的结果做逻辑回归拟合,在直接对抗赛中它打赢了我手工挑的那套数值。

我不打算引用拟合出的数字。早先的草稿引用了,并且把它们当作各棋子实际价值的测量结果来解读,而这超出了一个基于 8,000 个相关局面的逻辑回归能支撑的范围:早先只用四分之一数据做的拟合中,有几个系数的符号后来反了。留下来的是它认同的那个排序,也就是士/仕和象/相在这里几乎一文不值,而这一点根本不需要回归,因为它直接来自「没有东西需要防」这个事实。

规则内核是按链条验证的,这一点很重要,因为后来出问题的全在测量环节而不在规则。一个 JavaScript 原型与我本来就在用的象棋库 elephantops 做了差分测试,覆盖 720,947 次棋子几何比较,零处不一致。Rust 引擎在 1.13 亿个局面之内完全复现了那个内核的着法计数。一个 TypeScript 移植版复现了这两者。

有三个结果贯穿之后的每一次改动都成立,因为它们都不依赖引擎的判断:

测试项 结果
随机走子对随机走子,6,000 局 先行方 50.1%,0 和棋
从起始阵形出发的无削减证明搜索,208 亿节点 13 着之内没有必胜
立即败北的合法着法占比 均值 28%,中位数 10%,基于 1,696 着

这三项在引擎变化时都不动,所以我比胜率更信任它们。

这个引擎强到足以评判一个游戏吗?它每秒搜索约 160 万个局面,几秒内能到搜索深度 8 或 9。远远不及成熟的国际象棋引擎。但它不需要那么强。

上面三个结果完全不依赖它下得多好。一个是随机走子的对照,一个是证明,一个是合法着法的计数。这个证明值得说清楚,因为普通搜索给不出证明:这个引擎用了后期着法削减,靠后的着法搜得更浅,所以一次普通搜索没找到胜利,只说明它没有看遍所有地方。关掉削减和静态搜索,在将死阈值上把问题问成是非题,才真正构成证明,这就是上面那 13 着的含义。而 78% 这个数字,我检验的是这里真正要紧的性质,也就是引擎变强时它会不会变。它几乎不变:每着 30 万节点时是 77.8%,200 万节点时是 75.4%,两者相差七倍,而抽样误差上下有好几个百分点。

跨引擎强度的稳定性是我信任的检验,但单靠它还不够。这项测量的早期版本用六着随机开局库来制造对局多样性,得到 52.7%,并且在横跨同样十倍范围的三档引擎强度下都保持不变。它之所以稳定,是因为开局库正好在消耗被测量的那一个先手节奏:六着随机走子在任何一方引擎落子之前就把优势扔掉了,所以不管底下的游戏是什么样,结果都会漂到均势。一支坏了的温度计在每个房间都读出室温。上面的 78% 不用开局库,多样性来自评估值抖动。

更多算力能买到什么,范围有限而且相当清楚。位棋盘和更好的着法排序能到搜索深度 12 到 14,那是我在谈深层战术或真正的开局理论之前想达到的水平。学习式评估函数是昂贵的选项,而且大概是错的选项:我在其他变体上的两次早期尝试都在还没达到它们要替换的手工评估函数水平时就饱和了,而这两次失败都不是钱的问题。

所以本文描述的是俱乐部水平下的这个游戏。我保留下来的那些结论,在任何强度下都成立。

引擎、内核和测试框架一共几千行 Rust 和 TypeScript,都放在 GitHub 上:github.com/brianhliou/benedict-xiangqi,连同那 54 局对局和完整的开局表。本文中的每一个数字都出自 cargo run

一步炮就决定了胜负

先行方赢下 78% 的对局。测量方式是从起始阵形出发、不用开局库的同策略对局,并且让两种颜色轮流先行:每着 30 万节点时,红先得分 80.5%,黑先得分 75.0%,各 100 局;200 万节点时分别是 82.5% 和 68.3%,各 60 局。在两种算力预算下两个方向都远高于均势,所以优势跟着先手节奏走,而不是跟着颜色走,这正是它成为关于游戏本身而不是关于我的代码的事实的原因。在较高预算下红方看起来确实是这对搭配中更好的一半,但 60 局带着大约十二个百分点的误差,所以我暂时不会从中读出任何东西。

接着我把红方全部 42 个合法首着分别测了一遍,诊断结果的形状变了。

全部 42 个合法首着,每个 24 局,按红方随后的得分排序。

着法 红方得分   着法 红方得分   着法 红方得分
c1a3 0.0%   h1g3 25.0%   a4a5 41.7%
e1e2 0.0%   a1a3 27.1%   g4g5 43.8%
g1i3 0.0%   b3b2 27.1%   b3b4 45.8%
b3a3 4.2%   h3h2 29.2%   b3b7 50.0%
b3c3 8.3%   b3d3 29.2%   d1e2 54.2%
h1i3 12.5%   i4i5 29.2%   e4e5 54.2%
h3f3 16.7%   b3f3 33.3%   a1a2 58.3%
b3g3 18.8%   b1a3 35.4%   h3h7 60.4%
h3c3 20.8%   c1e3 35.4%   h3h4 60.4%
i1i2 20.8%   b3e3 35.4%   b3b6 70.8%
h3g3 22.9%   c4c5 35.4%   f1e2 72.9%
i1i3 25.0%   h3e3 37.5%   h3h5 79.2%
h3i3 25.0%   h3d3 39.6%   b3b5 87.5%
g1e3 25.0%   b1c3 41.7%   h3h6 91.7%

最好的两个是同一个思路:把炮提到第五或第六横线。得分为零的那三个分别是一步象/相、一只马跳到边线,以及将/帅(黑/红)向中间上一步。

红方第一着的价值可以从零一直到 92%,而 42 个着法中有三个把 24 局全部输掉。所以那 78% 并不是「在这个游戏里一个先手节奏值 78%」,而是「引擎每次都能找到那唯一的强力开局」。

开始楚 河 漢 界
1 / 4

象棋的炮需要在它和目标之间恰好有一个棋子。黑方的开局阵形免费提供了这个棋子,而且位置正好。这个威胁之所以存在,是因为象棋的起始阵形恰好摆在了 Benedict 规则能加以利用的地方。

它下过的每一局

最强设置下的全部 54 局,未经筛选。先行方赢了其中 46 局,而它输掉的那 8 局才是值得打开看的。

楚 河 漢 界
0

对局结束不是因为有人出错。结束是因为一方已经没有不输的着法可走,而这类着法的数量会在棋盘上出现任何可见变化之前攀升二十着。

用蛋糕规则把它变公平

那 42 个开局之间的分布差异正是这件事可以挽救的原因,而解决办法在别的游戏里已经存在。

蛋糕规则的运作方式就像两个小孩分蛋糕:一个人切,另一个人先挑。红方走出第一着。黑方随后选择继续执黑,还是干脆接手红方的局面。这一个选项改变了红方的优化目标,因为当黑方可以直接拿走最强开局时,走出最强开局就变得毫无意义。

于是红方被推向最接近均势的那一着,42 个里有四个落在那里。其中两个是炮放弃自己最好的那个落点,另外两个是棋盘上最平静的出子着法。

楚 河 漢 界
b3b4 45.8%
炮向前一步,远未到能造成杀伤的第五路。
楚 河 漢 界
b3b7 50.0%
炮一路推到底,此刻兑子,放弃了原有的持续威胁。
楚 河 漢 界
d1e2 54.2%
仕出动,腾空d1。将帅同线从第一着起就已生效。
楚 河 漢 界
e4e5 54.2%
中兵:唯一一步不算失误的安静开动着法。

被迫走上其中之一,对局结果就落在 50% 附近。这不是把优势压小,而是取消了红方使用它的能力,这比听起来更干净。Hex 在任何棋盘尺寸下都可证明是先行方必胜,却在全世界作为竞技项目进行,完全靠的就是交换权。

诚实的保留意见在于样本。那四个数字各来自 24 局,宽到我不会拿它们互相排名,也窄到足以说 0% 到 83% 的差距是真实的。用足够的样本确认其中之一确实稳定在 50% 附近,是尚未完成的工作。

蛋糕规则修好了公平性,却不触及锋利度。输着占比是规则本身的属性,不取决于谁领先,所以它完全不变。它让对抗变得均衡,但没有让它变得平和,而那是不是人们愿意玩的游戏,是另一个问题,我无法从胜率里回答。

目前的状态

作为竞技游戏还不能发布。先行方四局赢三局,没有任何等级分阶梯能撑得住。

这一点值得和一个听起来相似的说法区分开。不公平不等于不能玩:losing chess 是已被证明的先行方必胜,却每天都有人在竞技对局,因为那个证明花了五年、八台机器,而且走的是没有人类能找到的变例。Hex 在任何棋盘尺寸下都是已被证明的先行方必胜,也靠交换权照样在下。先手优势本身不会杀死一个游戏。

这里不同的地方在于深度。这 78% 建立在一步可以用一句话说清的着法上,把炮提到第五横线,而且引擎每一次都能找到它。一个被证明但无法企及的胜利只是奇谈。一个可以背下来的胜利就是这个游戏本身。

除蛋糕规则之外还有两种未经测试的修补。给将/帅(黑/红)做标记而不是直接转化它,让防守方有一步应招的机会,这会直接钝化炮的威胁,而把攻击方的棋子转化过来本来就是一种合法的应招。或者调整起始阵形里中路卒/兵的位置,因为这个威胁之所以存在,只是因为黑方的卒正好站在炮需要炮架的地方。

无论如何都能留下来的是这个设计。把转化规则放到象棋棋盘上,得到的是一个没有残局的游戏,炮永远不会变弱,防守棋子一文不值,被转化的士/仕永远滞留在敌方阵后。这一点贯穿了测量的每一个版本,因为规则是我用四种独立方式验证过的那一部分。

下一步是那条单一先手节奏规则:内核里二十行代码,再把已有的测试框架重跑一遍。

尚未解决的问题是红方是否有必胜。上面的一切都是胜率,那是关于两个特定引擎的陈述。底下的问题是,在双方最佳着法下这个游戏是否是先行方必胜,而这个问题的答案不依赖任何一方下得多好。

我手上有的是一个下界:13 着之内没有必胜,用 208 亿节点和大约半小时证明。这排除了廉价的胜法,而一个中位长度为 29 着的游戏,被它探索的部分刚刚过半。

有两件事让这件事比看上去更难,还有一件让它变容易。子力守恒,所以状态空间永不缩小,这意味着没有残局数据库可以建,也没有东西能让正向搜索在中途会合。逆向分析,也就是每一个已解决的国际象棋残局背后的技术,在原理上就不可用,而不只是昂贵。策略窃取,也就是通常从对称起点得到超弱解的办法,同样不适用:棋子是移动而不是累积,所以先行方不能随便走一着然后再把它丢掉。有帮助的是对局很短。中位数是 29 着,而 54 局引擎对局和 6,000 局随机走子中都没有和棋,所以这棵树很浅、终局很锋利,正是证明数搜索擅长的形状。

下一级是 14,而杠杆不在于更多硬件。上面那个证明可以按红方的 42 个首着拆分,几乎完美并行,恰恰因为答案是「没有」:既然没有必胜,任何一个分支都不会剪掉另一个,所以同时搜索它们不会有任何损失。这在一台笔记本上做到了 13。往下拆两着可以得到大约 1,700 个独立任务,那是需要集群的形状;而证明数搜索,也就是把力气花在证明树最窄处而不是均匀搜深,根本还没实现。最后这一条不是假想:losing chess 就是这样被解决的。Mark Watkins 在八台十六核机器上跑了五年多的证明数搜索,证明 1.e3 白方取胜,这既是这里需要的技术,也是一个关于完成这项工作代价的公平警告。

如果有人拿出弱解,甚至用我没想到的论证给出一个超弱解,那么蛋糕规则的问题也一并解决了,因为已被证明的先行方必胜正是交换权被发明出来所针对的情形。Hex 在任何棋盘尺寸下都是已被证明的先行方必胜,照样作为竞技项目在下。