Files
Gobang-Game/docs/superpowers/specs/2026-05-31-gobang-ai-upgrade-design.md
T

8.9 KiB
Raw Blame History

Gobang AI 升级设计文档

状态: 待审核 | 日期: 2026-05-31

目标

将当前基础 Alpha-Beta AI 升级为专业级五子棋 AI,具备迭代加深、置换表、组合棋形评估、VCF/VCT 杀棋搜索和开局库。

架构变更

当前:                             升级后:
AlphaBetaAi                       AlphaBetaAi
  └─ negamax()                      ├─ iterative_deepening()  ← 新增:逐层加深 + 时间控制
                                    ├─ negamax()              ← 改进:加 TT + killer
                                    │    ├─ evaluate_board()  ← 改进:组合棋形 + 位置权重
                                    │    └─ tt: TransTable    ← 新增:置换表缓存
                                    ├─ vcf_search()           ← 新增:连续冲四取胜搜索
                                    ├─ vct_search()           ← 新增:连续活三取胜搜索
                                    └─ opening_book           ← 新增:开局定式库

一、迭代加深 + 时间控制

原理

不再固定搜索 depth=N,而是从 depth=1 开始,每轮加深 1 层,直到时间用尽。每轮完成后保存 best_move,超时时返回上一轮的结果。

实现

best_move(board, color):
    start_time = now()
    time_limit = 根据难度映射: [1,2,3,5,8] 秒 → level 1~5
    best = center_position

    for depth in 1..=MAX_DEPTH:
        result = negamax_root(board, depth, color)
        if 搜索完成没超时:
            best = result.best_move
        else:
            break  // 超时,返回上轮 best

    return best

时间分配策略

  • 每层完成后检查是否剩余时间 < 本层耗时 × 1.5,如果是则不再加深
  • 防止"刚开搜就超时"的无效搜索

难度→时间映射

Level 时间上限 depth 上限
1 1s 4
2 2s 6
3 3s 8
4 5s 12
5 8s 20

二、置换表 (Transposition Table)

原理

五子棋中不同走子顺序可能到达同一局面。用 Zobrist 哈希为每个局面生成唯一 key,缓存搜索结果。下次遇到相同哈希直接查表,避免重复搜索。

数据结构

struct TransTable {
    entries: Vec<Option<TTEntry>>,  // 2^N 大小,N=20 → 约 100 万条目
    size_mask: u64,
}

struct TTEntry {
    hash: u64,           // 完整哈希(防冲突)
    depth: u8,           // 搜索深度
    score: i32,          // 局面评分 (转为整数)
    bound: BoundType,    // Exact / LowerBound / UpperBound
    best_move: Option<Position>,
}

Zobrist 哈希

// 初始化:全局二维随机数表
// zobrist[color][x][y] = random_u64()
// 局面哈希 = XOR 所有棋子的 zobrist[color][x][y]
// 落子时增量更新:hash ^= zobrist[color][x][y]
// 不提子所以不需要 undo 操作,直接用 hash ^=

查表/存表

negamax(board, depth, alpha, beta, color):
    hash = board.zobrist_hash
    // 查表
    if let Some(entry) = tt.probe(hash, depth):
        if entry.depth >= depth:
            match entry.bound:
                Exact    => return entry.score
                LowerBound => alpha = max(alpha, entry.score)
                UpperBound => beta  = min(beta,  entry.score)
            if alpha >= beta: return entry.score

    // ... 正常搜索 ...

    // 存表
    if score <= alpha_orig: bound = UpperBound
    elif score >= beta:     bound = LowerBound
    else:                   bound = Exact
    tt.store(hash, depth, score, bound, best_move)

配置

  • 表大小:2^20 条目 ≈ 32MB(每条目 ~32 bytes
  • 替换策略:深度优先(depth 深的覆盖 depth 浅的)

三、组合棋形评估

问题

当前评估单方向扫描,无法识别"一个方向活三 + 垂直方向活三 = 必胜威胁"的组合。

改进:多方向特征向量

struct PositionFeatures {
    // 四个方向 (水平、垂直、对角线1、对角线2) 各自的最大棋形
    max_pattern: [Pattern; 4],       // Five/OpenFour/RushFour/OpenThree/...
    combo_score: f64,                // 组合加分
    position_bonus: f64,             // 位置权重
}

enum Pattern {
    Five, OpenFour, RushFour, OpenThree, SleepThree,
    OpenTwo, SleepTwo, OpenOne, Empty,
}

组合评分规则

组合 加分 说明
活三 + 活三 (交叉方向) 5000 必胜形
活三 + 冲四 10000 近似必胜
冲四 + 冲四 8000 双重威胁
活三 + 活二 500 发展优势

位置权重

position_score(x, y, board_size) =
    基础距离分: 离中心越近越高 (高斯分布)
    + 边缘惩罚: 边缘 2 行内下降 50%
    + 星位偏好: 标准星位额外 +5%

位置权重占总评分的 ~5%,主要影响开局和中盘。


四、杀棋启发 (Killer Move Heuristic)

原理

记录每层深度中触发 Beta 剪枝的走法。同一层深的相似局面,上次有效的走法这次也优先搜索。

数据结构

// 每层存 2 个 killer move
killer_moves: [[Option<Position>; 2]; MAX_DEPTH]

候选排序优先级

  1. 置换表中的 best_move
  2. killer_moves[depth]
  3. 能立即五连的走法
  4. evaluate_board 预评分排序的其余走法

五、VCF/VCT 杀棋搜索

VCF (Victory by Continuous Fours)

连续冲四取胜。当一方有冲四时,对手必须堵,我方继续冲四,直到五连。

vcf_search(board, color, depth_limit=10):
    if check_win: return Some(win_path)

    for each rush_four position for `color`:
        board.place(pos)
        opponent_blocks = forced_block_positions(board)  // 对手只有一种堵法
        if len(opponent_blocks) == 1:
            board.place(opponent_block)   // 对手被迫堵
            result = vcf_search(board, color, depth-2)
            if result: return Some(pos + result)
        board.undo(2)

    return None  // 无必胜序列

VCT (Victory by Continuous Threats)

类似 VCF 但目标棋形更宽(冲四 + 活三),搜索更深。

  • VCF:仅搜索连续冲四序列,depth ≤ 10
  • VCT:搜索冲四/活三混合序列,depth ≤ 15

触发时机

best_move 中:

  1. 先跑 VCF/VCT 浅搜索(depth=6
  2. 如果找到必胜序列 → 直接返回第一步
  3. 否则 → 正常 Alpha-Beta 搜索
  4. 如果 AB 搜索发现对手有威胁 → 防御模式,优先堵 VCF/VCT 路径

六、开局库

格式

struct OpeningBook {
    // hash → [best_moves]
    positions: HashMap<u64, Vec<Position>>,
}

// 初始化时从内置数据加载
fn load_opening_book() -> OpeningBook {
    // 内置 50 个常见开局定式
    // 花月、浦月、云月、雨月、溪月、金星、水月、新月 ...
}

数据来源

内置 50 个标准五子棋开局定式,覆盖前 3~7 手。直接从专业棋谱提取坐标序列,编译期嵌入。

使用逻辑

best_move():
    if 总手数 < opening_book_threshold:  // 前 7 手
        if let Some(moves) = opening_book.lookup(board.hash):
            return random_choice(moves)  // 从定式中随机选一个变招

    // 超过开局阶段,正常搜索
    return iterative_deepening(...)

七、文件变更

文件 操作 内容
core/Cargo.toml rand 依赖(开局随机选择),加 fxhash(快速哈希)
core/src/ai/mod.rs AiEngine trait 不变
core/src/ai/search.rs 重写 迭代加深 + TT + killer + VCF/VCT 入口
core/src/ai/evaluate.rs 重写 组合棋形 + 位置权重
core/src/ai/trans_table.rs 新建 置换表实现 (Zobrist + HashMap)
core/src/ai/killer.rs 新建 Killer move 表
core/src/ai/vcf.rs 新建 VCF/VCT 杀棋搜索
core/src/ai/opening.rs 新建 开局库 (50 定式)
core/src/board.rs Board 加 zobrist_hash 字段,place/undo 增量更新
core/src/types.rs 加 Zobrist 相关类型
gui/src/commands.rs new_game 适配新的 AI 参数(时间上限替代 depth)

八、测试策略

模块 测试
Zobrist 哈希 落子后哈希变化、对称局面哈希不同、undo 后恢复
置换表 存/查/同局面命中、depth 优先级替换
组合棋形 单方向评分不变、交叉活三加分、冲四+活三加分
位置权重 中心>边缘、对称位置权重相同
Killer 插入、查询、满容量替换
VCF 已知必胜序列被找到、无解返回 None
开局库 lookup 已知局面、未知局面返回 None
迭代加深 超时返回有效 move、时间限制内完成
AI 回归 原有 3 个 AI 测试仍然通过

九、不做 (YAGNI)

  • 多线程并行搜索(收益有限,复杂度高)
  • 蒙特卡洛树搜索(五子棋不适合)
  • 神经网络评估(太重)
  • 在线开局库更新
  • 残局库