☰
Python+PyQt5五子棋毕设:深度搜索与α-β剪枝实战
2026/10/10 21:28:07 网站建设 项目流程

简介:这是一份面向计算机相关专业学生与AI爱好者的毕业设计级五子棋项目源码,基于Python与PyQt5实现完整的人机博弈对弈界面,核心算法涵盖深度优先搜索与α-β剪枝优化,适合作为人工智能、博弈树搜索方向的学习范例,也可直接用于毕设、课程设计或项目立项演示。资源包共17个文件,包含4个Python源码文件、1个Qt界面文件与1个资源文件,另有9张png界面截图、1个ico图标及README说明文档,压缩包约5.63MB,结构清晰便于快速上手。目前已有213人学习下载。项目代码均经过实际运行测试,答辩评审平均分达96分,读者可从中掌握PyQt5界面搭建、五子棋胜负判定、极大极小搜索与剪枝策略的完整实现思路,并在此基础上修改扩展功能,如调整评估函数或增加难度等级,是学习博弈AI与桌面应用开发的实用参考。

1. 从一份能跑通的毕设说起:Python+PyQt5 五子棋到底交付了什么

很多计算机专业的同学在选毕设题目时都会卡在同一个地方:想做 AI 方向,又怕算法太深写不出来;想做桌面应用,又担心没有技术含量。这份基于 Python+PyQt5 的多智能体博弈五子棋,恰好卡在两者的交叉点上——它既有一个能看得见摸得着的图形界面,又在底层塞进了深度搜索和 α-β 剪枝这套博弈树的核心逻辑。拿到手的第一感觉是:这不是那种只跑个 print 的玩具代码,而是一个从 UI 到 AI 决策完整闭环的小型工程。

它解决的问题很具体:让你在答辩时能指着屏幕说“这里 AI 走这一步,是因为它搜索了未来 N 层,并且剪掉了哪些废分支”。适合的人群也很明确——计科、人工智能、通信、自动化等专业的在校生,需要一份能讲清楚原理、又能现场演示的毕设或课设;也适合刚学完 Python 基础、想找一个有界面有算法的项目来练手的进阶者。整个包里有start.py作为入口,gobang_ui.py负责界面,AI.py承载博弈逻辑,gobang_qrc_rc.py是资源文件,结构清晰,没有故弄玄虚的封装。

2. 环境搭建与首次运行:把 PyQt5 和资源文件理顺

2.1 为什么是 PyQt5 而不是 Tkinter

选 PyQt5 做五子棋界面,不是因为它时髦,而是因为棋盘绘制这件事对控件刷新和事件响应有硬要求。Tkinter 画 15×15 的网格加棋子,重绘时容易闪烁,而且鼠标点击坐标和棋盘交叉点的映射要自己写不少换算。PyQt5 的QPainter配合QWidget的paintEvent,可以做到局部刷新,棋子落下的动画和悬停提示都更自然。另一个现实原因是:这份代码的 UI 文件是用 Qt Designer 生成的,gobang_ui.py里能看到典型的setupUi结构,如果你后续想改布局,直接拖控件比手写 Tkinter 布局代码快得多。

安装 PyQt5 本身不复杂,但版本要卡一下。Python 3.8 到 3.10 配 PyQt5 5.15.x 是经过大量项目验证的组合,太新的 Python 3.12 在某些系统上装 PyQt5 会碰到 wheel 缺失的问题。

# 建议在虚拟环境里操作,避免污染全局包 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS/Linux 激活 source venv/bin/activate # 安装 PyQt5,指定 5.15 系列 pip install PyQt5==5.15.9

这里指定5.15.9是因为这个版本对pyrcc生成的资源文件兼容性最稳。代码里的gobang_qrc_rc.py就是pyrcc5把.qrc资源编译后的产物,里面通常打包了棋盘背景、棋子图标、favicon 等。如果你拿到的是源码包,这个文件已经生成好了,不需要自己再跑pyrcc5;但如果你改了.qrc里的图片,就得重新编译。

2.2 入口文件与启动顺序

start.py是整个程序的入口,它做的事情通常就三件:创建QApplication、实例化主窗口、进入事件循环。但这份代码里有一个容易忽略的点——AI.py的导入时机。如果AI.py在模块顶层就初始化了搜索树或者加载了棋型评估表,那么启动时会有一小段延迟。我一般会先单独跑一次AI.py里的测试函数,确认评估逻辑不报错,再启动界面。

# start.py 的典型结构(根据项目文件推断) import sys from PyQt5.QtWidgets import QApplication from gobang_ui import GobangWindow # 主窗口类 if __name__ == '__main__': app = QApplication(sys.argv) window = GobangWindow() window.show() sys.exit(app.exec_())

逻辑说明:QApplication管理整个 GUI 程序的生命周期,GobangWindow继承自QMainWindow或QWidget,在它的__init__里会调用setupUi完成界面搭建,同时连接棋盘控件的鼠标点击信号到落子槽函数。sys.exit(app.exec_())保证窗口关闭时进程干净退出。参数方面,sys.argv直接透传即可,这个项目没有用到命令行参数。

运行命令就是最朴素的:

python start.py

如果报ModuleNotFoundError: No module named 'PyQt5',说明虚拟环境没激活或者 pip 装到了别的解释器里。用which python(Linux/macOS)或where python(Windows)确认当前解释器路径,再pip list看一眼 PyQt5 在不在列表里。

3. 深度搜索与 α-β 剪枝:AI.py 里的博弈树怎么搭

3.1 棋型评估:给每个局面打分

五子棋 AI 的核心不是搜索本身,而是评估函数。搜索只是在有限时间内找到评估分最高的走法,如果评估函数把活三当成死三,搜得再深也是白搭。这份代码里AI.py通常会定义一个棋型分值表,常见做法是用模式匹配来识别“活四”“冲四”“活三”“眠三”等。

# AI.py 中棋型评估的简化示意 SCORE = { 'FIVE': 100000, # 五连 'LIVE_FOUR': 10000, # 活四 'RUSH_FOUR': 1000, # 冲四 'LIVE_THREE': 1000, # 活三 'SLEEP_THREE': 100, # 眠三 'LIVE_TWO': 100, # 活二 'SLEEP_TWO': 10, # 眠二 } def evaluate_point(board, x, y, role): # 以 (x, y) 为中心,向四个方向统计连子情况 # 返回该点对 role 方的分值贡献 total = 0 for dx, dy in [(1,0), (0,1), (1,1), (1,-1)]: count = 1 block = 0 # 正向统计 for step in range(1, 5): nx, ny = x + dx*step, y + dy*step if not in_board(nx, ny): block += 1 break if board[nx][ny] == role: count += 1 elif board[nx][ny] == 0: break else: block += 1 break # 反向统计同理... total += score_of(count, block) return total

逻辑说明:evaluate_point不是全局评估,而是评估“在某个空点落子后,该点周围形成的棋型价值”。这种增量评估在搜索里很常用,因为每走一步只需要看落子点附近的变化,不用重算整个棋盘。参数role区分黑白,block记录被对方堵住的端数,count是连子数。score_of根据连子数和封堵情况查表返回分值。

这里有个血泪经验:很多同学直接把LIVE_THREE和RUSH_FOUR设成一样的分,结果 AI 会优先去堵冲四而不去形成自己的活四,因为冲四的威胁看起来更急。实际上活四的胜率远高于冲四,分值必须拉开一个数量级。

3.2 极大极小搜索与 α-β 剪枝的落点

AI.py里搜索部分一般会有一个minimax或negamax函数,配合alpha和beta两个边界值。α-β 剪枝的本质是:在极大层,如果当前节点的评估值已经超过了祖先节点已知的 β 上界,那这个分支后面的内容就不用看了,因为对手不会让你走到这一步。

def alphabeta(board, depth, alpha, beta, is_max, role): if depth == 0 or game_over(board): return evaluate_board(board, role) if is_max: max_eval = -float('inf') for x, y in generate_moves(board): board[x][y] = role eval_score = alphabeta(board, depth-1, alpha, beta, False, role) board[x][y] = 0 max_eval = max(max_eval, eval_score) alpha = max(alpha, eval_score) if beta <= alpha: break # β 剪枝 return max_eval else: min_eval = float('inf') for x, y in generate_moves(board): board[x][y] = 3 - role # 对手落子 eval_score = alphabeta(board, depth-1, alpha, beta, True, role) board[x][y] = 0 min_eval = min(min_eval, eval_score) beta = min(beta, eval_score) if beta <= alpha: break # α 剪枝 return min_eval

逻辑说明:is_max为真时轮到 AI 自己走,它要最大化评估值;为假时轮到对手,对手会最小化 AI 的评估值。alpha是当前路径上极大层已经找到的最好值,beta是极小层已经找到的最坏值。当beta <= alpha时,说明这个分支不可能影响最终决策,直接break。generate_moves是走法生成函数,常见优化是只考虑已有棋子周围两格内的空点,而不是全盘 225 个点,这样搜索宽度能降一个数量级。

参数depth控制搜索层数。这份毕设里通常设 4 到 6 层,再深的话 Python 的递归开销会让界面卡住。如果你在答辩时想演示“更深搜索更强”,可以临时把depth调到 6,但要提前跟评委说明会有几秒延迟。

4. 多智能体博弈的界面联动:落子、悔棋与状态同步

4.1 棋盘绘制与鼠标事件映射

gobang_ui.py里的棋盘通常是一个自定义QWidget,在paintEvent里画网格和棋子。鼠标点击时,mousePressEvent拿到的是像素坐标,需要换算成棋盘交叉点的行列索引。这个换算的精度直接决定落子准不准。

# gobang_ui.py 中坐标映射的典型写法 CELL_SIZE = 40 MARGIN = 30 def pixel_to_grid(self, px, py): # 将像素坐标转换为棋盘行列 col = round((px - MARGIN) / CELL_SIZE) row = round((py - MARGIN) / CELL_SIZE) if 0 <= row < 15 and 0 <= col < 15: return row, col return None, None

逻辑说明:MARGIN是棋盘边缘留白,CELL_SIZE是格子间距。用round而不是整除,是因为点击位置很少正好落在交叉点上,四舍五入能吸附到最近的交叉点。如果发现落子总是偏一格,先检查MARGIN和CELL_SIZE是否和paintEvent里画网格时用的值一致——这两个地方各写一套常量是新手最常见的翻车点。

4.2 人机交替与悔棋的实现

人机博弈的流程控制一般放在主窗口类里:玩家点击后,先判断该位置是否为空、是否轮到玩家,然后落子并刷新界面;接着调用AI.py的搜索函数拿到 AI 的走法,再落子刷新。悔棋则是把最近两步(玩家一步、AI 一步)从棋盘上撤掉,同时把历史记录栈弹出。

def on_click(self, row, col): if self.board[row][col] != 0 or self.game_over: return if self.current_player != self.human_role: return self.place_stone(row, col, self.human_role) if self.check_win(row, col, self.human_role): self.show_result("你赢了") return self.current_player = self.ai_role # 调用 AI 搜索 ai_row, ai_col = self.ai.get_best_move(self.board, self.ai_role) self.place_stone(ai_row, ai_col, self.ai_role) if self.check_win(ai_row, ai_col, self.ai_role): self.show_result("AI 赢了") return self.current_player = self.human_role

逻辑说明:place_stone负责更新棋盘数组、往历史栈里压入记录、触发update()重绘。check_win只需要检查刚落子的点向四个方向延伸是否形成五连,不用全盘扫描。悔棋时从历史栈弹出两条记录,把对应位置置零,并切换current_player回人类。注意:如果 AI 搜索耗时较长,界面会短暂无响应,常见做法是在搜索前把鼠标事件禁用,或者用QTimer.singleShot把 AI 走子延后到下一帧,让界面先刷新出玩家的棋子。

5. 避坑与排查:从环境到算法的五个真实翻车点

5.1 现象:运行start.py报ImportError: DLL load failed

原因:Windows 上 PyQt5 依赖的 Qt 动态库没有正确加载,通常是因为 Python 版本和 PyQt5 wheel 不匹配,或者系统缺少 Visual C++ 运行库。解决:先确认 Python 是 3.8~3.10 的 64 位版本,然后卸载重装pip install PyQt5==5.15.9。如果还不行,装一下 VC++ 2015-2022 运行库。别去网上找那些来路不明的 DLL 替换,越换越乱。

5.2 现象:棋盘画出来了,但点击没反应

原因:自定义棋盘控件的mousePressEvent没有正确绑定,或者控件被上层透明 widget 挡住了。解决:在gobang_ui.py里检查棋盘控件是否设置了setMouseTracking(True),以及mousePressEvent里是否调用了super().mousePressEvent(event)导致事件被吞。另一个可能是paintEvent里画的棋盘区域和实际控件大小不一致,点击落在了控件外面。

5.3 现象:AI 第一步思考很久,后面反而快

原因:搜索深度固定时,开局阶段棋盘空点最多,generate_moves产生的分支数最大,α-β 剪枝在根节点附近效果最弱。解决:在AI.py里给开局加一个简单的启发式——如果棋盘上棋子少于 4 个,直接走中心点或者中心附近的随机点,不走完整搜索。另外把generate_moves限制在已有棋子周围两格内,能显著减少开局分支。

5.4 现象:AI 明明能赢却去堵对方

原因:评估函数里防守分和进攻分的权重没调好,或者搜索时没有把“自己形成五连”的分数设得足够高。解决:检查SCORE表里FIVE是否远大于其他所有棋型之和。另一个常见错误是在evaluate_board里对双方分别评估后简单相减,但减的时候没有考虑轮到谁走——如果轮到 AI 走,AI 的活四应该比对手的活四权重更高。

5.5 现象:悔棋后 AI 走法变了

原因:悔棋只恢复了棋盘数组,但没有恢复 AI 内部的搜索缓存或随机种子。如果AI.py里用了random来做同分走法的随机选择,每次搜索的结果可能不同。解决:悔棋时把历史栈完整回滚,并且如果 AI 有置换表或缓存,一并清空。如果希望 AI 走法可复现,在start.py里固定random.seed(42)。

6. 进阶调参与验证:让 AI 强度可量化、可演示

把 AI 调到一个“看起来聪明”的状态,比把代码跑起来更花时间。我一般会用一个简单的自对弈脚本来验证搜索深度和评估函数的效果,而不是靠肉眼盯着棋盘猜。

# 自对弈验证脚本,放在项目根目录单独运行 from AI import AI from copy import deepcopy def self_play(depth_black=4, depth_white=2, rounds=10): black_win = 0 for _ in range(rounds): board = [[0]*15 for _ in range(15)] ai_black = AI(depth=depth_black) ai_white = AI(depth=depth_white) turn = 1 # 1 黑,2 白 while True: ai = ai_black if turn == 1 else ai_white x, y = ai.get_best_move(board, turn) board[x][y] = turn if ai.check_win(board, x, y, turn): if turn == 1: black_win += 1 break turn = 3 - turn print(f"黑方(深度{depth_black})胜率:{black_win}/{rounds}") if __name__ == '__main__': self_play(depth_black=4, depth_white=2, rounds=10)

逻辑说明:这个脚本让不同搜索深度的 AI 互相对弈,深度 4 的黑方如果胜率明显高于深度 2 的白方,说明搜索加深确实带来了棋力提升,评估函数没有把搜索方向带偏。rounds设 10 是因为每局搜索耗时可能几秒到十几秒,跑太多轮不现实。如果黑方胜率低于 7/10,优先检查评估函数里活三和冲四的分值是否合理,而不是继续加深度。

参数调整上,depth每加 1,搜索节点数大约翻 3 到 5 倍(有了 α-β 剪枝后),但棋力提升不是线性的。我一般会做一组对比:

搜索深度平均每步耗时对深度 2 的胜率适用场景
2< 0.1 秒基准演示、低配机器
40.5~2 秒约 80%答辩默认
63~10 秒约 95%性能演示,需提前说明延迟

如果答辩现场机器性能一般,建议默认用深度 4,并在界面上加一个“思考中”的提示标签,避免评委以为程序卡死。深度 6 可以作为“展示搜索威力”的按钮,点之前先口头说明会有几秒等待。

还有一个容易被忽略的验证点:gobang_qrc_rc.py里的资源路径。如果你把项目拷贝到另一台机器,图片路径如果是绝对路径就会失效。检查这个文件里qt_resource_data的引用方式,确保用的是相对路径或者 Qt 资源系统内部的:/前缀。我吃过一次亏,在实验室机器上跑得好好的,换到答辩教室的电脑上图标全没了,就是因为资源文件里写死了C:\Users\xxx\...。从那以后我每次拿到带_rc.py的项目,都先全局搜一遍盘符和用户目录名,确认没有硬编码路径才继续往下调。

希望这份拆解能帮你把这份五子棋毕设跑通、讲清、调出效果。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询