☰
多智能体博弈AI五子棋:Python+PyQt5实现与α-β剪枝搜索详解
2026/10/2 2:46:08 网站建设 项目流程

简介:这份毕业设计资源提供基于Python+PyQt5的多智能体博弈AI五子棋游戏完整项目,涵盖人机博弈、深度搜索与α-β剪枝算法,代码含详细注释,适合计算机相关专业学生用于毕业设计、课程设计或期末大作业参考。压缩包共17个文件,包含4个py源码、1个ui界面文件、1个qrc资源文件、9个png图片、1个ico图标及1份md说明文档,整体大小5.39MB,其中py文件实现主程序与AI逻辑,ui/qrc定义界面布局与资源,md说明文档辅助快速上手,结构清晰,简单配置环境即可运行。目前已获338人学习浏览,具有一定的参考热度。借助代码注释和文档说明,读者可快速理解棋盘UI、事件响应与AI决策流程,掌握深搜剪枝在博弈树优化中的实际写法,也可按自身需求扩展界面或算法,直接部署用于演示与答辩;该系统界面简洁、操作方便,适合直接迁移到课程设计或毕设展示场景。

1. 多智能体博弈 AI 五子棋游戏:毕设骨架里真正值钱的是 AI.py

每年到毕设和课程设计节点,最怕的不是功能做不完,而是交上去的东西要么是纯控制台程序,要么 AI 只会随机堵棋。这个基于 Python + PyQt5 的多智能体博弈 AI 五子棋游戏,把界面、入口和搜索逻辑拆成三层:start.py 负责启动,gobang_ui.py 画棋盘和接鼠标事件,AI.py 用深搜加 α-β 剪枝决定电脑落哪。它解决的是「人机博弈怎么写才像个 AI 作品」这件事:15×15 网格界面、鼠标落子、胜负判定、带注释的搜索代码一次性给齐。适合两类人——拿它当毕设、课程设计骨架,把界面风格和难度参数改成自己的设计;或者刚接触博弈树,想找一份能读得动的五子棋 AI 源码对照实现。拆完这套源码的直观感受是:新手能部署,熟手能改参数。AI 不是乱堵,而是真的在算后续几步的得失,这一点在同类项目里算做得比较扎实的。

2. 拆开三层结构:start.py 入口、gobang_ui.py 界面与 AI.py 博弈核心

把这份源码下载解压后,文件夹名一般带个-main后缀,这是下载平台自动加的,不影响运行,直接进到文件夹里面就行。我拆项目习惯先看文件结构,而不是急着双击运行,因为这类带界面的博弈程序,运行顺序、包的引用关系比代码本身更容易让人迷失。

2.1 文件清单:先知道谁负责什么

文件职责常规处理
start.py程序入口,创建窗口和 AI 实例一般不改
gobang_ui.py棋盘绘制、鼠标事件、胜负提示改界面布局时动
AI.py候选点生成、评估函数、α-β 剪枝搜索调难度主要改这里
gobang_qrc_rc.pyPyQt5 资源编译产物,界面里被 import 引用别手动改,换图标才碰
designer界面设计文件,Qt Designer 用想改布局时用 pyuic5 转换
favicon.ico窗口图标源文件可替换成自己的
README.md运行说明看一眼即可

我拿到项目的第一件事是先理 import 关系,而不是直接跑。这个项目的引用链一般是 start.py 引入 gobang_ui 和 AI,gobang_ui 里引入 gobang_qrc_rc 拿图标和样式资源。只要所有文件待在同一个根目录,在项目根目录执行python start.py就能起来。很多人直接把文件拖到 IDE 根目录外运行,相关模块一个都找不到,这是这类项目最常见的启动失败原因。

2.2 start.py:入口文件只做三件事

常见做法是入口文件只做三件事:创建 QApplication、创建 AI 实例、创建并显示主窗口。按这个项目的结构,可运行的骨架长这样:

import sys from PyQt5.QtWidgets import QApplication from gobang_ui import GobangWindow from AI import AIPlayer if __name__ == "__main__": app = QApplication(sys.argv) ai = AIPlayer(depth=4) # 搜索深度,后面调难度就是改这个 window = GobangWindow(ai) # 把 AI 实例传给界面 window.show() sys.exit(app.exec_())

逻辑说明:depth=4是 AI 往后想的层数,相当于自己走两步、对手走两步后再判断局面好坏。窗口拿到 ai 对象后,鼠标点击时由界面调用 ai 的落子接口。把 AI 实例创建放在入口而不是写死在界面里,是为了将来换搜索策略时不用动界面代码。参数说明里真正值得关注的就两个:depth决定棋力和耗时,window.show()之前的所有代码跑得再慢也只影响启动速度。

2.3 gobang_ui.py:棋盘网格与鼠标事件

界面文件的核心是两件事:画棋盘、处理鼠标事件。一般会用 QMainWindow 做窗口基类,paintEvent 里画网格线和棋子,mousePressEvent 里做落子。下面这段是这个项目典型的结构:

class GobangWindow(QMainWindow): def __init__(self, ai): super().__init__() self.ai = ai self.cell_size = 40 # 每格像素 self.margin = 30 # 棋盘左边距/上边距 self.board = [[0] * 15 for _ in range(15)] # 0空 1黑(玩家) 2白(AI) def mousePressEvent(self, event): pos = event.pos() col = round((pos.x() - self.margin) / self.cell_size) row = round((pos.y() - self.margin) / self.cell_size) if not (0 <= row < 15 and 0 <= col < 15): return if self.board[row][col] != 0: return self.board[row][col] = 1 self.update() # 先刷新,把玩家棋子画出来 ai_x, ai_y = self.ai.think(self.board, player=2) self.board[ai_y][ai_x] = 2 self.update() # 再刷新 AI 棋子

坐标换算用了round而不是int:像素点落在格子边界附近时,round 更接近用户眼睛看到的交叉点。注意 board 的下标是board[row][col],也就是先 y 后 x,这个顺序一旦搞反,棋子会沿棋盘对角线镜像。另外这里用的是event.pos().x()而不是event.x(),后者在 PyQt5 里虽然能用,但已经标记弃用,新写法更干净。调用 think 是同步的,界面会等 AI 算完才刷新第二次,这是演示项目的常见做法,卡顿问题我在第 4 章专门讲。

2.4 资源文件与 designer:改界面时的两条路径

gobang_qrc_rc.py 和 designer 这两个东西,初学者容易误删。gobang_qrc_rc.py 是 PyQt5 的 rcc 工具把 qrc 资源文件编译出来的 Python 模块,窗口图标、样式表都靠它生效,所以界面文件里会 import 它。想换图标,正确做法是替换 favicon.ico 然后重新编译资源,而不是手动改 rc 文件。如果 designer 是.ui格式,想改界面布局的常见路径是装好 PyQt5 后用命令转:

pyuic5 designer.ui -o designer.py

转出来的 designer.py 供 gobang_ui.py 调用。如果只是交作业展示功能,这两个文件可以完全不碰,整个项目最值得你花时间的只有一个:AI.py。

3. 深搜与 α-β 剪枝:AI 落子决策链路与关键参数

AI.py 是整个项目里最值钱的部分。标题里的「多智能体博弈」落到代码上,就是两个决策者轮流向棋盘写子,AI 开放的核心接口think(board, player)里的 player 就是智能体编号,1 是玩家,2 是 AI。这个文件承担三件事:生成候选点、评估局面、剪枝搜索。我拆开讲。

3.1 为什么是深搜 + α-β 剪枝而不是贪心

最朴素的五子棋 AI 是贪心:找一个当前局面得分最高的空位直接落子。缺点很明显——它可能看到下一步能活三,却看不到再下一步对手会反活四。深搜的做法是把未来若干步的局势都推演出来,再往回倒推选最优。α-β 剪枝是深搜的加速器:倒推过程中,一旦某个分支已经能确定不会比当前最优结果更好,就立刻停止展开,把整棵子树丢掉。

五子棋不剪枝的复杂度很吓人:不限制候选点时,深度 4 的搜索量是 225×224×223×222,约 25 亿个局面。加上 α-β 剪枝和候选点限制后,实际展开的节点数通常只剩几千到几万个,这就是这个算法对五子棋最核心的价值。α 代表己方能保底的下界,β 代表对方能压制的上界,当 α ≥ β 时说明当前分支已经不可能成为最优解,剪掉即可。

3.2 评估函数:让 0/1/2 的棋盘变成分数

搜索到叶子节点后,程序需要知道局面好坏。评估函数把棋盘状态映射成一个数字,我按这类项目的常见实现整理了一个评分思路:对每个位置分别统计横、竖、两条对角线四个方向的连子信息,赋予不同分数,再求和。

# 评估权重示意,AI.py 里一般是常量 SCORES = { "five": 1000000, # 五连,直接赢 "live_four": 100000, # 活四,两端都通 "rush_four": 10000, # 冲四,一端被堵 "live_three": 8000, # 活三,再一步变活四 "sleep_three": 1000, # 眠三 "live_two": 500, # 活二 }

AI 的进攻分用己方棋子的连子数求和,防守分用对手棋子的连子数求和,两者加权相减。权重比是调整棋风的关键:防守分乘 1.1,AI 就明显更爱堵;乘 0.8,AI 更爱自己发展进攻。

提示:评估函数不追求绝对精确,只追求相对排序正确。它只需要保证好局面分数一定高过坏局面,搜索才会往正确方向走。

3.3 候选点生成:先筛再搜,不然剪枝也救不了

深度再小,225 个点的每层展开也扛不住。常见做法是只搜已有棋子半径 2 以内的空位,棋盘候选从 225 降到二三十个,搜索量直接降一个数量级。我一般建议 radius=2,兼顾速度与不漏棋;radius=1 更快,但偶尔会漏掉关键进攻点。候选点生成按这个思路写:

def gen_candidates(board, radius=2): occupied = [(x, y) for y in range(15) for x in range(15) if board[y][x] != 0] if not occupied: return [(7, 7)] # 空棋盘固定天元 cand = set() for x, y in occupied: for dx in range(-radius, radius + 1): for dy in range(-radius, radius + 1): nx, ny = x + dx, y + dy if 0 <= nx < 15 and 0 <= ny < 15 and board[ny][nx] == 0: cand.add((nx, ny)) return list(cand)

参数说明:radius是邻居半径,双重循环覆盖半径内所有格子;set 自动去重,防止多个邻居重复加入同一个候选点。空棋盘特判返回(7, 7)是必须的,否则返回空列表,AI 第一步直接没子可下——这个边界是新手最容易漏掉的地方。

3.4 剪枝主循环:把决策倒推回来

核心搜索是这个项目 AI 的引擎。下面是按 Minimax + α-β 标准写法整理的主循环,AI.py 里实际代码的变量名可能不同,但结构一致:

def alpha_beta(self, board, depth, alpha, beta, is_max): winner = self.check_winner(board) if winner == self.me: return 10000 + depth # 早赢一步更好 if winner == self.opponent: return -10000 - depth if depth == 0: return self.evaluate(board) candidates = self.gen_candidates(board, radius=2) if is_max: # 己方回合,想拿最大分 best = -float("inf") for x, y in candidates: board[y][x] = self.me best = max(best, self.alpha_beta(board, depth - 1, alpha, beta, False)) board[y][x] = 0 # 回溯,恢复棋盘 alpha = max(alpha, best) if beta <= alpha: break return best else: # 对手回合,想拿最小分 best = float("inf") for x, y in candidates: board[y][x] = self.opponent best = min(best, self.alpha_beta(board, depth - 1, alpha, beta, True)) board[y][x] = 0 beta = min(beta, best) if beta <= alpha: break return best

外面的 think 接口负责遍历每一个候选点,假设自己先落子,然后交给内层搜索评估对手最优回应下的局面:

def think(self, board, player=2): self.me = player self.opponent = 3 - player candidates = self.gen_candidates(board, radius=2) best_score = -float("inf") best_move = candidates[0] for x, y in candidates: board[y][x] = self.me score = self.alpha_beta(board, self.depth - 1, -float("inf"), float("inf"), False) board[y][x] = 0 if score > best_score: best_score = score best_move = (x, y) return best_move

深搜里有个容易被忽略的细节:递归前board[y][x] = self.me,递归后立刻board[y][x] = 0,这叫回溯。如果忘了恢复现场,后面所有候选点的评估都建立在错误棋局上,AI 会表现得「时而正常时而抽风」,排查时极难定位。alpha、beta 初值用正负无穷,表示第一层还没有任何约束。

4. 避坑与排查:从 PyQt5 装不上到 AI 卡死、落子漂移的五个问题

跑通这个项目本身不难,真正花时间的往往是环境问题。下面五条是这个场景下最容易翻车的地方,也是我拆项目时大概率会遇到的,按现象到原因到解决拆开写。

4.1 环境与资源文件的坑

坑 1:import PyQt5 直接报 ModuleNotFoundError

现象:pip 装完 PyQt5 后,运行 start.py 第一行就报错找不到 PyQt5,或者提示找不到 Qt platform plugin。原因:解释器不对。电脑里有多个 Python,或 VSCode 打开项目后右下角解释器还指在系统默认,没指向装包那个环境。解决:进项目根目录建虚拟环境再装包:

python -m venv venv venv\Scripts\activate # Windows 激活 pip install pyqt5

VSCode 里按 Ctrl+Shift+P 执行 Python: Select Interpreter,选中 venv 下的解释器;PyCharm 里则是 File → Settings → Project → Python Interpreter 切到同一个虚拟环境。pip 下载慢的话加国内镜像源,这个跟项目本身无关但极其影响体验。

坑 2:报 No module named 'gobang_qrc_rc'

现象:界面文件 import 资源模块时直接报错,或者程序能跑但窗口图标、样式全部丢失。原因:gobang_qrc_rc.py 和 start.py 不在同一目录,或者 IDE 把运行目录设到了子文件夹,模块找不到。解决:把工作目录切到项目根目录,确认这个文件就在 start.py 旁边。如果文件本身缺失,用 PyQt5 自带的资源编译器重新生成:

pyrcc5 resources.qrc -o gobang_qrc_rc.py

resources.qrc 换成你手头实际的资源文件名。注意这类 rc 文件本质是编译产物,手动改里面内容是没用的。

4.2 交互与 AI 行为的坑

坑 3:点击棋盘,棋子落在偏移一格的位置

现象:鼠标点在交叉点上,棋子却出现在对角或旁边一格。原因:mousePressEvent 里的坐标换算少减了 margin,或者把行列顺序搞反,board[row][col] 写成了 board[col][row]。这个坑极其隐蔽,因为程序不报错,只是棋子位置全错。解决:先单独打印一次 event 坐标和换算结果,对照棋盘实际格子;然后坚持 board 的第一个下标是行(y 方向),第二个下标是列(x 方向),全项目统一,别混用。

坑 4:AI 思考时窗口假死,转圈几秒才恢复

现象:点击后窗口白屏或转圈,等 AI 落子后才恢复。原因:AI 深搜是 CPU 密集计算,界面同步调用了 think,Qt 事件循环被长时间阻塞。解决:演示场合最简单的兜底是落子前先执行QApplication.processEvents(),把玩家那颗棋子先画出来再进入计算;想彻底解决就把 think 丢进 QThread,用信号把落子坐标传回主线程。这个项目默认同步调用,不影响正确性,但答辩演示时体验很掉价。

坑 5:AI 第一步不落子或极慢

现象:空棋盘你先落子,AI 卡住不动,或很久之后才落一个无关位置。原因:候选点生成没对空棋盘特判,在 225 个空格上全量展开搜索,节点数直接爆表;另外首步没有中心偏好,搜索结果不稳定。解决:gen_candidates 开头加if not occupied: return [(7, 7)],AI 首步固定天元。再把候选点按邻居半径筛选后限制数量,比如每层只取分数最高的前 15 个,搜索耗时立刻降下来。

5. 调参定手感:搜索深度、候选点上限与评估权重怎么配

AI 棋风在别人嘴里像玄学,其实就两个旋钮:权重差值和搜索深度。这套项目真正会玩的人改的也主要是三个参数——搜索深度、候选点数量、评估权重。其余代码基本不用动,动了反而容易出边界问题。

5.1 搜索深度:depth 与耗时、棋力的折中

depth=2 时 AI 只能看自己下一步、对手下一步,会堵活三但不会主动造杀棋;depth=4 是大多数毕设项目的甜点位,能看到冲四活三的连续交换;depth=6 在这个评估函数下能走出像样的战术,但候选点多时明显卡顿。下面耗时区间是按这套算法结构的节点量级估算的,普通笔记本、top_n=15 左右:

depth每层候选点单步耗时棋力表现
215毫秒级反应快,只会堵
315数百毫秒会主动做活三
4151~3 秒能看出冲四交换
615十秒以上有攻击意图,但体验差

注意深度增加一层,节点数不是翻倍而是指数级增长。depth 从 4 到 6 看起来只差两,耗时会差几十倍。所以答辩演示时把 depth 调到 4、候选点上限压到 10,是比较稳的组合,既能看到 AI 的计算痕迹,又不会让老师等太久。

5.2 评估权重:想让 AI 变激进还是变保守

权重表在第 3 章给了基础版,实际调的时候记住一条经验:进攻分拉大与普通棋形的差值,AI 就更激进;防守分乘系数,AI 就更保守。比如把 live_four 提到 500000,活四和五连的分差变小,AI 会更愿意把冲四走到底;把 rush_four 调高,AI 对对手的冲四更敏感。常见误用是把所有分数等比例放大,结果棋风没变,反而可能因为数值过大出现诡异的整型问题。改权重要改的就是相对大小,不是绝对大小。我一般还会在评估函数出口加一步归一化:分数除以当前己方棋子数,避免残局阶段因为棋子多导致分数虚高。

5.3 候选点上限:控制搜索规模的最直接手段

gen_candidates 返回的候选列表按评估分数排序,只取前 N 个进入搜索,N 就是候选点上限。这个参数比 depth 更能控制耗时:depth=4 时,候选点 30 个和 10 个,耗时会差近一个数量级。推荐组合写在这里:depth=4、radius=2、top_n=10,日常演示够用;想做更有攻击性的展示,可以 depth=5、top_n=14,前提是机器别太老。这三个参数一起调,比单独改权重对棋力提升更明显,因为搜索广度决定了 AI 能不能看到关键的那一手。

6. 打印棋盘验证 AI 行为:答辩和调试都好用的一个习惯

现在各种 AI 编程助手确实能快速生成界面,但搜索树这段我还是建议自己手写一遍,因为答辩时老师问的就是这块。调试这种带界面的博弈程序,最怕黑匣子式改代码:改了权重,AI 突然乱走,你却只能隔着界面猜。我养成的习惯是先把棋盘 dump 出来再谈调参。给 AI.py 或者单独建一个 debug.py,放一个打印函数:

def dump_board(board): icon = {0: "·", 1: "●", 2: "○"} for row in board: print(" ".join(icon[c] for c in row))

调用方式很简单:在 AI.think 之前打印一次,在返回落子之后再打印一次,两次对比就能看清 AI 落子前后的棋盘变化。配合第 4 章的坐标验证,几乎能定位所有「AI 看起来乱走」的问题——大多数时候不是权重错,而是坐标读反了,或者候选点生成漏了空棋盘特判。答辩演示时这个习惯更实用,老师问 AI 为什么走这个点,你直接把打印出来的候选点和对应分数贴进讲稿,比空口说 α-β 剪枝有说服力得多。

从那以后我每次跑这类博弈项目,都会强制走一遍固定流程:先打印空棋盘首步,确认不崩;再摆一个必输局面,确认 AI 会堵;最后才调权重和深度。这一套流程下来,再没出过「平时正常、答辩翻车」的尴尬。希望帮到你。

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

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

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

立即咨询