上篇讲了搜索,引擎已经"会下棋"。但搜索只负责"看得远",给局面打多少分才是棋力天花板——这叫评估函数(evaluation)。这篇讲我在这块做的事,以及一个对工程师很有价值的结论:一个跑得更快、看起来更聪明的评估改动,未必让棋力变强;唯一裁判是等时限的 A/B 对弈。
一、评估函数长什么样
evaluateBoard(Events.php:2777)给当前局面算一个分。基础是子力价值,且分阶段:
// Events.php:2786 —— 同一棋子在开局/中局/残局价值不同 '兵' => [10, 15, 20], // 残局兵过河升值 '馬' => [30, 35, 40], // 残局马优于炮 '車' => [50, 60, 70], // 车永远是大子 // 阶段由总子数判定:<=20 中局,<=10 残局(:2812)
再加位置表(PST):同样是车,占肋道比占边线值钱。PST 让 AI 懂得"占位"而不只是"吃子"。
二、我做的三件事
按"评估结构改造"的思路,我依次落地了三块:
- 子力动态化 + PST 分阶段插值(tapered eval,FIX-27):不用"开局/中局/残局"三段跳变,而是用连续的
gamePhase在两端值之间线性插值,避免阶段切换时评估值突跳; - 残局知识(FIX-26):残局将帅活性加成、通路兵按阶段缩放;
- 残局库(FIX-28/29):对"光将残局"这类铁律棋例直接给确定结论,跳过搜索。
前两块默认关,第三块默认开——为什么?往下看。
三、唯一裁判:等时限 A/B 对弈
关键方法:固定深度(d3)、每步清置换表/killer/history(避免跨步污染)、唯一变量是被测开关,6 个开局 × 换先 = 12 局,步数上限判和,根节点返回 null 视为认输。这样测出来的是"同一算力下,开这个开关到底赢没赢"。
结果:
| 改动 | 12 局战绩(胜/负/和) | 净分 | 结论 |
|---|---|---|---|
| 残局知识 FIX-26 | 3 / 5 / 4 | 净负 | 默认关 |
| 分阶段插值 FIX-27 | 3 / 3 / 6 | 持平(净 0) | 默认关 |
| 残局库 FIX-28/29 | 正确性特性,非对比 | — | 默认开 |
FIX-26 净负:残局活性/通路兵加成让 AI 在实战里更"贪"局部、反而漏了大局,12 局输多赢少。 FIX-27 持平:插值方向是对的(消除了阶段跳变),但没有带来独立棋力增益,净分恰好为 0。
这俩的教训高度一致:不保正确的启发式,不能用"跑得快/看起来合理"证明收益。唯一判据是等时限评委分。
四、残局库为什么默认开(以及它的翻车)
残局库是正确性特性,不是"启发式调优":像"单车对光将必胜""光将对光将和棋"是铁律,命中就短路返回确定分,无副作用。所以默认开。但它的第一版(FIX-28)带着一个严重 bug,而且是静默上线的。
bug 长这样
初版兜底逻辑是"判不出胜负就返回 0(和棋)"。问题:对双方都还有子的正常中局局面,它也被误判成"和棋(0 分)"——结果满盘评估恒为 0,整个评估函数失效,AI 等于闭着眼睛乱走。根因是"正常局面"和"光将残局"被同一个兜底混在一起了。
修法(FIX-29)
只让光将残局短路返回具体分;双方都还有子一律返回 null,走通用评估。并补了回归断言:非对称正常局面必须走通用评估,绝不能误判和棋。扩展覆盖:車+士 / 車+象 / 双車 vs 光将必胜,車 vs 双士 / 双象和棋(isChariotVsAdvisorsDraw,Events.php:2748)。tablebase_check.php 扩到 18/18。
// Events.php:2732 —— 修复后的关键分支 } else { // 双方都还有子:仅「单车对士象联防」判和,其余一律走通用评估 if (self::isChariotVsAdvisorsDraw($counts)) { return 0; } return null; // ← 修复点:之前这里误返 0 }
教训:连"正确性特性"也必须有"正常局面走通用评估"的回归断言,否则一个 bug 会静默把整盘棋变成乱走,而测试因为核心回归关了开关没覆盖到。
五、还有两个方法论细节
- 战术题库当回归守卫:8 道"必须一步杀/必须解杀"的题,引擎基线 8/8 满分。任何评估改动先跑它,扣分就说明把战术嗅觉搞坏了——它查的是"评估没变傻",不查"变聪明了"。
- 跨语言概念不能照抄:象棋五兵天然全是"孤兵",国际象棋那套"孤兵罚分"照搬过来会误罚正常开局;象棋无兵升变,国际象棋的 KPK 升变表也不适用。改评估前先验证规则前提。
六、写在最后
这个象棋 AI 的棋力,今天不算顶尖,但每一行都是踩过坑、量过收益留下来的:搜索负责"看得远",评估负责"看得准",而A/B 对弈负责证明前面的改动真的有用。对个人项目来说,这种"不写扛不住的虚东西、每个改进都能量化"的纪律,比多撑 100 分棋力更值钱——毕竟它要写进简历,得经得起追问。
(系列完。源码在 api/plugin/webman/gateway/Events.php,测试在 api/tests/。)