规则

国际象棋的部落棋(Horde)是它的母版:36 个兵、没有王,对抗正常的一方军队。这次移植尽量保留象棋本身的规则。红方是兵团:只有兵,没有帅,红方先行。黑方是标准全军。红方靠将死取胜;黑方靠吃掉红方所有子取胜。由于棋盘上只有一个将,兵团一方不存在被将,也不存在白脸将。

轮到行棋而无合法着法的一方判负,与象棋相同;Lichess 的部落棋判为和棋,下面的终局规则一节会说明这有多大影响。三次重复局面判和;连续 60 或 120 步无吃子也判和。标准兵走法与象棋相同:向前一格,过河后才能横走,永不后退。老兵从第一步起就有过河兵的走法。无论哪种设定,全军一方的五个兵都是标准兵。

36 个标准兵,第 1-4 线楚 河 漢 界Mistboard36 个老兵,第 2-5 线楚 河 漢 界Mistboard
原版阵形配标准兵,以及我们找到的最接近一盘棋的设计:36 个老兵前移一行,用过河兵的棋子图案画出,因为那正是它们的走法。

设计空间

五个旋钮。一种设计由一种兵的走法和一种初始阵形构成,我们下了二十四种。

旋钮试过的设定
兵的走法标准;老兵(第一步即可横走)
兵的数量18, 27, 31, 32, 36, 40, 45
兵阵起始位置靠底(第 1 线往上)、前压(第 2 或第 3 线往上)、过河(前排位于第 6 线)
兵阵的形状整排;母版的形状(成排再加成对的前哨);象棋的五个兵位
终局规则60 或 120 步的无吃子步数限制;困毙判负(象棋)或判和(母版)

每种设计都跑在原版 Fairy-Stockfish 上,只用一小段配置,不改引擎代码,由一个规则内核担任裁判,并在读取任何对局之前就与引擎的着法生成器逐一核对一致(检查见“我们是怎么核验的”一节)。每种阵形下四局,引擎在四档搜索预算下自我对弈,无随机成分;再以每着一百万节点下一局。棋盘上没有红帅,引擎无法使用它的象棋神经网络,因此只靠古典评估下棋。它照样把棋下完了。

十二种初始阵形,按下方表格的顺序排列。红方是兵团,黑方是标准全军;每种阵形都用两种兵的走法各下过。

标准兵:车从背后吃光兵团

十二种阵形里有九种四局全归全军,靠的是同一个机制。标准兵只控制它正前方那一格,所以兵团是九列纵队,而不是一道墙。某一列一旦走空,车就从这一列直落底线,一着吃一个兵地从背后蚕食兵阵,而兵团没有任何子力能向后攻击。形状不起作用:前哨是靶子,不是掩护。

原版阵形的一百万节点对局,每一步都在。车在第 50 步落到底线;从那里往后一步步看兵阵如何从背后被吃掉。本页通过规则内核重放。

另外三种阵形是同一个例外:在车做完这件事之前,有足够多的兵过了河。第 1 至 5 线的 45 兵是五层深的兵阵,四局结果是全军两胜、兵团将死一局、和棋一局。两种从过河处起始的阵形一开局就与全军的兵短兵相接。27 兵仍然要输;36 兵则是五五开,它跳过了行军阶段,预演了老兵版的棋局。

兵数起始全军兵团和百万节点局步数
18第 1-2 线400全军,全灭46
18第 3-4 线400全军,全灭70
27第 1-3 线400全军,全灭84
27第 2-4 线400全军,全灭108
27第 4-6 线301全军,全灭154
36第 1-4 线400全军,全灭134
36第 2-5 线400全军,全灭184
36第 3-6 线220兵团,将死195
45第 1-5 线211全军,全灭198
32象棋的五个兵位400全军,全灭122
31母版形状400全军,全灭112
40母版形状400全军,全灭158
全军、兵团、和棋三栏统计每种阵形的四局,每着 150,000、200,000、300,000 和 500,000 节点,120 步无吃子限制,每着都取引擎首选。之后是每着一百万节点、60 步限制下的单局及其步数。有四种阵形在每着五百万节点下又下了一局:全军在 156 步赢下第 1 至 4 线的 36 兵,在 146 步赢下第 2 至 5 线的 36 兵,在 106 步赢下 32 兵;过河起始的 36 兵在第 104 步因重复局面和棋。

老兵:兵团守得住,围城是和棋

车之所以有路可走,是因为标准兵不攻击身旁任何一格,而象棋里本来就有一种能做到的兵:过河兵,可向前或横向一格行走与吃子。老兵就是一开始就已经升变的兵团。兵团的每个兵从第一步起就有这种走法,其余一切不变,玩家不需要学任何新东西。

这条路被堵死了。老兵纵队会攻击身旁各格,车没法白站在它旁边。兵团成了真正的一方,而在实力相当的双方之间,棋局变成围城:48 局,37 和。全军只赢得了对 18 兵的局,从 27 兵往上它一局不胜。

兵数起始全军兵团和百万节点局步数全军损失
18第 1-2 线202全军,全灭2589
18第 3-4 线301全军,全灭30810
27第 1-3 线004和棋,步数限制4597
27第 2-4 线004和棋,步数限制4908
27第 4-6 线004和棋,步数限制1669
36第 1-4 线004和棋,步数限制1,058*13
36第 2-5 线031兵团,困毙43515
36第 3-6 线013和棋,重复局面70313
45第 1-5 线013和棋,步数限制1,102*12
32象棋的五个兵位004和棋,步数限制4497
31母版形状004和棋,步数限制2475
40母版形状013和棋,步数限制1,016*12
每种阵形在同样的四档预算、120 步限制下各下四局,然后是一百万节点下的单局;全军损失一栏统计全军在那一局里损失的子力。*有三局一百万节点对局撞上了测试框架的 1,000 步上限,之后从该局面继续下,步数限制照常生效;之后未再出现吃子,表中所示步数即为步数限制终止之处。三局结束时都是九或十个兵对将加双车,其中两局还多一个象或一个士。

终局形态。兵团行军推进,路上吃掉全军的炮和相,抵达九宫时发现将和两只车在那里等着。到了这一步,兵冲不过车,车也吃不动互相保护的兵阵。起始横线和形状只决定兵团在路上能吃掉全军多少子,别无其他。母版的 31 兵展示了同一条规则的另一面:四局在 155 至 245 步和棋,过河的兵从未超过两个,因为没动过的兵阵无法被攻击,而一动就要丢掉动过的那个兵。

第 754 步:12 个兵楚 河 漢 界Mistboard第 1,000 步:10 个兵楚 河 漢 界Mistboard
终局形态,这里取自 40 兵阵形、每着一百万节点的对局:第 754 步时,十二个兵对将、士和双车。兵冲不过车,而车只有在某个兵走出兵阵时才吃得到它:接下来 246 步里只吃了两个,分别在第 822 和 896 步。

我们把 40 兵那局在第 754 步的残局拿出来,让引擎双方各以每着五百万节点再下 200 步:只吃了一个子,而引擎自己对兵团优势的评估值从 2.3 兵滑到 0.7。在一百万节点下,全军在同样这段里吃了两个子,接下来的 120 步里一个也没吃,步数限制结束了对局。262 步吃两个兵,算不上赢下残局。

取胜的那一局。前压一线的 36 兵吃掉了两只车,并在第 435 步把光将困毙:无着可走又未被将,象棋判负。这是兵团唯一占优的阵形,四局赢三局,一百万节点那局也赢了:离河近一线,抵达时全军已经损失更多,而剩下的兵也足够换掉两只车。兵团其余的胜局都是同样的收束,全部在第 390 步之后,六胜里有四胜是困毙。

兵团的胜局,每一步都在:行军、围城、第 267 和第 431 步双车先后被吃,以及第 435 步的困毙。本页通过规则内核重放。

终局规则

这里有三种让棋局无胜负而终止的方式,它们并不是一回事。重复局面:同一局面、同一方行棋出现三次;判和。步数限制:连续若干步无吃子,60 步或 120 步;判和,而且兵向前一步并不重置计数。上限:测试框架在 400、1,000 或 2,000 步处终止对局。这不是棋规,所以被截断的对局是未下完,而不是和棋。有三局一百万节点的老兵对局撞上了 1,000 步上限,随后继续下到步数限制终止;而四档预算的对局没有一局到过 2,000 步,因此表格里的每个结果都属于前两种。

步数限制。象棋的无吃子规则是 60 步。用标准兵时,48 局里只触发过一次。用老兵时它是常见的收束:37 和里有 30 局如此,另外七局是重复局面。设成 60 步会在行军还在进行时截断还活着的棋局,所以老兵对局和每一组四局样本都用 120 步。步数限制同时也是引擎遵循的规则,所以 60 步限制下的一局和 120 步限制下的一局,从第 2 步起就是两局不同的棋,而不是同一局在不同处被截断。

困毙。按母版的规则,将无着可走又未被将判和棋。把实验室里的每局都按这条规则重新判定:老兵、实力相当条件下兵团的七胜里有五胜变成和棋,标准兵的四胜里有一胜变成和棋;对随机走子的全军,标准兵下兵团的 35 胜里有 20 胜、老兵下 38 胜里有 16 胜变成和棋;老兵预算阶梯测试的十胜里有五胜同样如此。困毙大约占了兵团胜法的一半,而母版的规则会把它拿掉。

兵为什么不行,换一种兵又需要什么

棋局分两个阶段,象棋的兵在每个阶段都以不同方式失灵。

过河之前,兵阵无法自保。标准兵只攻击正前方一格。于是兵团是九列纵队,而不是一道墙:车站在某列旁边或它后面都碰不到,任何走空的纵线都是通往底线的大路。兵团在自己这半边、40 兵及以下的所有阵形,都在这一阶段被这一只车判定,四局全输。到 45 兵时,兵阵够厚,有时能熬过去,那一种阵形于是结果分化。

过河之后,兵阵动不了。过河兵(或老兵)也能横向攻击,所以兵阵原地就能互相保护。但它只保护自家兵能走到的格子,绝不保护邻兵正前方那一格。向前一步的兵落在没人控制的格子上,在邻兵跟上来并列之前就是白送的一子。于是兵阵只能整排整排地推进,每推一排要走九着,而全军只需等待;任何提前走动的兵都会被点掉。而全军一方也攻不动一个没动过的兵阵。十二种老兵阵形里有九种就在这里终止,或全部对局如此,或只有一局例外:将和两只车守在九宫,一堆兵围在外面,双方都无事可做。

国际象棋:e4-e5 之后,d4 控制 e5
兵:e4-e5 之后,无子控制 e5楚 河 漢 界Mistboard
老兵:仍然无子控制 e5楚 河 漢 界Mistboard

同一步棋,三张棋盘:中间那个子刚向前走了一格,绿圈是它这一方此刻保护的点。国际象棋的兵斜着吃子,所以 d4 的兵控制着 e5,走到那里的兵车吃不得:兵成链推进。象棋的兵怎么走就怎么吃,直着向前,所以 d4 和 f4 控制 d5 和 f5,没有任何子控制 e5;车白吃这个兵。老兵(右)也能横向攻击,所以兵阵原地就能互相保护,但兵要走到的那一点仍然没有任何子控制。兵团守得住阵地,却离不开它。

国际象棋的兵构造不同。它斜吃,所以 d4 的兵在 e 兵走到 e5 之前就已经控制了 e5:兵是成链推进的,一次走一个,每一步落点都有保护,而兵链可以从底部瓦解。这就是部落棋在国际象棋里成立、而国际象棋棋手根本不必考虑这件事的原因。在象棋盘上,河是一道悬崖:过去的路上没有掩护,一旦过去又是全覆盖,而数量、起始横线、形状、步数限制怎么调都改变不了这一点。这些正是我们转过的旋钮。除了老兵之外,我们没有碰的那个旋钮,就是兵本身。

下一次尝试就从那里开始。兵团缺的那条性质是路上的掩护:一个能保护邻兵即将踏上的那一格的兵,好让兵阵一次推进一个子,而不是一次推进一排。任何能提供这条性质的设计都是候选,改动从小到大不等:斜前吃子但仍直走的兵、第一步可走两格、像旧时“五虎”让子那样一回合走两个兵、到底线后再升一次变,或者干脆直接用国际象棋的兵,那就把这变成了大棋盘上的部落国际象棋。我们止步于老兵,因为它是这些方案里唯一象棋本来就有的;其余每一个都是新子力,而新子力就是另一种游戏。规则在实验室里就是数据,所以其中任何一个都只是一行配置加一下午的引擎时间,而这里的表格就是需要超越的基线。

自己跑一遍

整个变体就是 Fairy-Stockfish 的 variants.ini 里的一段。下面是母版阵形配老兵与 120 步限制(nMoveRule 按回合计,所以 60 就是 120 步);要用标准兵,删掉 customPiece1 那一行,并在初始局面里把 V 写成 P。

[hordexiangqi:xiangqi]
startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/VVVVVVVVV/VVVVVVVVV/VVVVVVVVV/VVVVVVVVV w - - 0 1
customPiece1 = v:fsW
extinctionValue = loss
extinctionPieceTypes = *
flyingGeneral = false
chasingRule = none
stalemateValue = loss
perpetualCheckIllegal = false
nFoldValue = draw
nMoveRule = 60
stockfish check horde.ini            # parses the stanza
setoption name VariantPath value horde.ini
setoption name UCI_Variant value hordexiangqi
position startpos
go nodes 1000000

有两行很关键、又很容易漏掉。chasingRule = none 是必需的:在无将的一方,引擎的捉子检测会去读那个不存在的将的位置。flyingGeneral = false 其实不改变任何东西,只是记录一个事实:引擎把白脸将规则挂在它的王类子力上,一方没有王时会静默关掉它。

我们是怎么核验的

引擎确实在按这些规则下棋。一个规则内核(就是跑原子象棋那一个,加上了无将一方、全灭取胜和老兵的接口)为每一局担任裁判:引擎提出一着,内核校验并落子,如果引擎悄悄按别的规则下棋,运行会中止,而不是产出另一种游戏的记录。在读取任何对局之前,两者必须在每一个合法着法上一致:从每个阵形做深度 3 的 perft、九个手工构造的局面按精确着法集合比对、以及随机走子产生的 300 个局面。零分歧,两种兵的走法、全部十二种阵形。

引擎懂这个游戏。在 100,000 节点下,它用标准兵对随机走子方 70-0,用老兵 80 局胜 78 局。十倍搜索预算对十分之一预算,在标准前压阵形上 17-1-2,在老兵上 11-0-9。一条脚注:在母版阵形配标准兵时,十倍预算只拿到 55%,因为无论哪一档预算坐哪一边,每一局有胜负的棋都归全军;决定结果的是座位,不是搜索。

搜索有多深。用节点预算而不是时间,所以结果不依赖机器:每着 200,000、一百万和五百万节点,在这些局面上大致达到深度 12、15 至 20、24 至 33。在这些预算下棋力还没饱和:标准兵的过河阵形,七档预算跑出来依次是和、全军、兵团、兵团、全军、兵团、和。标准兵的结论在试过的每一档预算下都成立,机制一致,这个论断是稳的。围城是搜索最不擅长的局面类型:一百步的计划在深度 20 下完全看不见,而古典评估根本不知道这种残局值多少(在全军正在蚕食兵阵时,它给这个堡垒打了兵团 +2.3)。所以和棋的论断比负局的论断要弱,有计划的人类在任何一边都可能下得比引擎好。

一局只是一个样本。每种阵形都下了四局,每着 150,000、200,000、300,000 和 500,000 节点,每着都取引擎首选,没有随机开局,所以表格里的计数是一个比率,而一百万节点对局是五局之一,不是结论。确定性的引擎从同一起始会重现同一局棋,所以正是预算让这四局不同;脚本会拒收其中两局完全相同的阵形(没有出现过)。结果分化时,分化本身就是结果。关于老兵数据还有一条说明:引擎把老兵当作自定义子力,其价值是按机动性估出来的,未经调优,所以它对“一兵换一子”的判断比对标准兵更粗糙。它在五百万节点下都攻不破的那个堡垒是真实的;而它推进路上舍掉的子力,可能并非最佳着法。

它目前处于什么水平

对这个系列而言,一个变体要算游戏,需要在可测量的对弈中越过三道门槛:在高手之间有胜负,而不是默认和棋;任何一方都不能从开局就必胜;两个座位都有事可做。部落象棋在试过的任何设计里都一道也没越过。

标准兵:兵团起始于自己这半边、40 兵及以下的所有阵形,全军全胜,36 局全胜,靠的是同一个招法。有胜负,但从第一着就定了。到 45 兵,或从过河处起始,阵形就变成由开局几手交换决定的五五开。

老兵:两位高手之间是一场围城,48 局里 37 局和棋,每局 137 至 1,098 步。兵团胜六局,其中四局靠困毙;全军胜五局,全都是对 18 兵。没有胜负,而且两个座位都没有计划:全军的最佳下法是待在九宫里等,兵团的最佳下法是把兵阵一格一格地挪。兵团唯一占优的阵形只是同一场围城分数更好一点,不是另一种游戏。

对付弱手时兵团很危险:在 100,000 节点下它对随机走子的全军 35 局全胜,其中 20 局靠困毙,而人比一百万节点的搜索要弱。这让它成了一个陷阱,而不是一个游戏。

什么能让它重新打开:人类对局的结果、一种能给兵阵提供路上掩护的兵的规则(候选已列在上面),或者车被吃的比率远高于这里测到的 48 局中 6 局。就象棋原本的兵而言,目前没有任何迹象指向那个方向。兵就是设计空间,河是其中的一道悬崖,下一个设计必须改兵。

已有工作检索。我们查过 Fairy-Stockfish 的变体文件、PyChess、Lichess、Chess.com 的官方列表、PlayStrategy、Ludii、中英文维基百科的象棋变体条目、chessvariants.com 的 9x10 索引、知乎上一份 231 种民间象棋变体的清单,以及百度百科上九种带“兵”字名称且有规则的变体,没有找到部落式的变体。贴吧的民间棋盘和 Chess.com 的用户变体我们看不到,所以这是一次尽力的否证,而非穷尽的否证。象棋本身的不对称让子棋(满洲棋、五虎、九子让先)走的是反方向,是从一方拿掉子力,先拿兵。国际象棋的部落棋源自 BrainKing 的 Horde Chess(2002)和 Lord Dunsany 的“Pieces against Pawns”(1942),Lichess 在 2015 年于 scalachess 定下了它的规则。

引擎是 Fairy-Stockfish;产出这些对局的测试框架,连同引擎校验关卡和相关命令,是变体实验室。

每一盘棋,逐步回放

引擎对局中有五十八盘在下面的查看器里:引擎在各个阵形上自战,每着 200,000、一百万和五百万节点,标准兵 28 盘,老兵 28 盘;另有两盘标准兵前压阵形上的阶梯对局,100,000 节点对 10,000 节点:兵团最快的一胜,第 151 步将死,以及一盘无吃子步数到限的和棋。查看器先打开上面那盘兵团的胜局。列表里的任何一盘都可以选。

全部 58 盘棋,每一盘都经规则内核重放。方向键可以逐步走棋盘上的这一盘。

我们发布的是什么,而不是一个对局页面

含两种兵的走法和全部十二种阵形的规则内核、Fairy-Stockfish 的变体配置、全部 387 盘引擎对局、它们背后的各项检验,以及一个不需要引擎、半分钟内就能对照规则重放每一盘并重新计算统计的校验器。如果你有一种能给兵阵路上掩护的兵的走法,下一次尝试就从那里开始;去那里提一个 issue,文章会如实写明。

亲自检验这些对局学习象棋