象棋基本残局:哪些已有定论,定论有多可靠
国际象棋有一张人人都要背的基本残局表。王加车必胜。王加双马无法强制取胜。王加单象是死局。象棋也有同样一张表,如果你读中文,你就会用同样的方式学到它:从残局手册里,而这些手册给出的结论都一致。而用英文,你只能看到碎片。
最好的那块碎片,是英文维基百科象棋条目里的残局一节。三十来条结论,用散文写出,没有一条附了出处。中文的对应内容是一篇叫象棋勝和定式(象棋勝和定式)的条目。它覆盖同样的范围,每条结论配一张图,并且引用了两本书:金启昌与杨典的《象棋残局胜和定式》(北京体育大学出版社,2008)和薛荣贤的《象棋入门实用指南》(2009)。这两本书我都没读过。下文我说「手册」,指的就是这两篇维基条目:一篇是手册传统的压缩汇报,另一篇让你看到局面。
下面收了其中三十二条结论,每条都做成一个局面并核对两遍,每条都是一块可以让你和核对它的引擎对下的棋盘。这份研究在 Mistboard 上,一条结论一章。
基础:什么能赢士象全
车是象棋里最强的子,相当于国际象棋里的车换了个名字。标准的士象全保留全部四个防守子:将在原位,双士,双象,并且摆成两个士正好挡在将前面,让炮无法把它们当炮架用。
单车对这样的防守是和棋,而现在这是一条已被证明的结论:残局库判和,Pikafish 给出的评估值正好是 0。
三个卒能赢。15 步内强制将死。
双马也能赢,25 步。
和棋
15 步将死
同样的防守,同样的一方走棋。两条结论都来自残局库,而不是搜索。随便挑一个执黑试试,看能不能守住。
按你能找到的任何一张子力价值表,车都比三个卒值钱,也比两个马值钱。可在这个残局里,它比两者都不如。卒不能后退,这听起来是弱点,结果却成了它们取胜的原因:三个卒逼向九宫,赶不回去,而防守方也没办法把它们兑掉。
同样的逻辑反过来也成立。五个兵全都停在底线,对光将是和棋。五子对零子。它们不能后退,又已经贴着底线,所以既无法将死,也无法困毙。
和棋
五子对一子,而局面里根本不存在将死。执红试试能不能赢下来。
决定这些残局的是位置,而英文那一节里恰好有一对图直接把这点画了出来。
单车对马双象。防守方摆成堡垒(马在 e8,象在 i8 和 g6)时能守住,残局库也确认守得住。把 g6 那个象移到 g10,红方就胜,从 1.Rb7 开始。
和棋
红方胜
全部差别就在棋盘右侧那个象,往上走了四个点。第一个局面守住,第二个局面破掉。
双方子力相同,进攻方相同,走棋方相同,结果却翻转。这一对能在翻译中留存下来,是因为有人把它画了出来。下面三条,是没人画的那些。
取胜需要什么
三十二条结论里有三条,取决于英文没有带上的一句指示。每一条都由一个摆位决定结果,而点明这个摆位的那句话恰好被漏掉了。
车对马炮,和棋只差一格
英文那一节说:「车对无士象的双小子:和棋,但需要良好的防守位置。」
我造了八个这样的局面。Pikafish 在八个里全都找到了强制将死,从我能构造出的最顽强那个算起是 15 步。八比零不可能是搜索的假象,于是我把这一条写成了争议并继续往下走,这个判断是错的。
中文条目画出了局面,并且多了一句英文没带上的指示:炮要直接摆在本方将的后面,马则尽量离将远。
和棋
12 步将死
一个点的差别。左边炮正好在本方将的后面,局面是和棋;右边它移了一格,红方 12 步将死。两边都可以执黑试试。
这个局面不论哪方走棋都是残局库判和,Pikafish 搜索深度到 40 给出 +0.10。炮在将后面守住底线,又无法被顶着将抽,这就是整个堡垒。把它横着挪一个点,同样的局面就是 12 步将死,而我试过的其他每一格也都输,从挪一个点到挪到远角都一样。
车自己摆在哪里,结果完全不影响。我从 a10、e5、i1 和 a1 各试了一遍,每一个仍是和棋,这正是它算堡垒而不是碰巧摆对的原因。马的要求比手册里那句「尽量离将远」宽松:它只要不站在车一步能吃到的点上。在 b7、c7、d7、e7、d6、d4 或 d2,局面守得住。在 d5,也就是和车同一线上,车直接吃掉它,5 步将死。
我那八个局面里,炮全都摆在别处。英文给出的结论本身是对的。「需要良好的防守位置」承担了全部结果,却什么都没告诉你。
车炮对车,其中「中路」不是某一个格子
英文那一节说:「车加炮无法战胜单车,只要防守方的车占住中路。」
这读起来像一条可以核验的规则。我的局面满足它:黑车在中路,在原位,正好挡在本方将前面。Pikafish 28 步将死。
能将死,是因为那句话没提到的那个子。炮吃子要隔正好一个子,所以它需要炮架,而且不管这个炮架是谁的。红炮滑到中路、落在黑车下方立刻叫将,把那个黑车当炮架,把它后面的黑将当目标。防守方自己那个求和的子,就是炮架。
把防守的车沿着这条线往下挪,残局库会在中间划出一条界线:
和棋
红方胜
两个局面里防守的车都占着中路。只有位置较低的那个是和棋。最后的杀法是海底捞月,在这条线的底端完成。
红方按图中摆法,防守的车在第 5 线及以下都能和,搜索深度 40 给出 0.00,在第 6 线及以上则输。就差一线。
红方要取胜,有两件事必须成立,而两件都和红方自己有关。炮必须能落到中路并且在那里活下来,在图中这意味着落在第 5 线,也就是红车正好保护它的那一线。把炮挪离车所在的那一线,挪到第 3 线或第 7 线,那些胜势就全都变回和棋。另外红方需要中路的底端,因为这个残局赖以取胜的杀法,海底捞月,是在下面完成的:车走到中路底端,本方将在其后,防守的车最后被卡在两个将之间,离开这条线就会形成白脸将,留在线上又会被吃。
一个站在自己半边中路上的防守子,这两件事一件都拦不住。占住一条线,说的是一个子控制哪些点。我把它读成了它站在哪个点,造了一个字面上满足那句话的局面,结果 28 步被将死。
马炮对马,搜索深度加到多少都定不下来
英文那一节说:「马+炮+士象全对单小子[加]士象全:若小子是马则胜……若是炮则和。」
十三个子。chessdb 只建过一张这么大的表,而且是另一种十三子组合,对这一种做实时查询返回的是未知。这并不意味着只剩搜索一条路。削减到十子的 KANCKAABBN,也就是给强方留一个士的那个简化,这张表是有的:24,251,768,055 个局面,红方胜率 85.48%。支撑这条结论的是这条简化阶梯,而不是搜索深度。
搜索解决不了它。搜索深度 26 给出 +2.24。深度 42 给出 +2.47,仍然没有将死,而防守方马的六种不同摆位落在 +2.32 到 +3.03 之间,没有一个变成决定性的。一个漫长的技术性胜势,从固定深度看正是这个样子,而一个只是红方占优但和棋的局面,看起来也一样。
不过有两项证据说手册仍然是对的。引擎自己就把这条结论的两半分开了:把防守方的马换成炮,也就是同一句话里判和的那一半,同样的深度给出 +1.26。这个差距就是手册作出的区分,出现在一个从未被告知这件事的数字里。
接着我让 Pikafish 在搜索深度 32 下自己对下这个局面。红方走了 33 着,双方都没有吃子,第 34 着吃了一个士,四着之后吃掉第二个,一直兑换到第 56 着,局面降到十个子,重新进入残局库的范围。黑方走棋,每一种合法应手都是负。
第一次吃子就是全部问题所在,因为有一条国际象棋里没有对应物的规则:自然限着规定双方各走 60 着都无吃子即判和。一个需要 70 着技术才能赢下来的例胜,不是你能下出来的胜。这一局在第 34 着就突破了,还剩 26 着余量。
象棋把另一扇门也关上了,而这一条对国际象棋棋手更致命。长将在这里算负:叫将的一方必须变着,否则判负。在国际象棋里这是把败局救回来的标准手段,所以这种本能不只是帮不上忙,而是直接把分数送出去。这两条规则合起来框住了本文里的每一个残局。防守方不能靠叫将脱身,进攻方也不能无限地磨。两者在国际象棋里都没有可以对译的东西,这正是英文摘要会把它们漏掉的原因。
这仍然不是对这一条的证明。黑方也是 Pikafish,更早一点更好的防守也许能守住;这里没有任何东西排除这一点。它解决的是原本靠我信任来支撑的那部分。最后的胜不是引擎在深度 32 宣布了一个将死、我选择相信,而是一个可被证明为负的局面,所以引擎给出的路线最终落到一个确切的东西上,而不是落到它自己的意见上。引擎仍然拒绝从根局面确认手册的结论。但它现在会走进一个确定的局面。如果你想体会 34 着的挤压有多紧,就在这一条里执守方,而判和的那一半只差一个子。
未解决
十三个子,没有任何残局库覆盖的一条。黑方要在 60 着内不丢子地活下来。
把子力削下来,它就不再是搜索问题。chessdb.cn 公布了这个残局各种简化形式的精确表,而它们改变了手册那句话的含义。把红方自己的士象都去掉,只留将、炮、马对士象全:5,492,247,706 个局面,红方胜 22.31%。给红方还回一个士,就变成 85.48%,覆盖 24,251,768,055 个局面。第二个士再加一个百分点。把这个士换成一个相,则加七个百分点。
所以那句话里进攻方一侧的「士象全」是在实实在在起作用的,而关键数字是一。一个士撑起了胜势,而象几乎是白送的。我是先用 Pikafish 得到这个结果的,在知道这些表存在之前就在搜索深度 30 跑过同一条阶梯,排序结果一样。
换个方向切同一个局面,答案就翻转。两边都保留士象全,改成把防守方的马拿掉:十二个子,10,453,951,719 个局面,chessdb 建了这一张。红方胜 92.71%,最长将死是 69 着,而表里完全没有记录到负。一种简化说胜势靠的是红方自己的士。另一种说,黑方一旦没有可以反击的东西,胜势就近乎彻底。手册真正在说的那组子力就夹在两者之间,不在那 8,705 张表的任何一张里,对它做实时查询返回未知。
六个词,两个筐
上面三条里有两条有一个共同点,而这个共同点是词汇。中文残局手册不会把结果只评为胜或和。它们用一个六级的尺度来评:
| 结论 | 它主张什么 | 本文归类 |
|---|---|---|
| 必胜 | 这组子力在任何局面下都胜 | 胜 |
| 例胜 | 用常规技术即可取胜 | 胜 |
| 巧胜 | 只在特定局面下取胜 | 胜 |
| 难胜 | 难以取胜,这和和棋不是一回事 | 胜或和 |
| 例和 | 用常规技术即可求和 | 和棋 |
| 必和 | 在任何局面下都和 | 和棋 |
六个里有五个能在翻译中留存。第六个必须选一边站,而上面三条里有两条正踩在它上面。
中文条目把车对马炮归在第四级,难胜。英文那一节把它归为「和棋」。残局库站在中文这边:车在棋盘上几乎任何位置都能赢,只被唯一一种防守阵形挡住。读到「和棋」的棋手会随意防守,15 步被将死。读到「难胜」的棋手知道,要找的东西只有一个。
第二条从另一个方向落在同一级上。中文那张表把车炮对单车列为胜/和,然后用一段话讲哪种情况是哪种。英文保留了干脆的那一半,把那段话丢掉了,而那段话才是要紧的部分。
局面才是事实。结论只是有人贴在一整排标签中的一个,把标签翻过来而不带上那一整排,就丢掉了它大部分的含义。
这些结论到底有多可靠
这张表里的结论分三个等级,而它们远不等价。有些是已被证明的:某个数据库把整个残局算完了,答案就是一次查表。有些是搜索出来的:引擎在我跑的那个搜索深度上同意。那是证据,不是证明。其余的是断言,出自一本没有附出处的手册。
分类做了两轮。
我从英文那一节里取出 32 条结论,为每条造一个代表性局面,然后用 Pikafish 在搜索深度 26 下询问。
| 搜索深度 26,32 条结论 | |
|---|---|
| 引擎同意 | 29 |
| 书上说和棋,引擎找到强制取胜 | 2 |
| 书上说胜,引擎赢不下来 | 1 |
然后我用第二种方式再核一遍同样的局面。象棋有云端残局库,是查答案而不是搜答案:chessdb.cn 对子力足够少的局面返回胜、和或负,外加一个步数。我那 32 个里有 27 个符合条件。这个门槛比听起来宽松,因为一个带八个士象的局面,受限程度几乎和只有三个子的局面差不多:士只有五个合法点,象只有七个,而且都不能过楚河汉界。它确实就是查表。沿着它自己给出的最佳路线一着一着走下去,结论不会翻转,步数也不会上升,而任何搜索都做不到这一点。
上面三条里现在有两条带上了残局库确认的防守摆法,而两次改口都是我的,不是手册的。整轮扫查的结果是 32 条里 31 条与 Pikafish 一致、1 条倾向性支持,其中 32 个局面里有 27 个小到能查残局库,而这 27 个全部精确。
核查在我造的局面里抓到了什么
核查抓到的其余问题都是我的。三个不合法的局面,两个将在一条通路上互相照面,解析没问题、看起来也没问题,但在象棋里不合法。两个我标成例和的局面,Pikafish 宣布 2 步将死,结论没错,我的截图是垃圾。还有一个局面本身没问题,但我对它的解释错了。
书上说三个高兵能赢士象全,也就是兵在第 7 线。把同样三个兵往后拉一线,胜势就消失了。我把这写成了先手快慢的问题,差一着差一切。不是的。黑象在整个棋盘上只能站在七个象位上,而从 e8 出发它正好能到 c6、g6、c10 和 g10。第 6 线上的三个兵里,有两个停在象位上。黑方在那里不需要技术,只需要吃一个子。
15 步将死
黑方吃一个
「高」保护的正是象的活动范围。第 7 线上的兵,在整局棋的任何时候、从任何点出发,都不可能被象吃掉。往后拉一线,就有两个站在象位上。
我的局面里有六个是坏的,核查把六个全找出来了。手册的结论里一个坏的都没有。
边界
残局库能定下一个残局,是因为有人把它全部算完了。chessdb.cn 为象棋建了 8,705 张这样的表。没有任何英文页面写明它们的构成,那就写在这里。
| 子数 | 表数 | 局面数 |
|---|---|---|
| 3 | 4 | 18,858 |
| 4 | 36 | 690 万 |
| 5 | 169 | 13 亿 |
| 6 | 615 | 1880 亿 |
| 7 | 1,582 | 7.0 万亿 |
| 8 | 2,494 | 26.1 万亿 |
| 9 | 2,169 | 33.9 万亿 |
| 10 | 1,227 | 23.6 万亿 |
| 11 | 368 | 10.4 万亿 |
| 12 | 40 | 1.0 万亿 |
| 13 | 1 | 304 亿 |
102,230,279,715,348 个局面。磁盘占用 25.56 TiB,把兑子距离表和 DTM(将死距离)表合起来算。数量在九个子时达到峰值,十一个子之后迅速跌落,这就是从内部看到的边界形状。整个语料里最长的强制取胜走 759 着:双马一士对双象马卒。
双方各十三个子、两边士象全都完整,已经越过那张表的最后一行。那就是马炮那一条。手册的例胜,它所谓的「常规取胜」,是对一个没人算过的局面下的结论。
chessdb 唯一建过的那张十三子表是另一个残局,也正是本文开头提到的那个:士象全后面的三个卒,对士象全。30,445,843,365 个局面,象棋里有人解出的最深子力组合,红方胜 48.00%。这个数字统计的是所有摆法,而不是好的摆法,这也正是手册结论和胜率不是同一种主张的原因。
这两个数字都值得和本页顶部那张表摆在一起看。基本残局都是小的那些,而其中大多数已被彻底解决。一旦一条结论描述的是双方满员,它描述的就是从来没有人算过的子力,而手册仍然是唯一的来源。
这 32 条条目,每一行都带来源和出处,都在 xiangqi-endgame-corpus.ts 里。核查程序是 verify-xiangqi-endgames.ts;把它指向一个 Pikafish 可执行文件,它就会把手册结论和引擎读数并排打印出来,附上主要变着。加上 --tablebase,它还会为每一个小到有精确答案的局面补上那个答案。
一个基本残局是一条你可以检验的主张,这正是把它们放到棋盘上而不是放进表格里的全部理由:执书上说必负的那一方,看看你能撑多久。这 32 个都在 Mistboard 上做成了一份研究,一条一章,以该局面为根,引擎的路线作主线,结论写在下面。下得不顺的时候,Pikafish 就在机器人阶梯上以满强度待着,下面还有八个 Fairy-Stockfish 等级,供满强度不再有教益时使用。