1.1 博弈与搜索的世界观
大家好,欢迎来到《Anima Chess Engine》——这门课讲的就一件事:怎么写一个会下棋的程序。我们会从最简单的井字棋出发,走到五子棋,最后抵达国际象棋;用到的方法也从最朴素的暴力搜索,一路升级到今天最前沿的神经网络自对弈。
先说一句题外话,帮你把这门课摆进整个项目里。Soma 指的是“有身体的那部分”——会动的机械臂、会走的人形机器人;而 Anima 指的是“脑子里的那部分”——认知框架、工具与引擎。下棋这件事,对手不需要有手有脚,它需要的是一个会思考的大脑,所以它属于 Anima。这就是为什么这套教程叫 Anima Chess Engine。
为什么第一章要从井字棋讲起?因为井字棋小到不可思议——整个游戏所有可能的下法,一台普通电脑一眨眼就能全部数完。正因为它“小到能看得一清二楚”,我们才能用它把后面两章会反复用到的每一个概念和算法,干干净净地讲透一遍,而不被庞大的计算量遮住视线。井字棋是我们的祛魅层:在这里看懂的东西,到五子棋和国际象棋只是“变大”,原理不变。
不过在写第一行代码之前,这一节我们先不碰键盘,而是建立一套世界观和词汇。后面每一节都会用到这里的几个词:什么样的游戏才适合用“搜索”来下?我们凭什么说一个游戏“难”或“简单”?以及一个会让你意外的事实——这些棋其实早就有标准答案了。我们一个一个来。
一、先认识“下棋 AI”到底是什么
1.1 一个下棋程序,其实是三个零件
“会下棋的程序”听起来很玄,但拆开看,它其实只由三个零件组成。我们用一句话先各给一个直觉,再展开:
- 规则引擎:负责“懂规则”——这局面下我能往哪儿走?走完之后棋盘变成什么样?谁赢了、是不是和棋?
- 决策大脑:负责“会思考”——在所有能走的地方里,挑哪一步最好?这是整门课真正的主角。
- 信息接口:负责“能对话”——把它脑子里的判断(这一步多好、有没有杀棋、最佳应对是什么)讲给外面听。
它们之间是这样配合的,你可以顺着箭头走一遍:
当前局面 ──►┌──────────┐ 能走的所有位置 ┌────────────┐──► 选定的那一步
│ 规则引擎 │─────────────►│ 决策大脑 │
│ 懂规则 │◄─────────────│ 搜索 + 评估 │
└──────────┘ 走子后的新局面 └────────────┘
│
▼
┌────────────────┐
│ 开放信息接口 │ ◄── 上层(比如 ANIMA)
│ 局面/评分/威胁 │ 通过它向引擎提问
└────────────────┘
这三个零件会贯穿整门课。规则引擎每换一个棋种就要重写一次(井字棋三行就够,国际象棋的王车易位却要小心伺候);决策大脑是我们花最多力气升级的地方,从 Minimax 一直做到 AlphaZero;信息接口则是一条暗线——我们会让三个棋种的引擎都对外提供一套统一的“问答口”,这样将来 Anima 认知框架就能像调用工具一样问它:“当前局面谁占优?”“有没有几步必杀?”
1.2 “大脑”到底在做什么:把未来推演一遍
这里要先纠正一个最常见的误解。很多人以为“下棋 AI 很强”是因为它“记住了很多棋谱”或者“很有灵感”。其实经典的下棋程序既不靠记忆,也不靠灵感,它靠的是一件很笨但很有效的事:把未来一步步推演出来,然后挑通向最好结局的那一步。
打个比方。你在岔路口犹豫该往左还是往右,于是你在脑子里想:“如果我往左,对手大概会那样应,然后我再……最后我会处于一个好局面;如果我往右,则会走到一个坏局面。”——你做的这件事,就叫搜索(search)。下棋程序干的是同一件事,只不过它能想得又快又深、还不会偷懒。这就是为什么这一章的标题里有“搜索”两个字:对绝大多数下棋 AI 来说,“思考”约等于“搜索未来”。(到第六节我们会见到一种例外的思路,再到第七节又会用神经网络给它“补上直觉”,但那是后话。)
二、让“搜索未来”成立的三个前提
可问题来了:凭什么我们能把未来“摆在桌面上”一步步推演?这其实需要游戏本身满足三个条件。这三个词后面每一节都会出现,我们现在就用大白话把它们认清楚。
2.1 完美信息:桌面上没有暗牌
完美信息(perfect information)的意思是:任何时刻,棋盘上的全部情况双方都看得见,没有任何藏起来的信息。井字棋、五子棋、国际象棋、围棋都是这样——所有棋子明明白白摆在那儿,没有谁手里攥着对方看不到的牌。
反过来,扑克、麻将就不是完美信息:对手的手牌你看不见,你只能猜。一旦有了“猜”的成分,你就没法把未来干净地推演到底,搜索这套打法就会失灵——那是另一个领域(博弈论里叫不完全信息博弈)的故事,不在本课范围内。本课从头到尾只处理完美信息的棋。
2.2 确定性:没有骰子,没有运气
确定性(deterministic)的意思更简单:你走了一步,结果是百分百确定的,不掺任何随机。你把棋子放到某格,它就老老实实在那格,不会有骰子来改写命运。
对照一下你就懂了:飞行棋、双陆棋(backgammon)要掷骰子,同样一步棋,运气好和运气坏后果完全不同——这就不是确定性的。有了随机,“推演未来”就变成了“推演无数个可能的未来再算概率”,复杂度陡增。我们这门课里的棋都没有运气成分,落子即定局,这让搜索踏实多了。
2.3 零和:我的好,就是你的坏
零和(zero-sum)说的是双方利益完全对立:对我越好的局面,对你就越坏,二者加起来是个定值(“零”)。下棋正是如此——没有“双赢”,我赢就是你输,我占优就是你吃亏。
别小看这一条,它是后面 Minimax 算法的灵魂。正因为“我的好就是你的坏”,我才能假设:轮到对手时,他一定会选对我最不利的那一步。于是我推演未来时不必猜对手的心思,只需假设他和我一样聪明、一心想让我难受——这个“最坏打算”恰恰能算出最稳的走法。
把三条合起来记一句话就够了:没有暗牌(完美信息)、没有运气(确定性)、没有双赢(零和)——满足这三条的游戏,我才能把整个未来摊在桌面上推演,搜索这套方法才立得住。本课的三个棋种,全都满足。
三、衡量难度的两把尺子:到底什么叫“难”
既然三个棋种都满足前提,那它们的难度差在哪儿?为什么井字棋能秒解、国际象棋却让全世界的超级计算机都束手无策?要把“难”说清楚,我们需要两把量复杂度的尺子。
3.1 状态空间复杂度:一共有多少种不同的局面
第一把尺子叫状态空间复杂度(state-space complexity):从开局到终局,这个游戏一共能摆出多少种不同的合法棋盘。它衡量的是“这个世界有多大”。井字棋满打满算也就几千种局面,而围棋的合法局面数比可观测宇宙里的原子还多得多。
3.2 博弈树复杂度:一共能下出多少种不同的棋局
第二把尺子叫博弈树复杂度(game-tree complexity),这把尺子才是真正“咬人”的那把。把每一步可能的走法都画成树枝,整棵树的叶子数量,就是从头到尾能下出的不同棋局总数。
为什么是它决定了搜索难不难?因为搜索就是在这棵树上往下走。树的大小大致是“每步的分支数”自乘“棋局的步数”那么多次,写成式子就是 bd(b 是每步的分支因子,d 是要往下看的步数)。这是个指数,所以分支多一点、棋局长一点,数字就会爆炸式地涨。国际象棋平均每步约有 35 种走法、一局约 80 个回合,于是它的博弈树大到约 10123,这个数字已经远超宇宙原子数,根本不可能整棵走完。
下面这张表,把本课三个棋种(外加围棋作参照)放在一起对比。先有个印象就好,具体数字记不住没关系,重要的是感受那种“一个比一个大得吓人”的落差:
| 游戏 | 棋盘 | 状态空间复杂度 | 博弈树复杂度 | 分支因子(约) |
|---|---|---|---|---|
| 井字棋 | 3×3 | ~103(约 5478 个合法局面) | ~105(9! 量级) | 开局 9,递减 |
| 五子棋(15×15 自由规则) | 15×15 | ~10105 | ~1070 | 开局 ~225,递减 |
| 国际象棋 | 8×8 | ~1044 | ~10123 | ~35 |
| 围棋(参照) | 19×19 | ~10170 | ~10360 | ~250 |
看出门道了吗?井字棋的博弈树只有十万量级,普通电脑一瞬间就能整棵走完——这正是它适合当教学起点的原因。而从井字棋到五子棋、再到国际象棋,博弈树一下子跳到天文数字。这门课后面所有“看起来很复杂”的技巧,归根到底都是在回答同一个问题:树大到走不完,我该怎么办?剪枝、评估函数、蒙特卡洛、神经网络……全都是为此而生。
四、Zermelo 定理:这些棋其实都“有标准答案”
4.1 一个让人安心(也有点意外)的定理
这里有个一百多年前就被证明、却常常让人意外的事实。德国数学家策梅洛(Zermelo)早在 1913 年就证明了:任何一个有限的、完美信息的、确定性的双人零和游戏,都存在一个确定的结局——也就是说,在双方都走得完美无缺的前提下,结果必然是下面三者之一,且早已注定:
- 先手必胜,或
- 双方必和,或
- 后手必胜。
注意这里的前提,正是我们第二节认清的那三个词。换句话说,井字棋、五子棋、国际象棋、围棋,理论上都有一个“标准答案”:要么先手能稳赢,要么注定和棋,没有第四种可能。这听起来是不是有点反直觉?我们待会儿就用它来解释一个常被问到的问题:既然有标准答案,为什么大家还在下棋?
4.2 “已解(solved)”到底是什么意思
关键的分寸在这儿:“理论上存在答案”和“我们真的把答案算出来了”,是两码事。一个游戏,只有当我们真的找出了那套完美策略,才说它被“解出了(solved)”。Zermelo 只保证答案存在,可没保证我们算得动——而能不能算得动,恰恰由上一节那两把尺子(尤其是博弈树有多大)决定。
于是本课三个棋种就分成了泾渭分明的两类,请看这张表:
| 游戏 | 是否已解 | 标准答案 | 谁解的 / 为什么没解出 |
|---|---|---|---|
| 井字棋 | ✅ 已解 | 必和(双方不犯错则平局) | 树太小,随手就能穷举 |
| 五子棋(自由规则) | ✅ 已解 | 先手必胜 | Allis 于 1994 年用威胁空间搜索证明 |
| 连珠(带禁手) | ✅ 已解 | 先手(黑)仍必胜 | Wágner & Virág 于 2001 年解出 |
| 国际象棋 | ❌ 未解 | 未知(很可能是和棋) | 博弈树 ~10123,算不动 |
| 围棋(参照) | ❌ 未解 | 未知 | 博弈树 ~10360,更算不动 |
这张表其实就是整门课的剧本梗概:第二章的五子棋虽然“理论上先手必胜”,但人类根本下不出那套完美解,所以我们造的引擎照样大有可为;第三章的国际象棋干脆连答案都不知道,于是只能去逼近最佳着法,这也正是它需要更聪明的评估与搜索的原因。
顺带回答那个常见疑问——“既然有标准答案,为什么还有人玩?”因为“有答案”不等于“人能下出答案”。井字棋的答案人人都能掌握,所以它确实没人当真比;可五子棋、国际象棋的完美解复杂到没有任何人类能凭脑子执行,于是实战里照样是真刀真枪的博弈。一个游戏会不会因为“被解出”而失去乐趣,取决于人能不能亲手把那个解走出来。
五、小结与下一节
这一节我们没写代码,但搭好了贯穿整门课的脚手架。临走前把核心拎成三组,方便你日后回看:
- 下棋程序 = 三个零件:规则引擎(懂规则)+ 决策大脑(会思考,本质是搜索未来)+ 信息接口(能对话)。
- 搜索成立的三个前提:完美信息(无暗牌)、确定性(无运气)、零和(无双赢)——本课所有棋都满足。
- 两把尺子 + 一个定理:状态空间与博弈树衡量“难”,其中博弈树的爆炸是一切技巧的根源;Zermelo 定理保证答案存在,但“已解”与否取决于我们算不算得动。
道理铺垫够了,是时候动手了。下一节 1.2 把井字棋写成代码,我们就把上面说的“规则引擎”真正写出来:用几十行 Python 表示棋盘、生成合法走子、判定胜负,并搭起那条贯穿全课的“开放信息接口”雏形。准备好,我们开始造第一个零件。