← 返回博客
算法2026-08-30 21:23:425 分钟 · 1,334 3

写一个会下中国象棋的 AI(下):当"改进"被 A/B 对弈证伪

评估函数才是棋力天花板。我做了子力动态化、分阶段插值(tapered eval)、残局库三件事,却用 A/B 对弈发现两个"改进"其实让棋力变弱。附真实对弈数据与一个静默上线的严重 bug 教训。

上篇讲了搜索,引擎已经"会下棋"。但搜索只负责"看得远",给局面打多少分才是棋力天花板——这叫评估函数(evaluation)。这篇讲我在这块做的事,以及一个对工程师很有价值的结论:一个跑得更快、看起来更聪明的评估改动,未必让棋力变强;唯一裁判是等时限的 A/B 对弈。

一、评估函数长什么样

evaluateBoardEvents.php:2777)给当前局面算一个分。基础是子力价值,且分阶段

// Events.php:2786 —— 同一棋子在开局/中局/残局价值不同
'兵' => [10, 15, 20],   // 残局兵过河升值
'馬' => [30, 35, 40],   // 残局马优于炮
'車' => [50, 60, 70],   // 车永远是大子
// 阶段由总子数判定:<=20 中局,<=10 残局(:2812)

再加位置表(PST):同样是车,占肋道比占边线值钱。PST 让 AI 懂得"占位"而不只是"吃子"。

二、我做的三件事

按"评估结构改造"的思路,我依次落地了三块:

  1. 子力动态化 + PST 分阶段插值(tapered eval,FIX-27):不用"开局/中局/残局"三段跳变,而是用连续的 gamePhase 在两端值之间线性插值,避免阶段切换时评估值突跳;
  2. 残局知识(FIX-26):残局将帅活性加成、通路兵按阶段缩放;
  3. 残局库(FIX-28/29):对"光将残局"这类铁律棋例直接给确定结论,跳过搜索。

前两块默认,第三块默认——为什么?往下看。

三、唯一裁判:等时限 A/B 对弈

关键方法:固定深度(d3)、每步清置换表/killer/history(避免跨步污染)、唯一变量是被测开关,6 个开局 × 换先 = 12 局,步数上限判和,根节点返回 null 视为认输。这样测出来的是"同一算力下,开这个开关到底赢没赢"。

结果:

改动12 局战绩(胜/负/和)净分结论
残局知识 FIX-263 / 5 / 4净负默认关
分阶段插值 FIX-273 / 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 双士 / 双象和棋(isChariotVsAdvisorsDrawEvents.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/。)

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/158