
简介一套基于pygame的Python小游戏合集将贪吃蛇、五子棋、扫雷和俄罗斯方块四款经典玩法组织在一起面向Python入门者与游戏开发学习者帮助解决从控制台脚本过渡到图形界面编程时的实战练习需求也适合作为课程设计或pygame入门实践素材。压缩包共29个文件包含6个py源码和18个bmp图片素材另有png运行截图、ttf字体与md说明文档素材与代码分离存放整体约182KB结构简单便于直接运行和二次修改。目前已有548人学习浏览。四款游戏分别示范了键盘监听与蛇身移动、棋盘落子与五连判断、雷区生成与逻辑推理、方块旋转与消除等核心编程点并完整覆盖游戏循环、事件处理、图片加载和界面刷新等pygame常用能力。通过阅读和调试这些代码还能了解带素材游戏项目的基本组织方式配合md说明快速定位各文件职责为独立完成小型游戏开发提供可复用的参考框架。1. 用 pygame 做四合一经典小游戏先拆开「复刻」和「重复造轮子」把贪吃蛇、五子棋、扫雷、俄罗斯方块放进一个 pygame 项目多数人第一反应是「四个游戏四份代码」。拆开看至少六成是重合的事件循环、时钟节拍、网格坐标换算、矩形绘制这些骨架从贪吃蛇搬到俄罗斯方块基本不用改。真正属于每个游戏的只剩一小块核心算法——贪吃蛇是方向队列扫雷是 BFS 翻格子俄罗斯方块是旋转矩阵加消行五子棋是四方向连珠判定。适合刚装好 pygame、想找能跑通又能扩展的项目当脚手架的新手也适合写过单游戏但没理过多游戏入口的老手。下面从安装报错讲起中间给可运行代码末章讲菜单切换和打包。2. pygame 安装与环境验证wheel 构建失败、import 不到模块的排查路径环境问题解决后才是代码问题。这一章先用最短路径把 pygame 装好并验明版本再处理绝大多数新手都会撞上的两三个报错每一条都给出可复制的命令。2.1 pygame 库怎么安装最小命令与版本确认当前 pygame 主线是 2.x对 Python 3.8 以上的常见平台都提供预编译 wheel正常情况下一条命令装完python -m pip install pygame装完别急着写代码先做版本验证确认解释器和包对上了号python -c import pygame; print(pygame.version.ver)能打出版本号比如 2.5.2说明安装成功。打印不出或报 ModuleNotFoundError 的按后面两节排查多半不是包本身的问题。提示始终用python -m pip而不是裸pip。Windows 上裸 pip 可能指向另一个 Python装完包却在当前解释器里 import 不到。2.2 安装时提示 failed to build pygame 的排查顺序完整报错通常是error: failed to build pygame when getting requirements to build wheel意思是 pip 在准备 wheel 阶段就打算走源码编译而不是下载现成的二进制包。出现这个报错先做一步升级 pip然后强制只接受预编译包。python -m pip install --upgrade pip python -m pip install --only-binary :all: pygame--only-binary :all:表示没有现成 wheel 就直接失败绝不编译源码。加了它仍然报错说明当前 Python 版本或平台确实没有对应 wheel常见于刚发布的 Python 新版本和 32 位解释器。pip 版本过旧是另一类原因pip 20.3 之前对 wheel 的优先级处理有缺陷会莫名退回源码编译所以升级 pip 这步不要省。如果机器上配了自定义 pip 镜像源偶尔会踩到下载了一半的坏包临时切回官方源再装一次可以排除这一项python -m pip install pygame -i https://pypi.org/simple真正要源码编译的场景多见于 Linux 或特殊平台先装 SDL2 相关系统库再装 pygamesudo apt install python3-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev。pygame 2.x 的 wheel 自带 SDL2 运行库桌面 Windows 和 macOS 基本走不到这条分支。2.3 常见报错对照python was not found、VSCode 解释器、pygame 手机版报错或现象常见原因处理error: failed to build pygame when getting requirements to build wheelpip 尝试源码编译升级 pip加--only-binary :all:强制 wheelpython was not found; run without arguments to install from the Microsoft Store触发 Windows 微软商店别名关闭系统「应用执行别名」改用 python.org 安装包ModuleNotFoundError: No module named pygame编辑器选了另一个解释器VSCode 里 CtrlShiftP 重新选择 Python 解释器python was not found这句单独说Windows 上执行的 python 如果指向商店占位程序就会看到这个提示它和 pygame 无关却最容易误导成「Python 没装好」。处理完别名问题回 2.1 再跑一遍版本确认。VSCode 里最典型的坑是「终端能 import编辑器里 ModuleNotFoundError」。VSCode 默认解释器未必是终端那个CtrlShiftP 打开命令面板搜 Python: Select Interpreter选 2.1 验证用的同一个。pygame 手机版是另一套环境Termux 下要先pkg install python libsdl2 libsdl2-image libsdl2-mixer libsdl2-ttf再 pip 安装手机端没有桌面平台的预编译 wheel必然走源码编译不想编译就用内置 pygame 的 Pydroid 3。手机端适合验证逻辑调试手感还是回桌面。3. 四个游戏共用的骨架事件循环、网格坐标与贪吃蛇实现四个游戏是四个独立主循环但骨架完全一致先学事件循环三件套再定网格坐标系最后拿贪吃蛇把整套结构跑通。这一章的代码同样适用于第 4 章的另外三个游戏。3.1 事件循环三段式取事件、更新、绘制pygame 的一切都发生在主循环里。每次迭代做三件事从事件队列取事件、更新游戏状态、把状态画到屏幕。最小骨架import pygame pygame.init() screen pygame.display.set_mode((800, 600)) pygame.display.set_caption(python小游戏集合) clock pygame.time.Clock() running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: pass # 方向键、功能键在这里分发 # 1. 更新移动、碰撞、计分都写在这里 # 2. 绘制先 fill 清屏再画网格和元素 pygame.display.flip() clock.tick(60) pygame.quit()pygame.event.get()每帧必须被调用否则操作系统会判定窗口失去响应拖动时出现白屏。clock.tick(60)把帧率锁在 60同时返回上一帧到这一帧的毫秒数第 4 章的下落计时就靠它。flip()把整块后备缓冲送到窗口小游戏用它足够update(rect_list)只刷新指定区域等性能不够再换。这四款小游戏是单线程事件循环模型不需要动多进程或协程逻辑集中在主循环里反而好调试。骨架里需要优先认识的三个事件类型事件触发时机在四个游戏里的用途pygame.QUIT点击窗口关闭按钮置 running False结束主循环pygame.KEYDOWN任意按键按下贪吃蛇方向、方块左移右移与旋转pygame.MOUSEBUTTONDOWN鼠标按键按下扫雷翻格/插旗、五子棋落子3.2 网格坐标换算把游戏当二维数组而不是像素画布贪吃蛇、五子棋、扫雷、俄罗斯方块的共同点逻辑都跑在网格上。稳妥的做法是逻辑里永远用 (col, row)只在绘制时换算成像素。这样碰撞检测、胜负判断全在整数网格上进行还能不开窗口直接测试逻辑。CELL 30 # 每个逻辑格对应 30 像素先定死再调 GRID_W, GRID_H 20, 20 def grid_to_pixel(col, row): return col * CELL, row * CELL def draw_cell(surface, color, col, row, margin1): x, y grid_to_pixel(col, row) pygame.draw.rect( surface, color, (x margin, y margin, CELL - 2 * margin) )draw_cell里的 margin 给相邻格子留一条缝扫雷和五子棋的棋盘感就是这么来的。以后就算要扩展一个 9×9 的三消游戏这套 col/row 换算也完全不用改换掉 GRID_W、GRID_H 再把「图」贴到换算出来的像素坐标上即可。3.3 贪吃蛇方向队列连按方向键时不回头、不翻车贪吃蛇最容易翻车的 bug 是「一帧内连按两个方向键」蛇头朝右时按上再按左如果事件处理里直接覆盖 direction蛇会在同一帧里掉头咬到自己。解法是维护一个方向队列每次移动只消费一个方向from collections import deque class Snake: def __init__(self, cols, rows): self.cols, self.rows cols, rows self.body deque([(cols // 2, rows // 2)]) self.direction (1, 0) # 初始向右 self.pending deque() # 待消费的方向 self.growing False def add_direction(self, d): last self.pending[-1] if self.pending else self.direction # 与最近一次入队方向相反则忽略防止 180 度掉头 if (d[0] last[0], d[1] last[1]) ! (0, 0): self.pending.append(d) def step(self): if self.pending: self.direction self.pending.popleft() head self.body[0] new_head (head[0] self.direction[0], head[1] self.direction[1]) if not (0 new_head[0] self.cols and 0 new_head[1] self.rows): return False # 撞墙 if new_head in self.body: return False # 撞自己 self.body.appendleft(new_head) if self.growing: self.growing False # 吃到食物尾巴先不收缩 else: self.body.pop() return Trueadd_direction的反方向判断对着pending[-1]而不是direction因为两个按键可能在同一个事件帧里先后入队只跟当前方向比会漏掉连按场景。主循环的 KEYDOWN 分支里把pygame.K_UP、K_DOWN、K_LEFT、K_RIGHT分别映射成 (0,-1)、(0,1)、(-1,0)、(1,0)调用snake.add_direction即可。移动频率不建议靠调clock.tick实现蛇每秒走几格和画面刷新率是两回事固定 60 帧、每 N 帧调一次step()更直观move_interval 6 # 每 6 帧动一格约每秒 10 格 frame 0 # 主循环 update 段 frame 1 if frame % move_interval 0: if not snake.step(): running False # 游戏结束分数越高把move_interval从 6 一路降到 4、3难度曲线就出来了。食物判定放在step返回后对比 head 是否等于食物坐标相等就把growing置 True同时重新生成食物生成位置随机且避开snake.body里的格子。渲染阶段按 body 里每个 (col, row) 画绿块、食物画红块贪吃蛇的最简版本到此完整可跑。4. 深入三个算法扫雷 BFS、俄罗斯方块旋转矩阵、五子棋连珠判定贪吃蛇验证了整体骨架剩下三个游戏分别考验区域扩散、几何变换和线型判定每个的难点都在各自的算法不在一遍遍重写的游戏循环里。4.1 扫雷首点安全的地雷生成与 BFS 翻开扫雷的难点不是布雷是「第一次点击不能踩雷」和「点空格连锁翻开一片」。首点安全的常见做法第一次点击后从点击格九宫格之外的候选格子里随机抽样布雷。import random def place_mines(rows, cols, safe_r, safe_c, count): cells [ (r, c) for r in range(rows) for c in range(cols) if abs(r - safe_r) 1 or abs(c - safe_c) 1 # 排除九宫格 ] return set(random.sample(cells, count)) def build_board(rows, cols, mines): board [[0] * cols for _ in range(rows)] for r, c in mines: board[r][c] -1 for dr in (-1, 0, 1): for dc in (-1, 0, 1): nr, nc r dr, c dc if 0 nr rows and 0 nc cols and board[nr][nc] ! -1: board[nr][nc] 1 return boardrandom.sample保证不重复mines 用 set 让「是否踩雷」的判断是 O(1)。build_board 先布雷再统一算数字比一边布雷一边加省一堆边界判断。连锁翻开用 BFS 而不是递归一个格子出队后数字为 0 就把八个邻居入队数字非 0 只翻开自己不再扩散from collections import deque def reveal(board, revealed, flagged, r, c): rows, cols len(board), len(board[0]) if revealed[r][c] or flagged[r][c] or board[r][c] -1: return queue deque([(r, c)]) while queue: cr, cc queue.popleft() if revealed[cr][cc]: continue revealed[cr][cc] True if board[cr][cc] ! 0: continue # 数字格只翻开不扩散 for dr in (-1, 0, 1): for dc in (-1, 0, 1): nr, nc cr dr, cc dc if (0 nr rows and 0 nc cols and not revealed[nr][nc] and not flagged[nr][nc]): queue.append((nr, nc))每格最多入队一次16×16 的棋盘最坏 256 次循环性能完全不是问题。右键插旗/拔旗用单独的flagged布尔表reveal开头挡住被插旗的格子扩散时也跳过 flagged 格子保证旗子不会因为连锁翻开被吞掉。鼠标事件里event.button 1走 reveal 3走插旗。地雷密度取总格子数的 15%20% 手感比较常规16×16 放 40 颗左右。胜利判定别数旗子直接判断「已翻开格数 总格数 - 地雷数」最保险。4.2 俄罗斯方块旋转矩阵、墙踢与消行方块用当前坐标的 (row, col) 列表表示绕原点顺时针旋转的变换是(r, c) - (c, -r)对 I、L、O、T、S、Z 全部形状成立def rotate_cw(points): return [(c, -r) for r, c in points]转完的坐标可能变负或超出棋盘所以不能直接覆盖位置要先试转。左右移动同理try_move给每个点加上位移后整体校验def try_move(piece, board, dpos): moved [(r dpos[0], c dpos[1]) for r, c in piece] return moved if is_valid(moved, board) else piece小项目不需要完整的 SRS 踢墙表按常见做法试四组偏移就够了def try_rotate(piece, board): rotated rotate_cw(piece) for dr, dc in ((0, 0), (0, -1), (0, 1), (-1, 0)): test [(r dr, c dc) for r, c in rotated] if is_valid(test, board): return test return piece # 各处都试过仍不行保持原形is_valid检查每个坐标是否在 0 ≤ row 20、0 ≤ col 10 内且目标格没有被已有方块占据。先原地、再左移、右移、上移这四组偏移覆盖了绝大多数贴墙旋转T 型块贴墙转不进缝的问题基本兜住。旋转键映射到try_rotate左移右移映射到try_move((0,-1))和try_move((0,1))。下落用毫秒计时器驱动别用帧计数drop_interval 500 # 初始下落间隔毫秒 last_drop pygame.time.get_ticks() # 主循环 update 段 now pygame.time.get_ticks() if now - last_drop drop_interval: moved try_move(piece, board, (1, 0)) if moved piece: # 下移失败固定并生成新块 lock_piece(piece, board) # 坐标写入 board 二维表 piece new_piece() # 棋盘顶部生成随机方块 else: piece moved last_drop now用get_ticks()而不是帧计数下落间隔和帧率解耦帧率波动不影响手感软降按住下键加速就是临时把drop_interval除以 10。消行有个简洁写法保留所有没满的行清掉的行统一补到顶部def clear_lines(board): kept [row for row in board if any(v 0 for v in row)] cleared len(board) - len(kept) board[:] [[0] * len(board[0]) for _ in range(cleared)] kept return clearedboard里 0 表示空格、非 0 表示已固定的方块或颜色编号。T-spin、连消奖励这类进阶判定需要额外记录「最后一次操作是不是旋转」集合版小游戏按消行数加分即可。4.3 五子棋只扫最后一子的四方向连珠检测五子棋的胜负检测有个天然优化新子落下后只有经过这一子的连线可能构成五连所以只从落子点向四个方向双向扫描不用全盘扫描def check_win(board, row, col, color): for dr, dc in ((1, 0), (0, 1), (1, 1), (1, -1)): count 1 for sign in (1, -1): # 沿直线正反两个方向数同色子 r, c row dr * sign, col dc * sign while 0 r 15 and 0 c 15 and board[r][c] color: count 1 r dr * sign c dc * sign if count 5: return True return False(1,0) 横线、(0,1) 竖线、(1,1) 和 (1,-1) 两条对角线四组方向对覆盖所有可能的五连。while里先判越界再访问 board避免 Python 负索引把棋盘另一端的子也算进来——board[-1][0]不报错但会误判这是最隐蔽的坑。落子前校验就三条坐标在 15×15 内、格子为空、轮到当前玩家。鼠标像素坐标x // CELL, y // CELL转格子位置落子容差由 CELL 决定点偏一点不会落错。禁手规则三三禁手、四四禁手需要另一套形状检测属于进阶扩展普通对战不加更接近大众玩法。4.4 四个游戏的核心算法与参数对照游戏逻辑网格输入方式核心算法最值得调的参数贪吃蛇20×20方向键方向队列 双端队列身体move_interval移动间隔五子棋15×15鼠标左键四方向连珠扫描CELL格子像素与落子容差扫雷16×16左键翻开 / 右键插旗BFS 区域翻开地雷数量总面积 15%20%俄罗斯方块10×20方向键 Z/X 旋转旋转矩阵 消行drop_interval下落毫秒数四个游戏的边界条件都集中在网格越界判断上贪吃蛇的 new_head、扫雷的邻居、俄罗斯方块的旋转结果、五子棋的扫描下标。把越界判断收敛成一个小函数in_grid(r, c)四个游戏的 bug 能少掉一半。5. 把四个小游戏合成一个入口菜单状态机与 PyInstaller 打包单个游戏能跑只是第一步标题里的关键词是「集合」四个游戏要共用一个入口还要能打包分给别人。5.1 菜单循环每个游戏是一个可调用函数简单可靠的方案不是场景栈而是每个游戏都写成接受(screen, clock)并自带主循环的函数菜单循环负责调度。状态流转是扁平的配合四个游戏的体量刚好GAMES { snake: snake_main, minesweeper: minesweeper_main, tetris: tetris_main, gomoku: gomoku_main, } def main(): pygame.init() screen pygame.display.set_mode((800, 600)) clock pygame.time.Clock() while True: choice menu(screen, clock) # 返回游戏名或 quit if choice quit: break GAMES[choice](screen, clock) # 游戏内按 ESC 结束自身 pygame.quit() if __name__ __main__: main()菜单返回游戏名靠鼠标点击区域判断游戏内部自己处理事件、绘制和退出按 ESC 或「返回菜单」时结束自身回到外层循环。这个约定的直接好处以后加第五个游戏比如 9×9 三消写一个match3_main(screen, clock)往 GAMES 里加一项即可。5.2 PyInstaller 打包 pygame 项目的两个注意点pyinstaller -F -w main.py --clean --collect-all pygame-F打单文件-w在 Windows 下不弹控制台--collect-all pygame收集 pygame 运行时字体等数据缺了它 exe 可能启动黑屏。用了图片或音效素材的项目注意单文件模式的资源路径运行时把sys._MEIPASS解压目录拼进资源路径开发环境用脚本所在目录兜底两套环境共用一份路径解析函数。验证分两步先不加-F打包目录模式确认无缺资源再打单文件拿到一台没有 Python 的机器上双击逐个进出菜单和四个游戏。本文还有配套的精品资源点击获取