简介:这份资源面向Python初学者与AI算法爱好者,提供一套可直接运行的五子棋AI实现方案,重点演示极大极小值搜索与Alpha-Beta剪枝在博弈决策中的落地方式。包内共12个文件,以py源码、xml配置、pyc缓存及iml工程文件为主,另附doc论文与pdf参考资料,压缩包约150KB,体量轻便,便于快速阅读与二次修改。核心代码围绕棋盘状态表示、评估函数、递归搜索与剪枝逻辑展开,并划分游戏规则、AI决策、玩家交互与主程序等模块,结构清晰,适合作为课程设计或算法练手项目。目前已有3017人学习下载,读者可借此理解零和博弈中的决策树搜索思路,掌握剪枝对搜索效率的优化手段,并在此基础上尝试调整评估权重、扩展搜索深度或加入缓存机制,从而完成从理论到可运行AI的完整实践。
1. 拆开这份 Python AI 五子棋压缩包:Minimax 与 Alpha-Beta 到底能跑出什么水平
很多人第一次听到「Python AI 五子棋实现」会下意识觉得是个玩具,真把压缩包解压开、把GOAI_RUN.py跑起来,才会发现它其实是一份相当完整的博弈搜索教学样本。这份资源里同时躺着graphics.py负责界面渲染、GOAI_RUN.py负责主循环、references目录里还塞了一篇wagnervirag_2001.pdf和论文内容.doc,说明作者不是随手写了个能落子的脚本,而是照着论文把极大极小值搜索和 Alpha-Beta 剪枝落成了可运行代码。它适合两类人:一类是刚学完 Python 基础、想找一个能看见「搜索树怎么被剪掉」的实战项目;另一类是做课程设计或面试准备,需要一份能讲清评估函数、递归深度、剪枝边界值怎么设的参考实现。下面我按「先跑通、再读懂、最后调参」的顺序,把这份资源拆成能直接抄作业的步骤。
2. 把压缩包跑起来:环境、入口与棋盘渲染链路
2.1 解压后先认清文件角色,别急着改代码
拿到AI五子棋.rar之后,第一件事不是双击GOAI_RUN.py,而是先把目录结构看清楚。压缩包里除了源码,还混进了__pycache__、.idea、.gitignore、五子棋AI.iml这些开发过程残留,它们不影响运行,但会干扰你判断哪些文件是真正要读的。核心文件其实就三个:GOAI_RUN.py是入口,负责初始化棋盘、绑定鼠标事件、驱动 AI 落子;graphics.py是渲染层,把二维数组画成格子并处理点击坐标换算;references里的 PDF 和 DOC 是算法出处,调参卡住时回去翻比在网上乱搜快得多。
我一般会先把__pycache__和.idea删掉,只留源码和参考文档,这样后面用编辑器全局搜索函数名时不会被.pyc的二进制内容干扰。删之前确认一下graphics.cpython-36.pyc对应的源码graphics.py还在,否则界面会直接报模块找不到。这一步看着琐碎,但很多「跑不起来」的翻车现场,根源就是残留文件让导入路径变乱。
2.2 依赖与 Python 版本:3.6 能跑,3.8 以上更稳
这份代码的字节码缓存是cpython-36,说明作者当年用的是 Python 3.6。实际复现时不必死守 3.6,3.8 到 3.11 都能跑,因为核心逻辑只用到标准库和graphics这类轻量绘图模块。真正需要注意的是图形库的安装,如果graphics.py依赖的是tkinter,那 Windows 和 macOS 自带,Linux 下要单独装。
# 先确认 Python 版本,3.8 以上即可 python --version # Linux 下如果 graphics 基于 tkinter,补装系统包 sudo apt-get install python3-tk # 进入解压后的目录,先做一次语法检查,避免带着错误往下走 python -m py_compile GOAI_RUN.py graphics.pypy_compile这步很关键,它不执行逻辑,只检查语法。如果这里就报SyntaxError,说明你拿到的文件在传输中被截断或编码损坏,后面所有调试都是白费。确认通过后再运行python GOAI_RUN.py,正常情况会弹出一个棋盘窗口,鼠标点击即可落子,AI 会在你落子后自动回应。
2.3 棋盘状态怎么表示:二维列表与坐标换算
读懂这份实现,绕不开棋盘的数据结构。常见做法是用一个N x N的二维列表,0 表示空位,1 表示人类,2 表示 AI。graphics.py负责把鼠标点击的像素坐标换算成行列下标,换算公式通常是col = (x - margin) // cell_size,row = (y - margin) // cell_size。这个换算一旦有半个格子的偏差,就会出现「点这里却下到旁边」的玄学现象。
# 棋盘初始化与坐标换算的典型写法 BOARD_SIZE = 15 board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] def pixel_to_grid(x, y, margin=30, cell=40): """把鼠标像素坐标转成棋盘行列,越界返回 None""" col = (x - margin) // cell row = (y - margin) // cell if 0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE: return int(row), int(col) return None参数说明:margin是棋盘边缘留白,cell是每格像素宽度,这两个值必须和graphics.py里画线时用的保持一致,否则点击和落子会对不上。board用列表推导式生成,避免[[0]*15]*15这种写法——后者每一行都是同一个对象的引用,改一行会连带改所有行,这是新手最常踩的坑之一。
3. 极大极小值搜索:评估函数与递归框架怎么落地
3.1 Minimax 的核心不是递归,是评估函数
极大极小值搜索(Minimax)在五子棋里的角色,是把「我走一步、对手走一步」的交替过程展开成一棵博弈树,然后从叶子节点往回推分值。AI 回合取最大值,对手回合取最小值,这样得到的根节点走法就是当前局面下的最优解。但很多人把注意力全放在递归写法上,忽略了真正决定 AI 强弱的是评估函数——也就是「给定一个棋盘,它值多少分」。
这份资源里评估函数的常见做法是扫描所有横、竖、两条斜线,统计连子形态:活四、冲四、活三、眠三、活二分别给不同权重。权重设得合理,AI 就能在搜索深度不深的情况下挡住你的活三;权重设得随意,哪怕搜到六层也会出现「看着对手连四还不堵」的翻车场面。
# 评估函数骨架:按连子形态累加分值 SCORES = { "FIVE": 100000, # 五连,直接赢 "OPEN_FOUR": 10000, # 活四,两头都能成五 "FOUR": 1000, # 冲四,只有一头能成五 "OPEN_THREE": 1000, # 活三,需要立即应对 "THREE": 100, # 眠三 "OPEN_TWO": 100, # 活二 "TWO": 10, # 眠二 } def evaluate(board, ai_player=2, human_player=1): """扫描四个方向,统计双方连子形态并累加""" score = 0 for line in extract_all_lines(board): score += score_line(line, ai_player) score -= score_line(line, human_player) * 1.1 # 防守权重略高 return score参数说明:extract_all_lines把棋盘按横、竖、正斜、反斜四个方向切成若干条线;score_line对单条线做模式匹配。注意防守权重乘了1.1,这是让 AI 稍微偏向堵对手而不是只顾自己进攻,实战里能明显减少「互相对攻结果自己慢一步」的情况。这个系数不是固定的,你可以按自己的棋风在1.0到1.3之间调。
3.2 递归搜索框架:深度、终止条件与落子还原
Minimax 的递归框架本身不复杂,难的是终止条件和状态还原。终止条件一般有三类:搜索深度到上限、某一方已经五连、棋盘已满。状态还原指的是「在棋盘上落一子、递归、再把这一子撤掉」,如果忘了撤,棋盘会越搜越满,AI 的行为会变得完全不可预测。
def minimax(board, depth, is_maximizing, alpha, beta): """极大极小值搜索主框架,带 Alpha-Beta 剪枝""" if depth == 0 or has_winner(board): return evaluate(board) if is_maximizing: best = -float("inf") for row, col in get_empty_cells(board): board[row][col] = 2 # AI 落子 val = minimax(board, depth - 1, False, alpha, beta) board[row][col] = 0 # 关键:还原棋盘 best = max(best, val) alpha = max(alpha, best) if beta <= alpha: break # 剪枝 return best else: best = float("inf") for row, col in get_empty_cells(board): board[row][col] = 1 # 对手落子 val = minimax(board, depth - 1, True, alpha, beta) board[row][col] = 0 best = min(best, val) beta = min(beta, best) if beta <= alpha: break return best逻辑说明:is_maximizing为真时是 AI 回合,遍历所有空位试落,取最大值;为假时是对手回合,取最小值。alpha记录当前路径上 AI 已能保证的最好结果,beta记录对手能保证的最差结果,当beta <= alpha时说明这一支已经不可能影响最终决策,直接break。参数depth控制搜索层数,层数越高棋力越强但耗时呈指数增长,15 路棋盘上纯 Python 递归一般开到 4 到 6 层就要开始等秒级响应了。
3.3 空位生成顺序:先搜好点,剪枝才有效
Alpha-Beta 剪枝的效果高度依赖搜索顺序。如果每次都从棋盘左上角开始遍历空位,剪枝几乎不起作用,因为好点往往排在后面。常见优化是先用一个简单的启发式给候选点排序,比如优先搜索已有棋子周围的空位,再搜远离棋子的位置。
def get_empty_cells(board): """按邻近已有棋子的程度排序,提升剪枝效率""" candidates = [] for r in range(len(board)): for c in range(len(board)): if board[r][c] == 0 and has_neighbor(board, r, c, radius=2): candidates.append((r, c)) # 没有邻居时退回全盘扫描,避免开局无点可搜 if not candidates: candidates = [(r, c) for r in range(len(board)) for c in range(len(board)) if board[r][c] == 0] return candidateshas_neighbor检查以(r, c)为中心、半径radius内是否有棋子。半径设 2 意味着只考虑已有棋子两格范围内的空位,这能把候选点数量压掉一大半,剪枝效率立刻上来。注意开局阶段棋盘上没几个子,candidates可能为空,所以要有退回全盘扫描的分支,否则 AI 第一步会直接卡死。
4. Alpha-Beta 剪枝:边界值传递与性能实测
4.1 Alpha 和 Beta 到底代表什么
Alpha-Beta 剪枝经常被讲得很抽象,其实用一句话就能说清:alpha是「AI 目前已经能确保拿到的最低分」,beta是「对手目前已经能确保压到的最高分」。在 AI 回合,随着找到更好的走法,alpha不断抬高;在对手回合,随着找到更差的走法,beta不断压低。一旦某个分支里beta已经小于等于alpha,说明对手不会让这个分支发生,或者 AI 已经有更好的选择,这一支就没必要再搜了。
这份资源里剪枝是嵌在递归里的,和上一节的框架一致。真正容易出错的是初始值:根节点调用时alpha传-inf、beta传+inf,如果传成 0 或者某个具体分值,剪枝会提前触发,AI 会漏掉本该考虑的好棋。这个 bug 很隐蔽,表现是 AI 偶尔走出莫名其妙的棋,而不是直接报错。
4.2 剪枝前后耗时对比:用数据决定搜索深度
光讲原理不够,得实测。我一般会在GOAI_RUN.py里临时加一段计时,分别关掉和打开剪枝,记录同一局面下搜索到第 4、5、6 层的耗时。下面是一个可复用的计时片段:
import time def timed_search(board, depth, use_pruning=True): """对比剪枝开关下的搜索耗时""" start = time.time() if use_pruning: score = minimax(board, depth, True, -float("inf"), float("inf")) else: score = minimax_no_pruning(board, depth, True) elapsed = time.time() - start print(f"depth={depth} pruning={use_pruning} score={score} time={elapsed:.3f}s") return score参数说明:depth是搜索层数,use_pruning控制是否走带剪枝的分支。实测下来,15 路棋盘、中局阶段,第 4 层不剪枝可能要几秒,剪枝后通常压到几百毫秒;第 5 层差距更明显,不剪枝可能几十秒,剪枝后仍在可接受范围。这就是为什么这份实现必须带 Alpha-Beta——没有它,纯 Minimax 在 Python 里根本撑不到有实战意义的深度。
4.3 缓存与置换表:避免重复计算同一局面
搜索树里大量局面会重复出现,只是到达顺序不同。用一个字典把「局面哈希 -> 评估值」缓存起来,能进一步省时间。常见做法是把棋盘序列化成字符串或元组作为 key,配合functools.lru_cache或手写字典。
from functools import lru_cache def board_key(board): """把棋盘转成可哈希的元组,作为缓存键""" return tuple(tuple(row) for row in board) @lru_cache(maxsize=100000) def cached_evaluate(board_tuple): """带缓存的评估,注意传入的是元组而非列表""" board = [list(row) for row in board_tuple] return evaluate(board)注意lru_cache要求参数可哈希,列表不行,所以要先转成元组。缓存命中率在中后盘会明显上升,但开局阶段局面差异大,命中率低,maxsize设太大反而占内存。我一般设 10 万条上限,跑一局下来内存占用可控,速度提升在复杂局面下能到两三成。
5. 避坑与排查:五子棋 AI 最常见的五类翻车
5.1 现象:AI 第一步就卡死或直接报错
原因通常是候选点生成逻辑在空棋盘上返回了空列表,递归里没有任何可落子的位置,或者graphics.py的初始化顺序不对,棋盘还没画完就开始搜索。解决方式是给get_empty_cells加空盘兜底,返回棋盘中心点;同时确认GOAI_RUN.py里是先建窗口再启动 AI 回合,顺序反了会拿到未初始化的画布对象。
5.2 现象:AI 明明能赢却去堵对手,或者该堵不堵
这是评估函数权重失衡的典型表现。进攻分给太高,AI 会沉迷自己做活三;防守分给太高,AI 会到处补棋错过杀招。排查方法是把SCORES里FIVE和OPEN_FOUR的权重打印出来,确认五连分值远大于其他形态,再微调防守系数。另一个隐蔽原因是has_winner判断漏了斜向,导致 AI 看不到对手的斜线四连。
5.3 现象:搜索越深越慢,第 6 层直接卡住界面
纯 Python 递归在 15 路棋盘上深度一上去就吃不消,这是语言层面的限制,不是代码写错。解决方向有三个:一是限制候选点数量,只搜邻近棋子两格内的空位;二是加置换表缓存;三是把评估函数里重复的连线扫描改成增量更新。如果还嫌慢,可以考虑用numpy把棋盘运算向量化,但那是另一个量级的改造,课程设计阶段没必要。
5.4 现象:点击落子位置偏移一格
几乎都是pixel_to_grid里的margin和cell与graphics.py画线参数不一致导致的。排查时把点击坐标和换算出的行列打印出来,对照棋盘实际格子位置,偏差固定就调margin,偏差随位置放大就调cell。另外注意高分屏下tkinter可能有缩放,窗口实际像素和逻辑像素不一致,也会造成偏移。
5.5 现象:AI 偶尔走出「自杀」棋,送对手成五
这种棋通常是剪枝边界值传错,或者递归里忘了还原棋盘,导致评估的是被污染的局面。排查时在minimax入口打印当前depth、alpha、beta和棋盘哈希,看是否出现同一局面在不同深度被评估出矛盾分值。如果确认是还原问题,重点检查每个board[row][col] = 0是否和落子严格配对,break跳出循环前也要保证已还原。
6. 进阶调优:把搜索深度和评估精度拆开验证
想让这份 AI 从「能下」变成「下得还行」,关键是别把搜索深度和评估精度混在一起调。我的习惯是固定评估函数,只改深度,记录每个深度下 AI 对同一组测试局面的胜率;再固定深度,只改评估权重,看胜率变化。这样能分清到底是搜得不够深,还是评估函数本身不准。
一个具体技巧是构造「必堵局面」测试集:摆出对手活三、冲四、活四等固定棋型,让 AI 执先手,看它是否在一步内正确应对。下面是一个简单的断言式验证脚本:
def test_block_open_three(): """对手活三,AI 必须堵在延长线上""" board = [[0] * 15 for _ in range(15)] # 对手在 (7,5)(7,6)(7,7) 形成活三 for c in (5, 6, 7): board[7][c] = 1 move = best_move(board, depth=4) assert move in [(7, 4), (7, 8)], f"AI 未堵活三,实际走 {move}" print("活三防守通过")best_move是包一层根节点搜索、返回最优坐标的函数。这类测试跑起来很快,改完权重立刻能验证有没有把防守能力调坏。我一般会准备十来个这样的局面,覆盖活三、冲四、双三,每次调参后全跑一遍,比直接跟 AI 下棋判断靠谱得多。
还有个容易被忽略的点是开局库。纯搜索在开局阶段因为局面太开放,评估函数给不出明确倾向,AI 前几步往往走得随意。常见做法是手工塞几张开局表,前两手直接查表落子,把搜索留给中盘。这份资源里没有现成开局库,但references里的论文提到了开局处理思路,照着补一个几十条的小表并不难。
从那以后我每次拿到这类博弈搜索项目,都会先跑一遍必堵局面测试集再谈调参,不然改了半天权重,连最基本的活三都堵不住,后面全是白费功夫。希望帮到你。
本文还有配套的精品资源,点击获取