
简介一个用Python实现的俄罗斯方块小游戏完整源码包面向Python初学者、游戏开发入门者及相关课程学生目标是帮助大家通过实战项目掌握Pygame库的事件处理、图形绘制、键盘输入与基础游戏循环设计同时获得可直接运行体验的完整游戏。资源内共收录4个文件包含1个Python主程序、2个wav格式的游戏音效文件和1个mp3背景音乐文件压缩包整体大小约6MB路径简洁、文件结构清晰解压后即可获得全部核心代码与配套素材。目前该资源已有322人学习下载代码中完整实现了方块生成、旋转变换、横向移动、加速下落、整行消除和分数累计等核心机制并在方块消除、游戏结束两个关键节点内置了不同音效提示配合背景音乐让反馈更直观。读者既可以对照源码理解碰撞检测、坐标映射、随机出现等经典算法细节也可以直接在此基础上进行二次开发例如增加难度分级、最高分排行榜、暂停功能或自定义主题非常适合作为课程设计、期末项目、实验报告配套或Python入门练手素材。 说实话学 Python 的路上如果没有亲手写过一两个小游戏总觉得差点意思。俄罗斯方块是我个人非常推荐的上手项目逻辑清晰、反馈即时、成就感强而且它几乎覆盖了 Python 编程里最核心的几个知识点——循环、列表、函数、类、事件处理、状态管理。这篇文章就把我从零写出一个可运行的俄罗斯方块的全过程拆开讲从设计思路到具体代码再到我实际调试时踩过的坑全部分享出来希望能帮正在练手的你省点时间。这篇文章适合谁刚学完 Python 基础语法、想找项目练手的新手或者想用 pygame 做小游戏但不知道怎么入门的读者。我尽量把每一步都讲透代码也能直接复制运行你可以先照着跑通再根据自己的想法去改。1. 先理清思路俄罗斯方块的核心机制与代码结构设计很多新手一上来就急着写代码结果写到一半逻辑乱了然后弃坑。我的习惯是先把游戏机制在脑子里过一遍想清楚“这个程序到底要管理哪些数据”再动手。1.1 拆解游戏的核心机制俄罗斯方块表面上看起来简单但程序层面其实要处理这样几件事一个棋盘10 列 × 20 行、一个正在下落的方块、一个已经落定并堆在底部的方块集合、下落计时、消行判定、得分与结束条件。几乎所有的实现方案底层都是同一个思路用一个二维数组表示棋盘每个格子要么是空记作 0要么被方块占据记作非 0 的颜色值。当前下落的方块本质上是另一个小尺寸的二维数组它每帧或每隔一段时间向下移动一格直到撞到棋盘底部或已经落定的方块。这样做的好处是碰撞检测、消行判断、重新绘制这些操作都变得非常直观。你不需要去处理什么复杂的图形碰撞算法只需要操作二维数组的坐标就行。1.2 为什么选 pygame 而不是其他方案俄罗斯方块的 Python 实现方案其实有好几种纯控制台用 print 输出、用 tkinter 画界面、用 pygame 做游戏窗口。我强烈建议用 pygame原因有三个。第一pygame 自带游戏循环、帧率控制、键盘事件监听这些正是游戏开发里最基础也最通用的东西你能在最短时间内感受到“做一个游戏”的完整流程。第二它的绘图接口非常简单画矩形用pygame.draw.rect加载字体用pygame.font.Font不需要像 tkinter 那样理解复杂的布局系统。第三社区资源极其丰富几乎你能想到的任何小游戏都有 pygame 案例遇到问题一搜就有答案。注意pygame 本身只是 Python 的一个第三方库它没有内置到 Python 里所以安装是必须的步骤。这个后面会细说。1.3 代码结构怎么组织在前几年我写这种小游戏习惯把代码全部堆在一个文件里因为简单直接跑起来方便。随着项目变大再拆模块也不迟。这篇文章给的版本就是单文件结构约 200 行主要分成这样几个部分常量定义窗口尺寸、棋盘行列数、格子大小、颜色值。方块形状定义用嵌套列表存储 7 种方块的初始形态。工具函数旋转矩阵、碰撞检测、消行处理。游戏主循环监听键盘事件、控制下落节奏、刷新绘制。这种分层方式很像做菜分步骤先把食材备好常量再处理主菜形状数据最后起锅烧油按顺序炒主循环。代码读起来清晰出 bug 也容易定位。2. 环境搭建从零跑通 Python 与 pygame 环境这部分不是废话很多新手卡在第一步就是环境没搭好。我见过太多人代码写得没问题结果因为 pygame 没装上、或者装错了版本而抓狂。2.1 Python 安装与验证如果你电脑上还没装 Python先到官网python.org下载对应系统的安装包。装的时候务必勾选“Add Python to PATH”这个选项不然后续在命令行里执行 python 命令会提示找不到。装好后打开终端Windows 上是 CMD 或 PowerShellmacOS / Linux 上是 Terminal输入python --version如果看到类似Python 3.12.x的输出说明安装成功。如果提示找不到命令大概率是 PATH 没配好重新安装一遍记得勾选那个选项。注意 Python 2 已经停止维护一定要装 3.x 版本。2.2 安装 pygame 并验证在终端里执行pip install pygame如果你在下载阶段发现速度特别慢或者直接超时可以用国内镜像源加速pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完之后在 Python 交互式环境里验证一下import pygame pygame.init() print(pygame.version.ver)能正常输出版本号就说明 pygame 环境已经准备好了。这里有个小坑python 解释器版本和 pygame 的兼容性。Python 3.13 刚发布那阵子部分 pygame 版本还没提供对应的 wheel 包安装会失败。解决办法是装 Python 3.10 或 3.11 这类稳定版本或者升级 pygame 到最新版。3. 代码实现一步步写出可运行的俄罗斯方块环境就绪后我们直接进入核心部分。我会按代码的依赖顺序分块讲最后给出完整可运行的整合代码。3.1 常量定义与窗口初始化游戏的第一步是用常量把棋盘尺寸和颜色固定下来。为什么要用常量而不是直接写死数字因为后续调整游戏难度或界面大小时你只需要改一个地方不需要满代码找数字。这里我把屏幕分成左右两块左边是 10×20 的棋盘区右边是 150 像素宽的信息栏用来显示得分和下一个方块预览。import pygame import random CELL_SIZE 30 COLS 10 ROWS 20 WIDTH COLS * CELL_SIZE HEIGHT ROWS * CELL_SIZE SIDEBAR_WIDTH 150 SCREEN_WIDTH WIDTH SIDEBAR_WIDTH SCREEN_HEIGHT HEIGHT FPS 60 BLACK (0, 0, 0) WHITE (255, 255, 255) GRAY (128, 128, 128)窗口初始化和游戏主循环的骨架长这样def init_game(): pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(Tetris - Python实现) clock pygame.time.Clock() return screen, clock def draw_text(surface, text, size, x, y, colorWHITE): font pygame.font.Font(None, size) img font.render(text, True, color) surface.blit(img, (x, y))pygame.font.Font(None, size)表示使用 pygame 自带的默认字体。这里有个经验默认字体不支持中文如果你在界面上显示中文标题大概率会渲染成方块。解决办法要么全部用英文要么指定一个中文字体文件路径。我测试时直接用了英文界面避免这个麻烦。3.2 方块数据与旋转、碰撞检测俄罗斯方块有 7 种标准方块I、O、T、S、Z、J、L。每种方块的形状可以用一个嵌套列表来表示1 表示有格子0 表示空。例如 T 字形方块是[ [0, 1, 0], [1, 1, 1] ]我习惯把 7 种方块和它们对应的颜色放在一起定义SHAPES [ ([[1, 1, 1, 1]], (0, 255, 255)), # I ([[1, 1], [1, 1]], (255, 255, 0)), # O ([[0, 1, 0], [1, 1, 1]], (128, 0, 128)), # T ([[1, 0, 0], [1, 1, 1]], (255, 165, 0)), # L ([[0, 0, 1], [1, 1, 1]], (0, 0, 255)), # J ([[0, 1, 1], [1, 1, 0]], (0, 255, 0)), # S ([[1, 1, 0], [0, 1, 1]], (255, 0, 0)), # Z ]方块的位置用(x, y)表示含义是方块矩阵左上角在棋盘上的列索引和行索引。棋盘坐标里x 向右增大y 向下增大这跟二维数组的行列索引正好对应。旋转操作是很多新手最容易写错的地方。标准做法是先把矩阵顺时针旋转 90 度也就是先上下翻转再转置def rotate_shape(shape): return [list(row) for row in zip(*shape[::-1])]拆开解释shape[::-1]把矩阵上下翻转zip(*...)把翻转后的行变成列最后用list转回列表形式。比如 I 方块从横着变成竖着就是这一步在做功。碰撞检测是游戏里最核心的函数没有之一。它要判断“如果我把当前方块移动到某个偏移量会不会越界、会不会和已经落定的方块重叠”def valid_move(board, shape, x, y): for r, row in enumerate(shape): for c, val in enumerate(row): if val: new_x x c new_y y r if new_x 0 or new_x COLS or new_y ROWS: return False if new_y 0 and board[new_y][new_x]: return False return True注意new_y 0时我们不判重叠因为方块刚生成时有一部分可能在棋盘上边界之外这是正常的。但如果居然还要继续往上越界那就要阻止。这个细节不处理好方块在顶部旋转时会莫名奇妙地卡住。3.3 主循环与消行逻辑主体框架用pygame的事件驱动模式每帧先处理键盘事件再判断是否到了下落的时间点然后重绘画面。控制逻辑是这样设计的左右键移动上键旋转下键加速下落空格键直落到底。这里的关键是下落节奏。直接把整个逻辑塞进while True循环里每秒跑 60 帧但方块下落速度不能是每帧一次否则快到根本没法玩。我用的方案是用pygame.time.get_ticks()获取当前毫秒数记录上一次下落的时间当时间差超过“下落间隔”时才执行一次下落。def game_loop(screen, clock): board [[0] * COLS for _ in range(ROWS)] current new_piece() next_piece new_piece() score 0 drop_speed 500 # 毫秒 last_drop_time pygame.time.get_ticks() running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_LEFT: if valid_move(board, current[shape], current[x] - 1, current[y]): current[x] - 1 elif event.key pygame.K_RIGHT: if valid_move(board, current[shape], current[x] 1, current[y]): current[x] 1 elif event.key pygame.K_DOWN: if valid_move(board, current[shape], current[x], current[y] 1): current[y] 1 score 1 elif event.key pygame.K_UP: rotated rotate_shape(current[shape]) if valid_move(board, rotated, current[x], current[y]): current[shape] rotated elif event.key pygame.K_SPACE: while valid_move(board, current[shape], current[x], current[y] 1): current[y] 1 score 2 lock_piece(board, current) now pygame.time.get_ticks() if now - last_drop_time drop_speed: if valid_move(board, current[shape], current[x], current[y] 1): current[y] 1 else: lock_piece(board, current) lines_cleared clear_rows(board) score lines_cleared * 100 current new_piece() if not valid_move(board, current[shape], current[x], current[y]): running False last_drop_time now draw_game(screen, board, current, next_piece, score) clock.tick(FPS) pygame.quit()lock_piece负责把当前方块写进棋盘数组clear_rows负责消行。消行逻辑要特别注意数组索引方向def clear_rows(board): lines 0 r ROWS - 1 while r 0: if all(board[r]): del board[r] board.insert(0, [0] * COLS) lines 1 else: r - 1 return lines为什么从下往上检查因为消掉一行后上面的所有行要整体下移如果从上往下遍历删掉一行后行号会错位容易漏判。all(board[r])表示这一行所有格子都不是 0是满行就消除。先del再在顶部insert空行巧妙保持了棋盘行数不变。3.4 完整代码整合把上面这些函数组合起来加上绘制部分就是一个可以直接运行的完整版本。绘制部分主要是遍历棋盘数组画矩形def draw_game(screen, board, current, next_piece, score): screen.fill(BLACK) # 绘制棋盘区 for r in range(ROWS): for c in range(COLS): if board[r][c]: pygame.draw.rect(screen, board[r][c], (c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE)) pygame.draw.rect(screen, GRAY, (c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE), 1) # 绘制当前方块 for r, row in enumerate(current[shape]): for c, val in enumerate(row): if val: pygame.draw.rect(screen, current[color], ((current[x] c) * CELL_SIZE, (current[y] r) * CELL_SIZE, CELL_SIZE, CELL_SIZE)) draw_text(screen, Score: str(score), 24, WIDTH 10, 20) draw_text(screen, Next:, 24, WIDTH 10, 60) # 下一个方块预览省略细节思路一样 pygame.display.flip()new_piece函数负责从SHAPES里随机取一个方块并把初始 x 坐标定在棋盘中线附近def new_piece(): shape, color random.choice(SHAPES) return { shape: shape, x: COLS // 2 - len(shape[0]) // 2, y: 0, color: color }把上面这些代码按顺序粘贴到同一个.py文件里运行后就能玩了。操作方式左右方向键移动上方向键旋转下方向键加速空格键直接落底。4. 调试实录几个容易翻车的细节代码写出来能跑是一回事真正顺手是另一回事。我在反复测试中遇到过几个非常典型的坑写在这里当个速查表你复现的时候大概率能用到。4.1 常见问题速查表症状原因解决办法窗口弹出后马上闪退主循环里没有pygame.event.get()处理窗口关闭事件确保在 while 循环开头调用pygame.event.get()按键没反应监听逻辑写在了while循环外面或者没有用事件类型判断把键盘处理放进for event in pygame.event.get():内部方块旋转时飞出边界旋转后没有做碰撞检测或者只检测了原位置旋转后必须把新形状传给valid_move进行检测消行消不干净遍历方向错误或行号错位从下往上遍历消行后insert空行在顶部游戏速度越来越快下落计时器没有正确重置每次下落动作后更新last_drop_time now方块在顶部卡住导致异常结束新方块生成时就和已有方块重叠生成新方块后立刻做一次valid_move判断失败则结束游戏4.2 几个值得记住的进阶细节第一硬降空格直落后的得分我给了双倍的基础分这样玩家在拍空格键时更有策略性是保守地慢慢放还是赌一手直落拿高分。这个平衡可以根据你的喜好调整。第二pygame 默认字体的英文渲染效果还行但如果后续你想加“游戏结束”“按 R 重新开始”这类中文提示建议提前准备好中文字体文件比如微软雅黑msyh.ttc然后用pygame.font.Font(路径, 字号)加载。我第一次用默认字体渲染中文屏幕上全是小方块排查了半天才意识到是字体问题。第三下落速度应该随分数增加而加快否则玩到后面会越来越无聊。我通常的做法是在进入下一等级时把drop_speed减去一个固定值比如每得 1000 分把下落间隔从 500ms 降到 450ms最低到 100ms。这样做游戏才有“越玩越紧张”的真实体验。我在实际调这个俄罗斯方块时最深的体会是游戏代码的 bug 特别适合用“分步打日志”的方式排查。把棋盘状态、方块坐标、旋转结果打印到控制台很容易看清楚逻辑在哪一步出了问题。等你把基础稳定性跑通再往里面加方块幽灵投影、下一个方块预览、残影效果、音效难度完全是可控的。这个项目的天花板其实挺高但从第一版能运行的 200 行代码开始你已经赢了很多人。本文还有配套的精品资源点击获取