大学的时候,我有过一个非常朴素、甚至有点天真的想法:
能不能不用复杂模型、不用大数据,只靠最基础的算法,写一个“会下棋”的 AI?
那时候我当然写不出来。
但这个想法一直没有消失,只是被丢进了一个“以后有时间再说”的角落。
后来学了更多算法、写了更多工程代码,也见过了 AlphaZero、Stockfish 这种“神级存在”。
反而更清楚一件事:
真正重要的不是它有多强,而是它是怎么思考的。
前段时间终于有空,我把当年那个念头拿出来,真的把它写完了:
👉 https://github.com/unknownparticles/chess
这是一个完全本地、基于最基础搜索算法的棋类 AI 引擎。
不追求强度,追求清晰、通用、可扩展。
我一开始就给自己设了三个限制
在动手之前,我给这个项目设了几个“看起来像是在给自己找麻烦”的限制:
- 不用神经网络
- 不用复杂启发式
- 算法要简单到能给大学时的自己讲明白
听起来像是在开倒车,但正是这些限制,让整个项目变得非常“干净”。
我真正想做的是:
👉 把“棋类 AI 的本质”拆到不能再拆。
下棋这件事,本质上是在干嘛?
如果把下棋这件事抽象到极致,其实只有三步:
- 当前局面,我能走哪些步?
- 对方会怎么回应?
- 哪条路最后对我最有利?
这正是 Minimax 算法在做的事情。
它并不聪明,只是非常认真:
- 我假设对手永远做对我最不利的选择
- 那我就提前把这些可能性全算一遍
- 然后选一条“最坏情况下也还不错”的路
你可以把它理解成一种极度悲观,但绝对理性的思考方式。
为什么我要先做一个“通用引擎”
我不想一上来就写“五子棋 AI”或者“象棋 AI”。
我更感兴趣的是一个问题:
AI 下棋的逻辑,和具体是什么棋,其实关系大吗?
答案是:
关系不大。
于是整个项目的核心不是某一种棋,而是一个抽象层:
class GameEngine {
public:
virtual std::vector<Move> getPossibleMoves() = 0;
virtual void makeMove(const Move &move) = 0;
virtual void undoMove(const Move &move) = 0;
virtual int evaluate() = 0;
virtual bool checkWin() = 0;
};
这个接口基本定义了一切“规则棋类”的共性:
- 当前能怎么走
- 走了之后局面如何变化
- 局面对我来说是好是坏
- 游戏有没有结束
AI 并不关心你是在下五子棋,还是井字棋。
只要你能回答这些问题,它就能开始“思考”。
Minimax,其实一点都不神秘
很多人第一次看到 Minimax,会被名字吓到。
但它的逻辑真的非常直白:
-
轮到我 → 我希望分数越大越好
-
轮到对手 → 对手希望分数越小越好
-
一直往下推,直到:
- 游戏结束
- 或者搜索深度用完
核心代码长这样(删掉细节后):
int minimax(GameEngine &engine, int depth, bool maximizing) {
if (depth == 0 || engine.checkWin()) {
return engine.evaluate();
}
if (maximizing) {
int best = -INF;
for (auto &move : engine.getPossibleMoves()) {
engine.makeMove(move);
best = max(best, minimax(engine, depth - 1, false));
engine.undoMove(move);
}
return best;
} else {
int best = INF;
for (auto &move : engine.getPossibleMoves()) {
engine.makeMove(move);
best = min(best, minimax(engine, depth - 1, true));
engine.undoMove(move);
}
return best;
}
}
这里没有魔法,只有一件事非常重要:
makeMove 之后,一定要 undoMove
这是整个引擎能否成立的关键。
一旦状态回滚不干净,AI 的“世界观”就会被污染,后面算出来的结果全部不可信。
为什么 Alpha–Beta 剪枝这么重要?
Minimax 最大的问题只有一个:
太慢了。
棋盘稍微大一点,分支数就会爆炸。
Alpha–Beta 剪枝本质上做了一件很“人类”的事:
“这条路已经比我目前最好的选择还差了,后面就不用看了。”
它不改变结果,只是少走弯路。
加上剪枝之后,搜索深度能明显往上提,这也是为什么哪怕是最基础的算法,在五子棋、井字棋这种规则游戏中已经能表现得“像模像样”。
先从最简单的棋开始
目前项目里实现了两个游戏:
- 井字棋(Tic-Tac-Toe)
- 五子棋(Gomoku)
井字棋几乎是“算法验收工具”:
- 状态空间小
- 最优解明确
- 非常适合验证 Minimax 是否写对
五子棋则更接近真实对弈:
- 分支多
- 评估函数开始变得重要
- 搜索深度和性能开始成为问题
当 AI 能在五子棋里下出“像人一样的棋”,那种感觉非常奇妙。
你会清楚地意识到:
它并没有理解棋,但它理解了规则。
写完之后,我最大的感受
回头看,这个项目并不复杂,甚至可以说“很基础”。
但它让我确认了一件事:
很多我们今天看到的“智能”,本质上仍然建立在这些最朴素的思想之上。
深度学习很强,但搜索和博弈论并没有过时。
复杂系统的理解,往往要从最简单的版本开始。
对我来说,这是一次把“大学时的一个未完成想法”真正关掉的过程。
如果你也对这个方向感兴趣
这个仓库本身并不“完美”,但它非常适合:
- 想真正理解棋类 AI 的人
- 想自己动手实现 Minimax / MCTS 的人
- 想在一个干净框架里加新算法的人
👉 https://github.com/unknownparticles/chess
如果未来我有时间,我会继续往这个引擎里加更多东西。
但即便停在这里,它也已经完成了自己的使命。
4350



被折叠的 条评论
为什么被折叠?



