ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python五子棋源码:pygame双人对战与AI改造实战

2026/10/7 10:26:36 拓冰建站 浏览量
Python五子棋源码:pygame双人对战与AI改造实战 简介这份 Python 五子棋小游戏源码面向刚接触 Python 图形界面与游戏逻辑的编程学习者也适合作为课程设计、练手项目或教学演示的参考素材。压缩包共 6 个文件以 3 个 py 源码文件为主另含 2 个 pyc 编译文件与 1 个可直接运行的 exe 程序整体约 7.75MB源码与可执行文件并存便于对照阅读与快速体验。内容围绕棋盘绘制、落子判定、胜负检测等核心模块展开从预览可见 checkerboard、Gomoku 等模块划分结构清晰适合逐层拆解五子棋的界面刷新、坐标映射与连珠判断思路。已有 375 人学习下载可作为入门 Python 游戏开发、理解事件循环与二维数组应用的实践样本也能在此基础上扩展悔棋、人机对战等功能。1. 五子棋 Python 源码一个能跑、能改、能交作业的完整小游戏如果你正在搜「Python 游戏源码」大概率不是想从零学 pygame 文档而是想找一个能直接跑起来、代码结构清楚、方便改的小项目。这份五子棋源码就是干这个的一个基于 pygame 的本地双人对战五子棋包含棋盘绘制、落子判定、五连胜负检测、悔棋和重新开始。它不依赖网络、不依赖数据库装好 Python 和 pygame 就能运行适合刚学完 Python 基础、想拿一个完整项目练手的人也适合需要交课程设计、想快速看懂一个游戏主循环怎么写的同学。下面我按「拿到源码后怎么跑通、代码怎么组织、参数怎么调、哪里容易翻车」的顺序拆一遍你照着走基本不会卡住。2. 环境准备与源码结构从解压到第一次落子2.1 运行前必须确认的三件事五子棋源码本身不复杂但它对运行环境有硬性要求装错了就是黑屏或者直接报 ModuleNotFoundError。我一般拿到任何 Python 游戏源码先确认三件事Python 版本、pygame 是否安装、源码目录结构是否完整。Python 版本建议 3.8 及以上。3.8 是很多教程和库的兼容基线源码里用到的 f-string、类型注解在 3.6 之后都支持但 3.8 更稳。查看版本python --version # 或者在某些系统上 python3 --version如果输出是 Python 3.8.x 以上继续。如果提示命令不存在说明 Python 没装或者没加进环境变量先去官网下载安装包安装时勾选「Add Python to PATH」这一步漏了后面全是坑。pygame 是这份源码唯一的外部依赖。安装命令pip install pygame装完验证一下能打印版本号才算成功import pygame print(pygame.version.ver)如果 pip 下载慢或者报错常见做法是换国内镜像源比如清华源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple源码目录一般长这样主程序文件比如gomoku.py或main.py、可能有一个images或assets文件夹放棋子图片、一个README。如果只有单个.py文件那更简单直接进目录运行即可。运行命令python gomoku.py窗口弹出来、棋盘画出来就说明环境这关过了。2.2 源码的模块划分与主循环逻辑这份五子棋源码虽然不大但结构是完整的通常分成四块常量配置、棋盘数据结构、绘制函数、事件主循环。看懂这四块你就能改任何地方。常量配置一般在文件开头定义棋盘格数、格子像素大小、窗口宽高、颜色值。比如BOARD_SIZE 15 # 15x15 标准五子棋棋盘 CELL_SIZE 40 # 每个格子 40 像素 MARGIN 40 # 棋盘四周留白 WINDOW_SIZE BOARD_SIZE * CELL_SIZE MARGIN * 2 BLACK (0, 0, 0) WHITE (255, 255, 255) LINE_COLOR (0, 0, 0)BOARD_SIZE决定棋盘是 15 路还是 19 路CELL_SIZE决定窗口大小和点击精度。这两个参数一改整个界面尺寸跟着变后面绘制和坐标换算都依赖它们所以改的时候要一起改不能只动一个。棋盘数据结构通常是一个二维列表board [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] # 0 表示空1 表示黑子2 表示白子用 0/1/2 而不是字符串是为了判定胜负时做数值比较更快也方便扩展 AI。落子就是把对应位置改成 1 或 2。绘制函数负责把二维数组画成屏幕上的棋盘和棋子。核心是把数组下标 (row, col) 换算成屏幕像素坐标def board_to_pixel(row, col): x MARGIN col * CELL_SIZE y MARGIN row * CELL_SIZE return x, y这个换算关系是整份源码的骨架点击落子时反向换算绘制棋子时正向换算两边必须一致否则就会出现「点这里、子下到那里」的经典 bug。主循环是 pygame 程序的标准写法running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.MOUSEBUTTONDOWN: x, y event.pos col round((x - MARGIN) / CELL_SIZE) row round((y - MARGIN) / CELL_SIZE) if 0 row BOARD_SIZE and 0 col BOARD_SIZE: if board[row][col] 0: board[row][col] current_player if check_win(row, col, current_player): print(玩家, current_player, 获胜) running False current_player 3 - current_player # 1 和 2 互换 draw_board() pygame.display.flip()pygame.event.get()一次取走所有事件MOUSEBUTTONDOWN里拿到的event.pos是窗口像素坐标减掉边距再除以格子大小、四舍五入就得到最近的交叉点行列号。round而不是int是因为玩家点击很难正好落在交叉点上四舍五入能吸附到最近的点手感更好。current_player 3 - current_player是个小技巧1 变 2、2 变 1比写 if-else 简洁。胜负检测check_win是这份源码里最值得读的函数它从当前落子点出发沿四个方向数连续同色棋子横、竖、左斜、右斜。每个方向往两边各数一次加起来够 5 就赢。这个逻辑后面第 4 章会展开讲因为它是改 AI 和对战规则的基础。3. 核心机制拆解落子、胜负判定与悔棋实现3.1 胜负检测的四个方向与边界处理五子棋判定胜负本质是问刚下的这颗子在横、竖、两条斜线上有没有形成连续五个同色。实现时最容易翻车的地方是数组越界和方向数重复。常见写法是定义一个方向列表DIRECTIONS [(0, 1), (1, 0), (1, 1), (1, -1)] # 分别代表水平、垂直、主对角线、副对角线对每个方向从当前点向正负两个方向延伸计数def check_win(row, col, player): for dr, dc in DIRECTIONS: count 1 # 当前这颗子本身算一个 # 正方向 r, c row dr, col dc while 0 r BOARD_SIZE and 0 c BOARD_SIZE and board[r][c] player: count 1 r dr c dc # 负方向 r, c row - dr, col - dc while 0 r BOARD_SIZE and 0 c BOARD_SIZE and board[r][c] player: count 1 r - dr c - dc if count 5: return True return Falsewhile条件里同时判断边界和颜色顺序不能反。如果先访问board[r][c]再判断边界当 r 或 c 越界时 Python 会抛 IndexError程序直接崩。把边界判断放前面利用短路求值越界时后面的颜色比较根本不会执行。count 5而不是 5是因为标准五子棋里长连六连及以上也算赢。如果你要做「正好五连才算赢」的变体规则这里改成 5即可但要注意长连情况下会漏判需要额外处理。方向列表只写四个而不是八个是因为每个方向的正负两边都数了写八个会重复计数。这是新手改代码时最常见的错误加了八个方向结果三颗子就被判成六连。3.2 悔棋功能的数据结构设计悔棋看起来简单做起来要考虑「悔一步还是悔两步」。双人对战里点一次悔棋通常应该退回上一步让当前玩家重新下。实现方式是用一个栈记录落子历史history [] # 每个元素是 (row, col, player) # 落子时 board[row][col] current_player history.append((row, col, current_player)) # 悔棋时 if history: r, c, p history.pop() board[r][c] 0 current_player p # 把出棋权还给刚才那个人用列表当栈append入栈、pop出栈天然后进先出。悔棋时把棋盘对应位置清 0再把current_player设回被撤销的那一手这样界面和状态就一致了。这里有个容易忽略的点悔棋后要重绘棋盘否则屏幕上还留着那颗子但数据已经没了下次点击同一位置会出问题。所以悔棋处理完必须调一次draw_board()。如果要做「悔两步」撤销自己和对方各一手就连续 pop 两次但要判断 history 长度够不够否则空栈 pop 会报 IndexError。常见做法是if len(history) 2: for _ in range(2): r, c, p history.pop() board[r][c] 0 current_player history[-1][2] if history else 1最后一行是恢复当前该谁下逻辑是「看历史里最后一手是谁下的下一手就轮到对方」如果历史空了就默认黑棋先手。3.3 重新开始与状态重置重新开始不是简单清空棋盘就完事要把所有相关状态一起重置棋盘数组、落子历史、当前玩家、游戏结束标志。漏掉任何一个都会出现「棋盘清了但轮到白棋先下」或者「赢了之后重开还是判赢」的玄学问题。def restart(): global board, history, current_player, game_over board [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] history [] current_player 1 game_over False注意board要用列表推导重新生成不能写board [[0] * BOARD_SIZE] * BOARD_SIZE。后者生成的是同一行的引用改一行全变这是 Python 二维数组的经典坑血泪经验。判断方法很简单改board[0][0]如果board[1][0]也跟着变就是用错了。game_over标志用来在胜负已分后屏蔽落子事件否则玩家还能继续下把已经结束的棋局搞乱。在主循环的点击处理里加一句if game_over: continue就行。4. 避坑与常见问题排查跑不起来先看这几条4.1 窗口一闪而过或直接退出现象双击运行或命令行执行后窗口闪一下就没了没有任何报错。原因主循环没有正确保持或者pygame.quit()和sys.exit()被提前调用。常见于把事件循环写在函数里但忘了调用或者while条件一开始就是 False。解决确认主循环是while running:且running初始为 True确认pygame.init()在创建窗口之前调用如果用了sys.exit()检查它是不是被放在了循环外面。可以在pygame.quit()前加一句print(exit)看是否真的走到了退出逻辑。4.2 点击落子位置偏移现象鼠标点在交叉点上棋子却下到了旁边一格越往棋盘边缘偏得越厉害。原因坐标换算公式和绘制公式不一致或者MARGIN、CELL_SIZE在两个地方用了不同的值。常见于绘制时用了硬编码数字而点击换算用了常量。解决把坐标换算抽成一个函数绘制和点击都调它保证只有一处定义。检查round和int的使用点击吸附应该用round如果用int直接截断靠近格子左边和上边的点击会偏到前一格。4.3 胜负判定误报或漏报现象明明只有四颗子却判赢或者五颗子连成一线却没反应。原因方向数重复导致 count 虚高或者边界判断顺序错误导致漏数了边缘的棋子。漏报还可能是check_win只在落子后调用但落子位置传错了行列。解决打印每次check_win的count值看四个方向分别数了多少。如果某个方向明显偏大检查方向列表是不是写成了八个。边缘漏报就检查while条件里边界和颜色的顺序确保先判边界。另外确认调用时传的是row, col而不是col, row行列颠倒会让判定完全错位。4.4 pygame 安装成功但 import 报错现象pip install pygame显示成功但import pygame提示找不到模块。原因机器上有多个 Python 版本pip 装到了 A 版本运行用的是 B 版本。或者虚拟环境没激活。解决用python -m pip install pygame代替直接pip install这样能保证装到当前python对应的环境里。然后用python -c import pygame; print(pygame.__file__)确认模块路径。如果用了 venv 或 conda先激活环境再装。4.5 中文显示成方块现象如果源码里有中文提示文字窗口里显示成一个个方块。原因pygame 默认字体不支持中文。解决加载系统里的中文字体比如 Windows 下的simhei.ttf或msyh.ttcfont pygame.font.Font(C:/Windows/Fonts/simhei.ttf, 24)或者用pygame.font.SysFont(simhei, 24)。路径写错会直接报错确认字体文件存在。跨平台的话把字体文件放进源码目录一起分发更稳。5. 进阶改造把双人对战改成带 AI 的版本5.1 用评分函数给每个空位打分双人对战跑通之后很多人想加个电脑对手。最直接的做法不是上深度学习而是给每个空位算一个分选最高的落子。评分逻辑就是假设我在这里下能形成几连同时假设对手在这里下能形成几连两者加权。def evaluate_point(row, col, player): score 0 for dr, dc in DIRECTIONS: count 1 # 数自己这边 for direction in (1, -1): r, c row dr * direction, col dc * direction while 0 r BOARD_SIZE and 0 c BOARD_SIZE and board[r][c] player: count 1 r dr * direction c dc * direction score count ** 2 # 连得越多分越高平方拉开差距 return score def ai_move(player): opponent 3 - player best_score -1 best_pos None for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if board[r][c] 0: attack evaluate_point(r, c, player) defend evaluate_point(r, c, opponent) total attack * 1.2 defend # 进攻权重略高 if total best_score: best_score total best_pos (r, c) return best_poscount ** 2是让四连的分远高于三连逼 AI 优先堵活四。attack * 1.2 defend里的 1.2 是进攻系数调大 AI 更激进调小更保守。这个系数没有标准答案我一般从 1.0 到 1.5 之间试看对局手感。这个评分 AI 的边界很明显它只看单点局部不会算两步之后的棋遇到双三、冲四活三这种组合会漏。但对一个练手项目来说够用而且代码短、好懂、好改。5.2 用极小化极大搜索提升棋力如果嫌评分 AI 太弱可以加一层搜索。极小化极大Minimax的思路是我下完之后假设对手也选最优应对往前看 N 步选一个最坏情况下最好的落子。配合 Alpha-Beta 剪枝深度 3 到 4 层在 15 路棋盘上还能接受。def minimax(depth, is_max, alpha, beta): if depth 0 or game_over: return evaluate_board() if is_max: best -float(inf) for r, c in get_empty_points(): board[r][c] ai_player val minimax(depth - 1, False, alpha, beta) board[r][c] 0 best max(best, val) alpha max(alpha, best) if beta alpha: break return best else: best float(inf) for r, c in get_empty_points(): board[r][c] human_player val minimax(depth - 1, True, alpha, beta) board[r][c] 0 best min(best, val) beta min(beta, best) if beta alpha: break return bestget_empty_points不要返回全部空位只返回已有棋子周围两格内的空位否则搜索空间爆炸深度 3 都要算很久。这是实战里必须做的剪枝不然程序会卡到你以为死机了。evaluate_board是整盘棋的评分可以简单地把所有己方棋子的evaluate_point加起来减去对方的。搜索深度每加一层耗时大概翻几倍深度 4 在普通笔记本上单步可能要一两秒体验会变差所以深度和评分函数的复杂度要平衡。5.3 改造时的验证方法改完 AI 别急着说「能跑就行」至少验证三件事AI 会不会下在已经有子的位置说明空位判断漏了、AI 面对活四会不会堵说明防守权重不够、AI 自己有成五机会时会不会抓说明进攻评分没生效。我一般会手动摆几个固定局面给 AI 一个冲四看它堵不堵给它一个活三看它理不理给它自己一个四连看它会不会直接成五。这三个场景过了基本逻辑就没大问题。剩下的棋力强弱靠多下几盘调系数没有捷径。从那以后我每次拿到一份游戏源码都强制先跑通默认配置、再动一行代码确认基线能复现之后才开始改。这样出了问题能立刻分清是源码本身的坑还是我改出来的坑。希望这份拆解帮到你源码拿去跑一遍比看十篇教程都实在。本文还有配套的精品资源点击获取