基于Python pygame的俄罗斯方块课程设计:源码解析与运行避坑指南
简介基于Python pygame开发的经典俄罗斯方块小游戏课程实训资源包含完整源代码、说明文档与设计报告面向Python初学者、游戏开发爱好者及高校计算机相关专业学生可支撑课程作业、课程设计或项目初期的快速演示与学习。资源包内含6个文件共6.88MB核心为Python游戏主程序同时提供Markdown说明文档、docx设计报告、wav及mp3背景音乐和文本格式的分数记录文件代码、文档与多媒体素材齐全解压后即可按说明运行。该资源经测试功能稳定已有46人学习下载代码完整易复现在俄罗斯方块的生成、旋转、移动、消行与计分逻辑之上留出了扩展空间可继续增加关卡加速、暂停菜单或界面美化等功能。配套设计报告同样实用可作为课程实训报告的撰写参考也能帮助理解pygame游戏循环、碰撞检测、事件处理等核心机制无论是完成作业还是入门游戏开发都很有价值。1. 基于 Python pygame 的俄罗斯方块这份课程实训资源到底能帮你什么做课设的时候最怕的不是写不出代码而是写出来了却跑不起来或者跑起来了但答辩时说不清原理。这份「课程实训-基于Python pygame的经典俄罗斯方块小游戏开发含源代码及全部资料报告」就是冲着这个问题去的一个完整可运行的俄罗斯方块游戏tetris.py 主程序、素材、音乐、设计报告、说明文档一条龙配齐解压就能跑。如果你是计算机、人工智能、自动化、物联网这类专业的学生拿它做 Python 课程设计、pygame 入门作业甚至毕设的前期演示都很合适——因为它不是那种只有一堆零散函数的半成品而是把游戏主循环、七种方块形状、碰撞检测、消行计分、最高分存档都串起来的完整实现。这篇笔记我来拆一拆它的代码结构、运行方式和值得改写的切入点顺便把运行环境配置和几个高频坑说清楚。它适合两类人一是想快速交一份能演示的作业二是想把代码读懂后加自己的功能。2. 俄罗斯方块的骨架游戏循环、方块定义与碰撞检测很多人拿到源码第一反应是先双击运行但我建议先花半小时把 tetris.py 的主干读一遍。俄罗斯方块这类游戏代码再怎么变核心骨架永远是「游戏循环 状态数据 碰撞判定」三件事。读懂这三件事后面改代码才有底气。2.1 七种方块的数据结构与旋转矩阵俄罗斯方块的经典之处在于形状只有七种代码里常见的做法是为每种形状准备一个 4x4 的矩阵1 表示有方块0 表示空位。比如 T 形方块可以存成SHAPES { T: [ [0, 1, 0], [1, 1, 1], [0, 0, 0] ], I: [ [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0] ] }这里用字典存形状键是形状名值是二维矩阵。用二维矩阵而不是直接用像素坐标的好处是判断旋转和碰撞时只需要操作矩阵的行列索引不需要重新计算像素位置。T 形用 3x3 矩阵I 形用 4x4 矩阵大小不同没关系游戏里统一按 4x4 的边界框来做碰撞检测这样旋转逻辑可以共用一套代码。旋转操作在代码里通常是「先转置再翻转行」的矩阵运算Python 里一行就能写def rotate(shape): # 转置矩阵再把每一行反转实现顺时针旋转 return [list(row[::-1]) for row in zip(*shape)]zip(*shape) 把矩阵的行变成列完成转置row[::-1] 把每行反转合起来就是顺时针旋转 90 度。这里有个细节旋转后要重新检查碰撞旋转后的新位置如果撞到边界或已有方块就要放弃这次旋转保持原形状。判断顺序是「先旋转 → 再检测 → 不行就回滚」这个顺序新手经常写反。2.2 主循环三段式事件处理、逻辑更新、屏幕重绘pygame 游戏不管多复杂主循环一定是三个步骤循环执行处理输入事件、更新游戏状态、重绘画面。这份代码里的 while 循环基本长这样while not game_over: # 第一步事件处理 for event in pygame.event.get(): if event.type pygame.QUIT: game_over True elif event.type pygame.KEYDOWN: if event.key pygame.K_LEFT: move_left() elif event.key pygame.K_RIGHT: move_right() # 第二步逻辑更新按当前下落速度自动下落 if pygame.time.get_ticks() - last_drop_time drop_interval: move_down() last_drop_time pygame.time.get_ticks() # 第三步重绘画面 screen.fill(BLACK) draw_board() draw_current_piece() pygame.display.flip() # 控制帧率 clock.tick(60)事件处理要放在循环开头且集中处理不要在更新逻辑中间去 get() 事件否则会出现按键丢失或响应迟钝。drop_interval 是两次自动下落的时间间隔单位毫秒等级越高间隔越短这就是传统俄罗斯方块「难度上升」的实现方式。pygame.time.get_ticks() 返回的是程序启动以来的毫秒数用它判断是否到了该下落的时间比累加计数器更可靠。2.3 碰撞检测把方块映射到网格地图俄罗斯方块的棋盘是一个二维数组每个格子状态用 0 或 1 表示。碰撞检测的思路是把当前方块矩阵覆盖到棋盘数组上检查覆盖到的格子是不是已经被占用def check_collision(board, shape, offset): # offset 是 (row, col)表示方块左上角在棋盘上的位置 for r, row in enumerate(shape): for c, val in enumerate(row): if val: board_r offset[0] r board_c offset[1] c # 超出边界或者已有方块都算碰撞 if board_r 0 or board_r ROWS or board_c 0 or board_c COLS: return True if board[board_r][board_c]: return True return False这一段是整个游戏最值得反复读的代码。board 是棋盘二维数组shape 是方块矩阵offset 是方块左上角的坐标偏移。双重循环遍历方块矩阵中的每个有效格子换算到棋盘坐标后做边界判断和占用判断。注意边界判断要先做否则 board[board_r][board_c] 在越界时会抛 IndexError。当方块无法继续下落时就把它写进棋盘数组然后检查是否有完整行可以消除。3. 把源码跑起来环境配置、文件清单与运行流程代码读懂了接下来就是让它在自己电脑上跑起来。这个环节问题最多的是环境版本和文件路径不是代码本身的问题。3.1 Python 与 pygame 环境配置项目基于 Python推荐 Python 3.8 到 3.11 之间的版本pygame 用 2.x 系列。装完 Python 后打开命令行工具执行pip install pygame如果下载慢可以加国内镜像源pip install pygame -i https://mirrors.aliyun.com/pypi/simple/安装完成后可以快速验证python -c import pygame; print(pygame.version.ver)能打印出版本号说明环境就绪。需要注意:不要用 Python 3.12 以上版本跑老项目pygame 对 Python 3.12 的支持曾经有兼容性问题我这边的经验是 3.10 最稳。如果你用的是 Anaconda可以在 Anaconda Prompt 里先把环境建好再激活环境装包避免和系统 Python 打架。3.2 解压后文件清单与各自作用拿到压缩包后不要急着运行先看文件结构。完整的项目一般包含以下部分文件/目录作用tetris.py游戏主程序全部逻辑都在里面assets/music游戏背景音乐与音效文件scores.txt最高分存档文件首次运行前通常是空的design_report.docx课程设计报告供参考学习description.md项目说明文档运行最简单的方式是在项目目录下打开命令行执行python tetris.py如果想指定使用 Python 3.10 运行可以写成 python3.10 tetris.py。图省事的话把 tetris.py 的 import 部分看懂确认 assets 目录和 scores.txt 都在同级目录下基本就能直接跑。3.3 scores.txt 读写逻辑与路径问题scores.txt 是容易被忽略但坑最多的文件。游戏结束时会读取它来判断是否刷新最高分代码里一般这样处理import os score_file os.path.join(os.path.dirname(os.path.abspath(__file__)), scores.txt) def load_high_score(): if not os.path.exists(score_file): return 0 with open(score_file, r, encodingutf-8) as f: content f.read().strip() if content.isdigit(): return int(content) return 0用 os.path.dirname(os.path.abspath(file)) 拼接绝对路径是为了避免在不同目录下启动程序时找不到文件。很多初学者直接写相对路径 scores.txt然后在 IDE 里能跑换到命令行或双击运行就报错原因就是当前工作目录变了。保存最高分时同理def save_high_score(score): with open(score_file, w, encodingutf-8) as f: f.write(str(score))写入前把分数转成字符串读取时再转回整数中间加 strip() 去掉换行干扰。这里的教训是读写文件的路径永远用绝对路径拼接不要赌你的程序运行目录在哪。4. 玩法与逻辑的细节计分规则、下落速度、按键控制跑通一次之后你要对着代码把玩法和参数对应起来。课程设计答辩时老师最喜欢问的恰恰是消一行多少分速度怎么变快按键怎么控制这些问题答案全部藏在参数里。4.1 消行判定与计分设计棋盘上的行只要被方块填满这一整行就要被消除。代码里的典型写法def clear_lines(board): lines_cleared 0 new_board [row for row in board if any(cell 0 for cell in row)] lines_cleared ROWS - len(new_board) while len(new_board) ROWS: new_board.insert(0, [0] * COLS) return new_board, lines_cleared这个写法的思路很巧妙board 中所有「存在空格的行」保留下来其余满行自然被过滤掉。lines_cleared 计算的是被过滤掉的行数也就是消除行数。然后在新棋盘最上方补零行把棋盘恢复成完整高度。用列表推导式解决消行问题比逐行遍历再 delete 和 insert 更简洁。计分配置一般是这样的参数表:消除行数得分说明1 行100基础得分2 行300连击奖励3 行600阶梯递增4 行1000消四行的最高奖励这个计分逻辑在源码里面会表现为一个 reward 列表根据 lines_cleared 索引取分。4.2 等级系统与下落速度的参数关系难度递增是俄罗斯方块的灵魂。代码里通常维护一个 level 变量每消除一定数量行就升一级下落间隔随之缩短。示例参数base_interval 1000 # 初始下落间隔单位毫秒即 1 秒 min_interval 100 # 最快下落间隔防止无限提速导致完全无法玩 level_up_lines 10 # 每消除 10 行升一级 def get_drop_interval(level): interval base_interval - (level - 1) * 80 return max(interval, min_interval)base_interval 是初始值 1000 毫秒每升一级减少 80 毫秒用 max() 限定下限 100 毫秒。如果你的目标是交作业这段参数就是最好的「可调整点」——把 80 改成 50游戏会温柔很多演示时不容易翻车把 80 改成 120难度曲线就会陡峭起来。答辩时被问哪块代码控制难度指这两个参数解释一下比背定义有说服力得多。4.3 按键控制与状态机切换游戏界面需要响应的不仅是左右移动和旋转还有暂停、重启。代码里一般用一个状态变量管理游戏阶段:GAME_STATES [READY, RUNNING, PAUSE, GAME_OVER] state READY if event.key pygame.K_p and state RUNNING: state PAUSE elif event.key pygame.K_p and state PAUSE: state RUNNING elif event.key pygame.K_q and state GAME_OVER: reset_game()用字符串状态而不是零散的 boolean 标志位代码可读性高很多。注意按键事件在 pygame 里分为 KEYDOWN 和 KEYUP移动时要处理 KEYDOWN 触发和键盘持续按住两种模式。持续按住用 pygame.key.get_pressed() 更好keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: move_left() if keys[pygame.K_DOWN]: move_down()这里需要区分KEYDOWN 事件适合旋转、暂停这类「点按」操作get_pressed() 适合左右移动、加速下落这类「持续」操作。两个混着用手感才正常这是个体验细节。5. 避坑指南运行与改装过程中的高频问题拿到资源后大概率你会遇到下面几个问题我把现象、原因和解决步骤写清楚。按照这个顺序排查大多数情况能在十分钟内恢复运行。5.1 现象双击运行后窗口一闪就消失原因Python 脚本运行到一半报错退出或者主循环正常结束后 pygame 窗口关闭太快你根本来不及看到报错信息。解决不要在桌面双击运行而是打开命令行定位到项目目录执行 python tetris.py。如果报错终端会显示完整异常信息。如果是代码逻辑问题可以在 Python 文件末尾加 try/except 把异常写进日志if __name__ __main__: try: main() except Exception as e: with open(error_log.txt, w, encodingutf-8) as f: f.write(str(e))这是最实用的排查手段能看到报错内容问题就解决了一半。千万不用怕报错pygame 项目的报错信息一般都很直白。5.2 现象一直报找不到 scores.txt 或 assets 目录原因程序使用了相对路径而当前工作目录不是项目目录导致路径解析失败。解决把代码里所有文件访问改成基于脚本文件绝对路径的拼接参考前面给出的 os.path.dirname(os.path.abspath(file)) 写法。改完之后无论从哪个目录启动都不会出问题。同理音乐和音效文件路径也要用绝对路径拼接。5.3 现象汉字显示成方块或者代码注释乱码原因pygame 默认字体不包含中文字符集或者源码文件编码不是 UTF-8。解决如果你要给界面加中文标题需要加载一个支持中文的字体文件比如把 Windows 自带字体放到项目目录里再加载:font pygame.font.Font(simhei.ttf, 24)源码文件的编码问题用 IDE 打开时右下角确认是 UTF-8把 scores.txt 的读取编码统一成 utf-8一般就不会乱码。5.4 现象按键响应时有延迟或者偶尔失灵原因事件处理逻辑写在了阻塞操作之后或者两个事件循环互相冲突。常见的错误写法是在 update 和 draw 之间穿插 time.sleep导致事件积压。解决事件处理集中在主循环最前面确保 update 和 draw 阶段不再获取事件。避免使用 time.sleep 控制下落改用 pygame.time.get_ticks() 配合间隔时间判断这样事件循环不会被阻塞。5.5 现象音乐文件播放不出来但代码没报错原因pygame.mixer 没有完成初始化或者音频文件格式与平台不兼容。pygame 2.x 对 mp3 的兼容性在不同系统上有差异有些环境只支持 WAV 和 OGG。解决在 pygame.init() 之后单独初始化 mixerimport pygame pygame.mixer.init() pygame.mixer.music.load(assets/music/background.wav) pygame.mixer.music.play(-1) # -1 表示循环播放如果你的素材是 mp3 但播放异常用格式工厂或 ffmpeg 转成 wav 格式再加载这是最省事的方案。6. 把这份课设做出区分度三个可扩展方向与验证清单基础代码跑通、报告也写完了如果你想拿一个更高的分数或者以后想往游戏开发方向走我建议在这份源码上做小改造。三个方向按性价比排序加方块预览与下一个方块提示、加暂停菜单与音效开关、把最高分改成 Top7 排行榜。第一个改动最小在界面右上角渲染一个 4x4 区域把随机队列中下一个形状画进去。你在控制当前方块的同时能看到下一个是什么这是俄罗斯方块的标配功能代码改动量大概 40 行。第二个改动需要引入一个选项状态变量控制背景音乐是否播放能用得上 pygame.mixer.music.set_volume。第三个改动最有分量:把 scores.txt 从单条最高分改成多行榜单涉及读写、排序、UI 渲染三个模块非常适合在答辩时讲解从单一功能到完整功能的设计过程。验证清单按顺序过一遍基础运行 5 分钟不崩溃消行计分符合参数表连按旋转键不越界下落速度随等级变化最高分能正确写入并在重启后恢复窗口缩放或点击关闭按钮不报错。全部通过这份课设就达到了「能演示、能讲清、能答辩」的状态。最后说个我自己的习惯:以前写游戏课设我也吃过亏代码跑通就交结果答辩时老师问等级升到多少会死循环我在台上愣住了。从那以后我每次改完课设代码都会把关键参数打印到控制台验证一遍比如把 level 调到 20 跑 10 分钟确认速度曲线正常、分数不会越界。你拿到这份源码后也建议先做一遍这个冒烟测试心里有底答辩才不慌。希望帮到你。本文还有配套的精品资源点击获取