五子棋 AI(Gomoku)
和 Rust + WebAssembly 实现的 α-β 剪枝 AI 下一盘
🖱️ 点击 / 👆 轻点 交叉点落子 | 先连成五子(横、竖、斜均可)者获胜
📖 原理说明
五子棋规则简单,却足以展示博弈 AI 的核心思路:「如果我走这里,对手最好的应对是什么,我再怎么应对……」把这棵走法树往下展开几层,在叶子上给局面打分,再一层层往回推,就能选出当前最好的一步。这个 AI 用 Rust 实现了带 α-β 剪枝的搜索,每一步都在浏览器里实时计算。
🧮算法原理
score(n) = maxm −score(child(n, m))负极大值(negamax):对手的最好就是我的最坏
- 局面评估:五元组
- 棋盘上所有连续 5 格的窗口共有 572 个。只含一方棋子的窗口按子数计分(1 子 1 分、2 子 12 分、3 子 150 分、4 子 2000 分),双方都有子的窗口已经不可能连成五,记 0 分。一条活四会同时出现在两个「4 子窗口」里,所以自然比冲四分高;「XX_X」这样中间有空的棋型也能被识别。
- α-β 剪枝
- 搜索时记住「我方至少能拿到的分」α 和「对手最多允许的分」β。一旦发现某个分支已经不可能比已知的更好,就不再展开它的其余走法。走法排序越好,剪掉的越多,同样时间能搜得更深。
- 候选走法
- 只考虑已有棋子周围 2 格内的空位,并按「进攻价值 + 防守价值」排序,只展开最好的 12 个。这样每层分支数从两百多降到 12,4 层搜索也只需要几万个局面。
- 战术优先
- 搜索之前先检查两件事:自己有没有一步就能连五的点(有就直接下),对手有没有(有就必须堵)。更复杂的战术(比如堵活三、做双三)交给搜索去发现。
🔄Rust 与 JavaScript 的分工
- JS点击棋盘时,JS 把像素坐标换算成交叉点,调用
gomoku_play(x, y),Rust 检查是否合法、是否连成五子。
- Rust轮到 AI 时,JS 调用
gomoku_ai_move(深度)。Rust 在棋盘副本上做 α-β 搜索,返回落点、搜索过的局面数和评分。
- JSJS 先让浏览器画出玩家的棋子,再开始计算(搜索会占用主线程),然后落下 AI 的棋子,并高亮最后一手或获胜的五子。
⚡性能要点
- 难度对应搜索深度。每多搜一层,局面数大约乘以有效分支数(剪枝后通常只有 3~5),所以 5 层比 3 层慢一个数量级左右。底部会显示每一步实际搜索的局面数和耗时。
- 实测(Node 中运行同一个 WASM,AI 自我对弈 30 手):深度 3 每步约 1ms,深度 4 中位数 4ms,深度 5 中位数 9ms、最慢 23ms,最多搜索约 5,800 个局面。搜索在主线程同步运行,这个量级不会让页面卡顿;如果继续加深,可以把搜索放进 Web Worker。
- 这是无禁手规则:黑棋先手有明显优势,长连(6 子以上)也算获胜。
源码crates/games/gomoku/src/lib.rswww/gomoku/index.js