GOMOKU
选择游戏模式
评分算法 + 随机选择
启发式评估函数
Minimax + Alpha-Beta 剪枝
博弈论的数学基础可追溯到 1928年 冯·诺依曼(John von Neumann)的极小化极大定理。1950年代,克劳德·香农(Claude Shannon)首次提出计算机下棋的理论框架。
趣味知识:1997年,IBM的"深蓝"击败国际象棋世界冠军卡斯帕罗夫,每秒可评估2亿个棋局位置。而我们的五子棋AI在你的浏览器中运行,每秒约评估数万个位置!
五子棋属于二人零和完全信息博弈:
其中 V 表示局面价值
评分算法是最直观的AI策略,源于人类棋手的经验总结。早在1970年代的早期电脑游戏中就已广泛使用,至今仍是许多棋类AI的基础。
为棋盘上每个空位计算一个分数,分数越高表示该位置越有价值。分数由两部分组成:
p = 位置坐标
Attack(p) = 进攻价值(我方在此落子的收益)
Defense(p) = 防守价值(阻止对手在此落子的收益)
α, β = 权重系数(中等模式:α=1.1, β=1.0)
| 棋型 | 形态 | 分值 |
|---|---|---|
| 连五 | ●●●●● | 1,000,000 |
| 活四 | ○●●●●○ | 100,000 |
| 冲四 | ●●●●○ 或 ●●●○● | 10,000 |
| 活三 | ○●●●○ | 5,000 |
| 眠三 | ●●●○○ | 500 |
| 活二 | ○●●○ | 200 |
| 眠二 | ●●○○ | 50 |
● = 棋子,○ = 空位
A B C D E F G
7 · · · · · · ·
6 · · · ● · · ·
5 · · ● · · · ·
4 · ● · · ○ · ·
3 · · · · · · ·
2 · · · · · · ·
假设黑棋(●)考虑在 D5 位置落子:
简单模式的秘密:为什么简单模式更"笨"?它从得分最高的前3个位置中随机选择,而不是总选最高分。这模拟了人类"手滑"的情况!
1928年,数学家冯·诺依曼证明了极小化极大定理,奠定了博弈论的数学基础。该算法成为AI博弈领域的基石,几乎所有棋类AI都基于此原理。
历史时刻:1997年国际象棋、2016年围棋(AlphaGo)、2019年德州扑克——AI在复杂博弈中一次次超越人类,Minimax的变体始终是核心组件。
假设双方都是完美理性的:
s = 当前状态,s' = 所有可能的后继状态
[MAX] 根节点 (AI选择)
│
┌────────────┼────────────┐
▼ ▼ ▼
[MIN] [MIN] [MIN] (对手选择)
│ │ │
┌──┴──┐ ┌──┴──┐ ┌──┴──┐
▼ ▼ ▼ ▼ ▼ ▼
[3] [5] [2] [9] [1] [6] (叶子节点评分)
MIN层选择: 3 2 1
MAX层选择: max(3,2,1) = 3
AI会选择左边分支,因为即使对手最优应对,AI也能保证得分≥3
b = 分支因子(每个节点的子节点数)
d = 搜索深度
五子棋:b ≈ 200(空位数),d = 2 → 约40,000个节点
1958年,Allen Newell 和 Herbert Simon 在研究国际象棋程序时首次描述了这一优化技术。1975年,Donald Knuth 和 Ronald Moore 对其进行了严格的数学分析。
在Minimax搜索中,很多分支是不必要探索的——因为无论结果如何,都不会影响最终决策。
[MAX] α=-∞
│
┌────────────┼────────────┐
▼ ▼ ▼
[MIN] [MIN] [MIN]
β=+∞ β=3
│ │
┌──┴──┐ ┌──┴──┐
▼ ▼ ▼ ▼
[3] [5] [2] ✂️ ← 剪枝!
↑
发现2 < α(=3),后续无需搜索
第二个MIN节点发现值为2,小于已知的α=3,无论后续节点值如何,MAX都不会选择这条分支。
在最优情况下,搜索效率提升至平方根级别!
原本需要40,000节点 → 优化后约200节点
优化技巧:本游戏还使用了"候选位置筛选"——只考虑得分最高的15个位置,进一步将分支因子从200+降到15!
对每个位置,AI在四个方向上扫描棋型:
function evaluateLine(position, direction):
count = 1 // 连续棋子数
block = 0 // 被封堵的端点数
// 正向扫描
for i = 1 to 4:
next = position + direction * i
if next是己方棋子: count++
else if next是空位: break
else: block++; break
// 反向扫描
for i = 1 to 4:
next = position - direction * i
// ... 同上
// 返回棋型分数
return getScore(count, block)
💡 选择困难模式,与Alpha-Beta剪枝算法一决高下吧!