象棋基本残局:哪些结论已确定,确定到什么程度
摘要
- 我把英文和中文维基百科文章里的 32 条象棋基本残局结论做成了局面,并用 Pikafish 和 chessdb.cn 的残局库逐一核对。32 个局面都可以在一份研究里对弈。
- 棋谱的结论站得住:32 条里有 31 条与引擎一致,剩下一条也倾向同一结果。有六个局面是错的,全都是我自己摆错的。
- 有三条结论取决于英文里省略掉的一个摆位。车对马炮只有在炮正好位于自己将的后方时才和棋;往旁边挪一格,就是 12 步将死。
- 数据库覆盖了 8,705 类残局、102 万亿个局面。再往上,当双方士象俱全时,棋谱仍是唯一的依据。
国际象棋有一张人人都学过的基本残局表。王车必胜。王双马无法强制取胜。王单象是死局。象棋也有同样的一张表,如果你读得懂中文,学法也一样,来自那些结论彼此一致的残局棋谱。用英文,你只能拿到碎片。
最好的那块碎片是英文维基百科象棋条目里的残局一节。三十多条结论,用散文写出,没有一条附注来源。中文那边对应的是一篇叫象棋胜和定式(象棋勝和定式)的条目。它讲的是同样的内容,每条结论配一幅图,并给出了两本书:金启昌与杨典的《象棋实用残局胜和定式》(北京体育大学出版社,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 条结论,为每条摆出一个代表性局面,然后在深度 26 问 Pikafish。
| 深度 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,把转化距离表和将死距离表合起来算。数量在九个子时达到峰值,过了十一个子迅速下降,这就是从内部看边界的样子。整个语料里最长的强制取胜是 759 步:双马一士对双象马兵。
每方十三个子、双方士象俱全,已经超出那张表的最后一行。那就是马炮那一条。棋谱的例胜,它那个「按常规技术取胜」,是对一个无人计算过的局面下的结论。
chessdb 建成的那唯一一张十三子表是另一个残局,也正是本文开头讲的那个:三个兵加士象全,对士象全。30,445,843,365 个局面,是象棋里被解决过的最深子力,红方胜其中 48.00%。这个数字统计的是所有摆法而不是好的摆法,这也正是棋谱结论和胜率不是同一种主张的原因。
这两个数字都值得跟本页顶部那张表并在一起看。基本残局都是小的那些,其中大部分已被彻底解决。一旦某条结论描述的是双方都满装的情形,它描述的就是从未被任何人计算过的子力,而那里棋谱仍是唯一的依据。
这 32 条条目,每一行都带出处和来源,放在 xiangqi-endgame-corpus.ts 里。核查工具是 verify-xiangqi-endgames.ts;把它指向一个 Pikafish 可执行文件,它就会把棋谱结论和引擎读数并排打印出来,附上主要变着。加上 --tablebase,它还会为每个小到有精确答案的局面附上那个答案。
基本残局是一种你可以检验的主张,这正是把它们放到棋盘上而不是放进表格里的全部理由:执棋谱判为负的那一方,看看你能坚持多久。全部 32 个都在 Mistboard 上做成了一份研究,每个一章,以该局面为根,引擎的着法序列作为主线,结论写在下面。当这件事进行得不顺时,Pikafish 就在机器人阶梯的顶端满强度待着,下面还有八级 Fairy-Stockfish,留给满强度不再有教学意义的时候。