电脑会对暗子可能是的每一种棋子取平均

电脑考虑一步翻子时,会对暗子可能是的每一种棋子各走一次这步棋,分别打分,再按每种棋子剩余的数量加权平均。一步翻子如果翻出车就赢、翻出兵就被将死,分数应该介于两者之间。两个错误破坏了这个平均。

对黑方来说,平均取了最好的那个棋子

取平均之前,代码会把每个分数换算到某一方的视角。它根据一个从未被赋值的设置来判断是哪一方,所以总是当成红方。对红方的翻子,这样没有问题。对黑方的翻子,分数就是反的:一条本该在结果相差很大时取最差结果的规则,取了最好的结果。

AB-JChess 那篇文章最初开头用的就是这盘棋。皮卡鱼执黑,把 d10 的暗子翻开走到 e9,自评领先将近 19 个兵。只有 e9 上是士,才能挡住车在 f10 的将杀,而在它可能是的 11 个棋子里,士只有 1 个。翻出来是车,黑方下一步就被将死。

被搜索中途放弃的分数,被当成精确值取了平均

引擎为了节省时间,一旦知道某步棋不如目前最好的一步,就会放弃继续搜索它,返回一个界限,意思是“最多这么多,或者更差”。如果接下来只是比较大小,这样做是安全的。但这个电脑把这些界限当成精确分数放进了平均,于是一个会被将死的棋子可能返回“不超过 +7”,然后就按 +7 计算。

在 AB-JChess 那篇文章的第一盘棋里,电脑执红,把 b3 的暗子翻开走到 d3。用旧版电脑重新搜索,这步棋评为红方领先八个兵以上。这个棋子十次里有六次是兵或相,黑炮立刻在 h1 将死。如实平均的话,这步棋对黑方来说价值大约三个兵。修复后的电脑看到了这个将杀,改走仕从 e2 到 f3,也就是 AB-JChess 推荐的着法。

错误在代码里的位置

两个错误都在我们复制的皮卡鱼揭棋分支里。简化后,旧代码是这样给翻子打分的:

# Before (simplified)
for piece in pieces_it_could_be:
    # cut short, this returns the window's edge
    s = -search(position_with(piece), window)
    # set from a field nobody fills in
    if mover_is_black:
        s = -s
    scores.add(s, weight = count[piece])
v = weighted_average(scores)
# for Black, "min" was really the best
if v - min(scores) > MAX_SPREAD:
    v = min(scores)

颜色翻转在 misc.h 第 360 到 366 行,由 search.cpp 第 1310 行设置:它判断走棋方是不是“先手方”,而这个字段从来没有被赋值。窗口问题在 search.cpp 第 1462 行:每种棋子都在上一层着法的窗口内搜索,一个会被将死的棋子返回的是窗口的下沿。

我们通过打印翻子时每种棋子的分数找到了第二个错误。在一盘对抗赛对局里,电脑可能翻出的四种棋子中有三种会被将死,而这三种返回的都恰好是 738,也就是窗口的下沿。第四种得 792。电脑把它们平均成 745,把这步棋评为胜势。

# After
for piece in pieces_it_could_be:
    s = -search(position_with(piece), window)
    # only a bound: search again for the score
    if at_root and s <= window.low:
        s = -search(position_with(piece), no_lower_limit)
    scores.add(s, weight = count[piece])
v = weighted_average(scores)
if v - min(scores) > MAX_SPREAD:
    v = min(scores)

颜色翻转已经去掉,返回界限值的棋子会重新搜索。全部改动是一次提交,28 行。

修复后的电脑对翻子的估值接近实际,棋力也基本不变

在 AB-JChess 对抗赛中决定皮卡鱼输棋的 71 步里,旧版电脑把其中 16 步评得比实际好至少五个兵。修复后的电脑只有 3 步。它的棋力和旧版差不多,也许略弱一点:快棋 600 盘,它赢 264 盘、输 300 盘;每步搜索量加到四倍后对下 200 盘,它赢 95 盘、输 97 盘。

修复只覆盖电脑正在选择的那一步。在更深的搜索里,旧的平均方式依然存在,因为在所有地方都修复会让电脑弱大约 150 Elo。

AB-JChess 也有第二个错误,但很少有影响

AB-JChess 基于皮卡鱼,也用同样的方式对翻子取平均。它有搜索中途放弃的问题,没有颜色的问题。在决定它输给我们电脑的 60 步翻子中,它只高估了 2 步。

一个关于一步棋的问题发现了它

AB-JChess 那篇文章最初用第 108 盘来说明皮卡鱼如何判断翻子。发现这个错误的,是一个问题:电脑到底为什么会走 d10-e9?正确的搜索不可能在自己的最佳变化里看到将杀,还把局面评为胜势。我们当天就把那些例子从那篇文章里删掉了。

挑战揭棋电脑揭棋规则