如何为3、4、5阶井字棋正确实现Minimax AI?
正确实现n阶井字棋Minimax AI的方案
问题背景
需要为3、4、5阶井字棋实现Minimax AI,游戏规则如下:
- n阶井字棋拥有n行n列共n²个单元格的棋盘,初始为空
- 两名玩家交替落子,每次必须选择未被占据的单元格
- 当某玩家占据完整一行、一列或一条对角线,或所有单元格被占时,游戏结束
已完成3、4阶合法状态枚举,当前尝试实现Minimax AI时遇到以下问题:
- 重写的基础Minimax代码仅返回10/-10/0三种得分,未区分早获胜与晚获胜的权重
- 自行编写的Minimax变体逻辑不清晰,不确定正确性
- 需要针对大棋盘(4、5阶)优化性能,同时优先处理能直接获胜或阻止对手的关键落位点
棋盘采用位编码方案:用2n²位存储两名玩家的占位情况,o和x分别代表O和X的位掩码,full = o | x表示已占位单元格,通过o << n*n | x打包为整数,解压用o = full >> n*n; x = full & ((1 << n*n) - 1)。
核心实现方案
1. 明确Minimax核心逻辑
Minimax是零和博弈算法,核心逻辑为:
- 最大化玩家(如X)追求当前状态的最高可能得分
- 最小化玩家(如O)追求当前状态的最低可能得分
- 终端状态得分规则:
- 获胜玩家获得正分,失败玩家获得负分,平局得0分
- 获胜回合越早,得分权重越高(比如用
得分 * (剩余可走步数 + 1),让早赢的路径优先级更高)
- 非终端状态得分:最大化玩家取所有子状态的最大得分,最小化玩家取所有子状态的最小得分
2. 修复基础Minimax的得分缺陷
之前的代码未考虑递归深度,导致所有获胜状态得分相同。调整终端状态得分计算:
# 示例:SCORES定义为(获胜得分, 失败得分, 平局得分) SCORES = (10, -10, 0) # depth为剩余可走步数(初始值为n² - 已走步数) score = SCORES[winner] * (depth + 1)
例如3阶井字棋中,第3步获胜得10*7=70,第5步获胜得10*5=50,算法会优先选择更快获胜的路径。
3. 加入Alpha-Beta剪枝提升性能
针对4、5阶大棋盘,必须加入Alpha-Beta剪枝避免不必要的递归:
alpha:最大化玩家能保证的最低得分beta:最小化玩家能保证的最高得分- 当子节点得分超过
beta(最大化玩家)或低于alpha(最小化玩家)时,直接停止该分支的搜索
同时优先搜索关键落位点,大幅提升剪枝效率:
- 先搜索能直接获胜的落位点
- 再搜索需要阻止对手获胜的落位点
- 最后搜索剩余空位
4. 完善状态缓存(Transposition Table)
缓存需存储状态的精确得分、搜索深度和状态类型,避免重复计算:
# 缓存格式:{棋盘哈希值: (得分, 搜索深度, 状态类型)} # 状态类型:EXACT(精确得分)、LOWER(下界)、UPPER(上界) transposition_table = {}
5. 通用n阶Minimax实现代码
from typing import Dict, Tuple, List def generate_lines(n: int) -> List[int]: """生成所有获胜线的位掩码""" lines = [] # 生成行掩码 for i in range(n): line = 0 for j in range(n): pos = i * n + j line |= 1 << pos lines.append(line) # 生成列掩码 for j in range(n): line = 0 for i in range(n): pos = i * n + j line |= 1 << pos lines.append(line) # 生成主对角线掩码 diag1 = 0 for i in range(n): pos = i * n + i diag1 |= 1 << pos lines.append(diag1) # 生成副对角线掩码 diag2 = 0 for i in range(n): pos = i * n + (n - 1 - i) diag2 |= 1 << pos lines.append(diag2) return lines def check_win(player_mask: int, lines: List[int]) -> bool: """检查玩家是否获胜""" for line in lines: if (player_mask & line) == line: return True return False def get_empty_positions(full_mask: int, n: int) -> List[int]: """获取所有空位的位置""" empty = [] total_cells = n * n for pos in range(total_cells): if not (full_mask & (1 << pos)): empty.append(pos) return empty def find_critical_moves(o_mask: int, x_mask: int, lines: List[int], n: int) -> List[int]: """找到关键落位点:直接获胜或阻止对手获胜的位置""" full_mask = o_mask | x_mask critical = set() # 检查当前玩家(X)是否能一步获胜 for line in lines: if (x_mask & line).bit_count() == n - 1 and (full_mask & line).bit_count() == n - 1: empty_pos = line ^ (x_mask & line) pos = (empty_pos).bit_length() - 1 critical.add(pos) # 检查对手(O)是否能一步获胜,需要阻止 for line in lines: if (o_mask & line).bit_count() == n - 1 and (full_mask & line).bit_count() == n - 1: empty_pos = line ^ (o_mask & line) pos = (empty_pos).bit_length() - 1 critical.add(pos) return list(critical) def minimax( o_mask: int, x_mask: int, lines: List[int], n: int, is_maximizing: bool, depth: int, alpha: float, beta: float, trans_table: Dict[int, Tuple[int, int, str]] ) -> int: """带Alpha-Beta剪枝和状态缓存的通用Minimax函数""" full_mask = o_mask | x_mask board_hash = (o_mask << (n*n)) | x_mask # 读取缓存 if board_hash in trans_table: cached_score, cached_depth, flag = trans_table[board_hash] if cached_depth >= depth: if flag == 'EXACT': return cached_score elif flag == 'LOWER': alpha = max(alpha, cached_score) elif flag == 'UPPER': beta = min(beta, cached_score) if alpha >= beta: return cached_score # 检查终端状态 if check_win(x_mask, lines): score = 10 * (depth + 1) trans_table[board_hash] = (score, depth, 'EXACT') return score if check_win(o_mask, lines): score = -10 * (depth + 1) trans_table[board_hash] = (score, depth, 'EXACT') return score if full_mask.bit_count() == n*n: trans_table[board_hash] = (0, depth, 'EXACT') return 0 # 确定搜索顺序:优先关键落位点 empty_positions = get_empty_positions(full_mask, n) critical_moves = find_critical_moves(o_mask, x_mask, lines, n) if critical_moves: search_order = critical_moves + [pos for pos in empty_positions if pos not in critical_moves] else: search_order = empty_positions if is_maximizing: max_score = -float('inf') for pos in search_order: new_x_mask = x_mask | (1 << pos) current_score = minimax(o_mask, new_x_mask, lines, n, False, depth - 1, alpha, beta, trans_table) max_score = max(max_score, current_score) alpha = max(alpha, current_score) if beta <= alpha: break trans_table[board_hash] = (max_score, depth, 'EXACT' if max_score > alpha else 'UPPER') return max_score else: min_score = float('inf') for pos in search_order: new_o_mask = o_mask | (1 << pos) current_score = minimax(new_o_mask, x_mask, lines, n, True, depth - 1, alpha, beta, trans_table) min_score = min(min_score, current_score) beta = min(beta, current_score) if beta <= alpha: break trans_table[board_hash] = (min_score, depth, 'EXACT' if min_score < beta else 'LOWER') return min_score # 3阶井字棋使用示例 n = 3 lines = generate_lines(n) trans_table = {} initial_score = minimax(0, 0, lines, n, True, n*n, -float('inf'), float('inf'), trans_table)
6. 大棋盘(4、5阶)额外优化
- 深度限制:设置最大搜索深度(如8步),超过深度时使用启发式评估函数(比如计算玩家拥有的潜在获胜线数量,按已占子数加权打分)
- 并行搜索:用多线程/多进程并行搜索不同落位点,提升搜索速度
- 内存优化:利用Python原生
int的任意长度特性存储位掩码,避免额外内存开销
内容的提问来源于stack exchange,提问作者Ξένη Γήινος
相关产品推荐
相关产品推荐

