资讯动态

基于Python的AI五子棋:Minimax与Alpha-Beta剪枝实战解析

发布时间:2026/10/12 21:29:10 来源:尧图企业网站定制
简介面向Python初学者的AI五子棋实战项目以极大极小值搜索与Alpha-Beta剪枝为核心清晰演示从棋盘初始化、落子校验、胜负判断到AI决策的完整实现思路。项目模块划分明确game.py处理规则ai.py实现递归搜索、评估函数与剪枝逻辑player.py封装人机交互并附带论文与说明文档便于对照算法原理阅读源码。压缩包共12个文件涵盖py源码、pyc编译文件、xml工程配置及doc/pdf参考文档整体仅150KB轻量易用适合课程设计、算法入门或游戏AI开发练习。已有3017人学习下载运行后可直观感受Alpha-Beta剪枝对搜索效率的提升也可基于现有框架调整评估策略、加深搜索层级进一步打磨AI棋力。1. 基于 Python 的 AI 五子棋极大极小值搜索与 Alpha-Beta 剪枝能实战到什么程度做课设或者入门 AI 博弈最容易卡住的地方不是「规则判断怎么写」而是「搜索到底怎么组织」。基于 Python 的 AI 五子棋这个压缩包恰好把完整可运行的决策引擎和图形界面一起给了你GOAI_RUN.py 负责走棋逻辑和极大极小值搜索graphics.py 负责棋盘渲染references 里还附带了一篇参考论文和中文整理稿。它解决的问题很具体——让你的 AI 在笔记本上以 2 到 4 层搜索深度完成落子同时把 Alpha-Beta 剪枝的每一步都摆在代码里不是封在某个框架的黑匣子里。这个资源的核心价值不在于「AI 棋力有多强」而在于它把搜索树的展开、评估值的传递、剪枝条件的触发全部显式写在了一个单文件工程里。你改一个数字就能看到棋力变化删掉剪枝条件就能对比时间差距这对理解 Minimax 比任何图解都直接。如果你刚学完 Python 基础、想亲手跑通第一个 AI 博弈项目或者正在写课程设计、需要把决策树搜索原理讲得让老师信服这份资源是一个很好的落脚点。解压、运行、改参数、观察行为四步走完你对这类搜索算法的理解会比看十篇教程都扎实。2. Minimax 与 Alpha-Beta 剪枝搜索树怎么搜才能让对手的每一步都按你的剧本走2.1 零和博弈与评估值先定义一个「局面有多好」的数字五子棋是信息完全的双人零和博弈棋盘上没有隐藏信息一方所得就是另一方所失。Minimax 的核心假设是「我走一步对手也走最优」——AI 不能赌对手犯错只能在自己的回合里选择那个让最坏情况也足够好的落子点。这就是为什么它叫「极大极小值」最大化我方的收益下限最小化对手的收益上限。要让搜索能比较不同局面的优劣必须有一个数字化的评估函数。评估值为正说明局面偏向 AI为负说明偏向对手。叶子节点的评估值沿着搜索树向上传递AI 的回合max 层取所有子节点中的最大值因为 AI 会选对自己最有利的一手对手的回合min 层取最小值因为假设对手也会挑让 AI 最难受的一手。这个交替取值的规则就是整棵搜索树的骨架。只看一层显然不够。如果 AI 只评估「我这一手能不能成四」就会忽略对手下一步可能连五的反击。所以 Minimax 必须往下多看几层每多一层就能多识破一次对手的威胁。但代价是计算量爆炸假设每一层有 20 个候选落子点搜索深度 4 就要评估约 16 万个局面深度 6 是 6400 万次。纯递归绝对跑不完Alpha-Beta 剪枝就是用来压缩这个数字的。2.2 Alpha-Beta 剪枝的实现一个函数说清四个关键点Alpha-Beta 剪枝不改变 Minimax 的决策结果只是提前砍掉那些「不可能影响根节点选择」的分支。实现时维护两个边界值Alpha 是 max 层目前已经找到的最好值Beta 是 min 层目前已经找到的最差值。当某个节点上 Alpha 大于等于 Beta说明这个分支再搜下去也不会被父节点选中可以直接停止。def minimax(board, depth, alpha, beta, is_maximizing): # 到达叶子层返回评估值 if depth 0: return evaluate(board) # 只搜索候选落子而不是全盘空位 moves get_candidate_moves(board) if is_maximizing: best -float(inf) for move in moves: board.place(move, AI_PIECE) # 模拟 AI 落子 val minimax(board, depth - 1, alpha, beta, False) board.remove(move) # 悔棋恢复局面 best max(best, val) alpha max(alpha, val) # 只在 max 层更新 alpha if alpha beta: # 剪枝条件 break return best else: best float(inf) for move in moves: board.place(move, HUMAN_PIECE) # 模拟对手落子 val minimax(board, depth - 1, alpha, beta, True) board.remove(move) best min(best, val) beta min(beta, val) # 只在 min 层更新 beta if beta alpha: break return best这套代码有四个关键点需要注意第一board.place 和 board.remove 必须成对出现否则局面状态在递归返回时会错乱这是新手最容易翻车的地方。第二alpha 只允许在 max 层变大beta 只允许在 min 层变小二者的更新方向写反剪枝就会把最优解剪掉。第三剪枝条件统一用alpha beta不需要区分大于还是等于。第四传入的 alpha、beta 是父层传下来的边界值每个子树都基于这个边界继续收敛。参数上初始调用写minimax(board, depth, -float(inf), float(inf), True)。depth 是剩余搜索层数每递归一层减 1到 0 就返回评估值。is_maximizing 为 True 表示当前轮到 AI 决策False 表示对手回合。2.3 剪枝为什么可靠不是贪心是数学上证明过安全很多人第一次接触剪枝会怀疑砍掉的分支里万一藏着最优解怎么办答案是Alpha-Beta 剪枝在信息完全、评估函数确定的前提下剪掉的分支确实不可能比当前已知结果更好。Max 层的 alpha 已经说明「我至少能拿到这个分数」如果某个 min 节点传回的 beta 比这个还低父节点根本不会选择它Min 层的 beta 同理。剪枝的实际收益高度依赖走法顺序。如果先搜那些评估值高的走法alpha 会快速收敛到接近真实最优值后续分支被剪掉的概率就大要是先搜烂走法alpha 一直很低剪枝几乎不生效性能跟裸 Minimax 一样。所以正规工程里都会在进入搜索前给候选走法排序这比盲目加大搜索深度划算得多。验证剪枝是否生效也很简单在 minimax 入口打印当前层的候选点数量对比不剪枝时的数值。正常情况下固定搜索深度时候选点规模应该从 20 左右降到 6 到 8 个同时搜索结果不变。如果候选数量没下降基本可以确定是走法排序没起作用而不是剪枝逻辑写错了。3. 评估函数与候选落子AI 的棋力一半靠搜索另一半靠会看棋3.1 棋型与打分把「活三」「冲四」翻译成数字评估函数决定的是「叶子节点这个局面到底值几分」。五子棋的棋力核心是棋型识别——同样是连成 3 个子两端都没堵的「活三」和一头被堵的「眠三」威胁程度完全是两个量级。一份合理的评分表层次必须拉得足够开否则 AI 会分不清「先成四」和「先成五」哪个更紧急。棋型分数说明五连1000000已经赢了分数必须拉满活四100000两端都空的连四下一手必胜冲四10000一端被堵只能单方向延伸成五活三5000下一手能变成活四眠三1000下一手能变成冲四活二500有发展成活的潜力眠二100潜力有限但聊胜于无这份表的核心原则是「量级差距」五连要比冲四高两个数量级活三要比眠三高 5 倍。如果分数差距不够大AI 在搜索时可能为了堵一个冲四放过自己直接连五的机会——这种低智商操作基本都是评分表层级不够造成的。棋型扫描的常见做法是沿横、竖、两个斜共四个方向统计连子数。判断「活」还是「眠」看连子串两端是否为空位两端都空是活一端堵死是眠两端都堵就基本是废棋。简化版的方向扫描可以这样写def count_line(board, x, y, dx, dy, role): # 统计从 (x,y) 开始沿 (dx,dy) 方向的连子数量 count 0 tx, ty x, y while board.is_inside(tx, ty) and board.get(tx, ty) role: count 1 tx dx ty dy # 再往反方向数一遍 tx, ty x - dx, y - dy while board.is_inside(tx, ty) and board.get(tx, ty) role: count 1 tx - dx ty - dy return count这段代码先沿正方向数到非己方棋子或边界再调头往反方向数最终 count 就是该方向上连续的己方棋子数。注意空位和对手棋子都会中断计数所以它返回的是「一段连续串」的长度。拿到长度后再检查两端状态就能映射到上面的评分表。3.2 候选落子邻域搜索把分支因子从几百压到几十Minimax 的性能瓶颈不在评估函数而在候选落子的数量。15×15 的棋盘上有 200 多个空位如果每个节点都遍历全部空位深度 4 就是 200 的四次方等一晚上也出不来一步棋。解决思路很朴素有经验的棋手落子基本都落在已有棋子附近远离战场的空位在前期毫无价值。def get_candidate_moves(board, radius2): candidates set() # 用 set 去重防止同一个空位被多个棋子扫到 for piece in board.all_pieces(): x, y piece.x, piece.y for dx in range(-radius, radius 1): for dy in range(-radius, radius 1): nx, ny x dx, y dy if board.is_inside(nx, ny) and board.is_empty(nx, ny): candidates.add((nx, ny)) return list(candidates)逻辑上这段代码遍历棋盘上每个已有棋子以它为中心扫一个 5×5 的方阵把方阵内的空位全部收进候选集合。set 保证同一个空位即使被多个棋子重复扫到也只保留一次。radius 这个参数很值得调设成 1候选点只有个位数搜索飞快但会漏掉「隔一个空位就能成五」的棋——AI 可能为了堵眼前的三连无视掉远处空一格就能连五的杀招。设成 3候选点又回到 40 到 60 个搜索速度明显下降。我一般的做法是前中盘用 radius2进入残局阶段、空位少于 30 个时再扩大到 radius3 或者直接全盘扫描反正此时空位已经不多了。3.3 攻守平衡评估函数里最常见的三个「人工智障」现象现象一AI 只堵不攻。玩家连出两个子AI 立刻去堵但玩家不进攻时AI 自己也不积累棋型。这种问题几乎都是评估函数只算了对手分数或者把己方棋型的权重压得太低导致的。现象二AI 猛冲但看不到对方的威胁明明对方已经活三AI 还在远处搭自己的二连。这是典型的「只顾己方得分」写法。现象三更隐蔽AI 在局部棋盘上反复横跳看起来每一步都合法但棋型积累永远是散的这是扫描方向遗漏或连子计数重复计算导致的。解决办法是让评估函数在结构上保持对称def evaluate(board, my_role, opp_role): my_score evaluate_all_lines(board, my_role) opp_score evaluate_all_lines(board, opp_role) return my_score - opp_score己方棋型和对方棋型分别扫全盘四方向算分后相减。返回值为正说明我方有优势为负说明对方有优势。搜索的 max 层会把局面往正方向推min 层会往负方向拉攻守自然就平衡了。如果想让 AI 稍微偏向进攻可以把公式改成my_score * 1.05 - opp_score。注意这个系数不要超过 1.2否则 AI 会变得极度贪婪为了自己成四可以直接无视对方的活三——你们对局时就会看到一个反复横跳的「莽夫」。棋型权重这东西有点玄学但它确实是评估函数里最有调校空间的部分建议每改一次就用双 AI 对局验证一轮。4. 压缩包到手文件结构、运行环境与二次开发从哪里下手4.1 压缩包里真正要用的文件只有这几个这份压缩包解压后目录里真正参与运行的文件比想象中少。GOAI_RUN.py 是主程序入口棋盘初始化、落子事件、AI 决策循环都集中在里面graphics.py 是轻量图形渲染库基于 tkinter 封装了窗口和画布负责把棋盘画出来。这两个文件放同目录就能跑。文件/目录作用我的处理方式GOAI_RUN.py主程序棋盘逻辑 AI 搜索 事件循环运行入口主要阅读对象graphics.py图形渲染库基于 tkinter与主程序同目录保留references/wagnervirag_2001.pdfMinimax 相关英文论文写课程报告时引用论文内容.doc中文整理稿对照理解算法细节pycache/graphics.cpython-36.pycPython 3.6 编译缓存忽略不参与分发.idea/PyCharm 工程配置忽略这个项目没有 game.py、ai.py 那种模块拆分算法和 UI 事件挤在 GOAI_RUN.py 一个文件里是早期课设工程的典型形态。好处是单文件好运行、好分发坏处是读代码时要自己按区块划线。但反过来这也让它特别好改——棋盘的尺寸常量、搜索深度、先后手设置通常都堆在文件顶部改完重启就能看到效果。4.2 运行项目Python 3.6-3.9 最稳不需要 pip 装任何包环境这块很多人会惯性思维觉得 AI 项目一定要装 numpy但这个项目不需要。graphics.py 走的是 tkinterPython 标准库自带。你只需要确认系统 tkinter 可用然后直接启动脚本。# 进入解压目录 cd AI五子棋 # 确认 Python 版本3.6 到 3.9 都比较稳 python --version # 验证 tkinter 可用graphics.py 靠它渲染窗口 python -c import tkinter; print(tkinter.TkVersion) # 启动对局 python GOAI_RUN.py第一条命令只是确认版本pyc 文件显示原工程是在 Python 3.6 环境下跑的如果你用的是 3.10 以上graphics.py 可能会有兼容性小问题后面避坑章会细说。第二条命令如果报 ImportError说明系统缺 tkinterUbuntu 下执行sudo apt-get install python3-tkWindows 的官方 Python 安装包默认自带不用额外处理。最后启动后窗口弹出就可以用鼠标点击落子AI 会自动响应。4.3 我一般怎么改把搜索深度和先后手做成可调参数拿到一个单文件工程第一步不是读完整代码而是先定位文件开头的常量区。这个项目的常量往往散落在几个位置但棋盘尺寸、搜索深度这类值一定在最前面。改成下面这种集中管理的形式调试起来会顺手很多# 找到文件开头的常量区改成这种集中配置 BOARD_SIZE 15 # 棋盘尺寸标准五子棋 15 路 SEARCH_DEPTH 2 # 搜索深度2 层适合教学4 层开始有棋感 AI_FIRST True # AI 是否执黑先行 MOVE_DELAY 300 # AI 落子后的停顿毫秒数纯演示节奏参数深度 2 的 AI 基本只能防住直接威胁你连出两个活二它就开始乱堵深度 3 会开始考虑「对手有没有可能反杀」深度 4 才算真正有棋感能预判两步之后的棋型冲突。但注意depth 每加 1单步耗时通常乘 3 到 5 倍。在普通笔记本上深度 4 搜索一次可能要 3 到 8 秒能不能接受取决于你的耐心。我一般先设 2 跑通全流程确认没有逻辑错误再往上加。4.4 把「人机对战」改成「AI 互打」观察搜索效果的最低成本方式验证评估函数和搜索逻辑好不好最直接的手段是让两个 AI 自己对弈。原工程是人和 AI 对局但你只需要把主循环里「等玩家输入」的那段换成第二个 AI 调用即可。如果原文件里的 AI 决策写成了一个独立函数改动成本极低# 双 AI 对局示意假设原文件提供 search(board, role, depth) 函数 from GOAI_RUN import search def ai_vs_ai(board, depth_black3, depth_white3): while not board.is_game_over(): move search(board, role1, depthdepth_black) if board.turn 1 else search(board, role2, depthdepth_white) board.place(move[0], move[1]) board.display()提前说明一下如果原文件的游戏逻辑写在if __name__ __main__:块里直接 import 不会弹窗、不会触发循环这是安全的。改双 AI 对局时最值得观察的是棋型分布黑 AI 会不会连续在某个区域叠子、白 AI 是不是只会贴着黑 AI 落子。如果一方开局 20 手内从不主动成三而另一方拼命堵基本可以断定评估函数里己方棋型的权重出了问题。5. 避坑与排查换了几台机器之后我记住的四条踩坑记录5.1 RecursionError 与单步思考 10 秒深度没控制住现象运行后 AI 一落子就报 RecursionError或者程序像卡死一样等十几秒才出棋。原因minimax 递归没有正确触底depth 参数在递归链路里没有递减或者 get_candidate_moves 把全盘空位都返回了分支因子过大还有一种可能是有人把 SEARCH_DEPTH 直接设成了 8、10 这类笔记本扛不住的值。解决先把 SEARCH_DEPTH 固定为 2确认能正常走完一盘。再把候选函数改成邻域式生成。最后加一层保险——在 minimax 函数入口写死if depth 0: return evaluate(board)而不是依赖调用方保证 depth 为正。这样即使外部传参错了递归也能正常收敛。5.2 AI 只堵不攻评估函数把攻防权重写反了现象玩家只要连出两个子AI 立刻去堵但玩家不进攻的时候AI 自己也不积累棋型整盘棋都在被动防守。原因评估函数写成return -opp_score或者对 opponent 的得分乘了很大惩罚系数。这样一来AI 眼里只有「别人的威胁」己方棋型没有任何得分激励。解决改成my_score - opp_score的对称写法。如果想让 AI 偏进攻给 my_score 乘 1.05 到 1.1 的系数但尽量不要超过 1.2。我见过有人乘 1.5结果 AI 在对方已经活三的局面里还去搭自己的二连属于典型的评分权重失衡。5.3 graphics.py 在新环境报错AttributeError 或窗口秒退现象新机器运行python GOAI_RUN.py直接报module graphics has no attribute GraphWin或者窗口闪一下就消失。原因大概率是曾经用 pip 装过同名的 graphics 包Python 的 import 机制优先找到了 site-packages 里那个版本而不是项目同目录的 graphics.py。pip 的 graphics 包和教学版 graphics.py 是两个不同的项目接口完全不同。解决先执行pip uninstall graphics清掉同名包。再把压缩包里的 graphics.py 和 GOAI_RUN.py 放到同一个目录。运行前可以用python -c import graphics; print(graphics.__file__)确认导入的是哪个文件路径指向项目目录才是对的。5.4 加了剪枝之后 AI 反而变蠢alpha/beta 更新方向错了现象不加剪枝时 AI 能找到连五加剪枝后同样的局面AI 走出无关紧要的散棋好像把最优解剪掉了。原因max 层误更新了 betamin 层误更新了 alpha或者剪枝条件写成了而不是更隐蔽的是递归传参时把 alpha、beta 两个值对调了。解决记住一句话——alpha 只属于 max 层beta 只属于 min 层。代码里只允许alpha max(alpha, val)和beta min(beta, val)这两种更新出现任何反向更新都要立即修正。剪枝条件统一写if alpha beta: break。如果还不对就在每层递归入口打印depth, alpha, beta看边界值是不是沿着搜索方向单调压紧——如果不是传参链路里一定有问题。5.5 定位问题的通用手段给 minimax 加一层「手术探针」现象AI 行为忽高忽低评估值在相邻两次落子间剧烈跳动肉眼完全看不出是哪一环出了bug。原因评估函数、候选生成、搜索剪枝三个环节叠加在一起光靠行为观察很难定位问题在哪一层。评估值跳到异常区间可能是棋型算错也可能是候选点漏掉了关键手。解决在 minimax 入口加一个调试开关打印每个节点的深度、候选数量、alpha、beta 值def minimax(board, depth, alpha, beta, is_maximizing, debugFalse): moves get_candidate_moves(board) if debug: print(f{ * (4 - depth)}depth{depth} falpha{alpha:.0f} beta{beta:.0f} moves{len(moves)}) # ... 后续正常递归逻辑上debug 打开后能看到剪枝发生在哪一层、候选点是多是少、边界值如何收敛。排查完记得把 debug 关掉不然每一层都 print深度 4 就能刷屏几百行。这个技巧是我处理这类单文件工程问题时最常用的一招比逐行读代码快得多。6. 进阶迭代加深、走法排序与置换表把搜索深度再压两层固定 SEARCH_DEPTH 的问题在实战中很明显设成 2落子飞快但棋力弱设成 4每步要等好几秒体验断崖式下降。与其赌一个固定深度不如用迭代加深——先从 depth1 开始搜每搜完一层就看一眼用掉的时间时间预算没超就继续加深超了就返回上一层完整搜完的结果。def iterative_search(board, time_limit_ms800): best_move None for depth in range(1, 7): if elapsed_ms() time_limit_ms: break best_move minimax_root(board, depth, -float(inf), float(inf)) return best_move这段代码的逻辑是每一层都从头搜一遍前几层耗时极短真正吃时间的是最后一层。超时后直接返回上一次完整搜完的 best_move而不是当前搜到一半的残缺结果。time_limit_ms 参数控制在 500 到 800 毫秒之间比较均衡——既不会让玩家等得不耐烦又能保证至少搜到第 3 层。走法排序是配合剪枝最有效的一招。进入搜索前把候选点按评估函数降序排列越好的走法越先被搜索alpha 就能更快收敛到接近真实最优值的水平后续分支被剪掉的概率随之大幅提升。实现成本很低但效果经常能让等效搜索深度提高 1 层。置换表是更进阶的做法用一个字典缓存(board_hash, depth, alpha, beta) - score。同一个局面在搜索树里可能被不同路径到达多次命中缓存就能省掉整棵子树的重复计算。但要注意棋盘哈希不能简单用字符串拼接否则哈希耗时比搜索还长建议用 zobrist hashing 做增量哈希。我从这个项目里养成的习惯是每次调整评估函数或搜索参数都会强制走一遍「depth2 基线对局 迭代加深时间盒对比」的流程确认棋力没有倒退再往上加深度。棋盘 AI 的修炼本质上就是把搜索效率一点一点磨上去的过程。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑