1. 前言
俄罗斯方块(Tetris) 是一款由方块下落、行消除等核心规则构成的经典益智游戏:
- 每次从屏幕顶部出现一个随机的方块(由若干小方格组成),玩家可以左右移动或旋转该方块,让它合适地堆叠在底部或其他方块之上。
- 当某一行被填满时,该行会被消除,并给玩家增加分数。
- 若方块叠加到顶部无法容纳新的方块,就表示游戏结束。
在本篇中,我们将使用 Python 与 Pygame 来实现一个简化版的俄罗斯方块,主要演示核心流程,帮助你掌握更多 2D 游戏编程的常见技巧。
2. 开发环境
- Python 3.x
- Pygame:如果尚未安装,请执行
pip install pygame
- 支持图形界面的桌面操作系统(Windows、macOS 或大部分 Linux)。
3. 游戏思路与要点
俄罗斯方块主要有以下几个关键逻辑点:
-
网格与方块
- 整个游戏区域可被拆分为若干行列(比如 10 列 × 20 行)。
- 下落的“方块”通常由四个小方格(称为 Tetromino)组成,常见形状有 I、O、T、S、Z、J、L 七种。
-
方块的移动和旋转
- 在每帧或固定时间间隔,让方块自动向下移动一次。
- 监听玩家按键:左右移动、加速下落、旋转方块等。
- 在方块运动前要检测是否与已有方块发生碰撞或超出边界;若无法移动,则保持在原处。
-
行检测与消除
- 每次放置完一个方块后,需要检查游戏网格中是否有某一行被填满(全部不是空)。
- 若有满行,则消除该行,并将上方的所有行整体下移相应的行数,为玩家增加分数。
-
游戏结束检测
- 当新的方块在顶部生成时,若该区域已被占满而无法容纳,即表示游戏结束。
-
随机性与关卡速度
- 生成方块形状时,通常随机从七种基本形状中选择。
- 随着分数的提升,可以让方块下落的速度越来越快,增强挑战性。
4. 完整示例代码
以下代码示例仅实现一个简易版本的俄罗斯方块,展示最核心的逻辑。你可以将其保存为 tetris_game.py
并运行,进一步研究与改进。
import pygame
import sys
import random
# 初始化 pygame
pygame.init()
# --------------------------
# 配置参数
# --------------------------
CELL_SIZE = 30 # 每个网格的像素大小
COLS = 10 # 游戏区域的列数
ROWS = 20 # 游戏区域的行数
WINDOW_WIDTH = CELL_SIZE * COLS
WINDOW_HEIGHT = CELL_SIZE * ROWS
FPS = 30 # 帧率
# 颜色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
ORANGE = (255, 165, 0)
# 为了区分不同形状,定义一个全局形状/颜色映射
SHAPES_COLORS = [
([[1, 1, 1, 1]], CYAN), # I 形 (1行4列)
([[1, 1],
[1, 1]], YELLOW), # O 形 (2行2列)
([[0, 1, 0],
[1, 1, 1]], MAGENTA), # T 形
([[1, 1, 0],
[0, 1, 1]], GREEN), # S 形
([[0, 1, 1],
[1, 1, 0]], RED), # Z 形
([[1, 0, 0],
[1, 1, 1]], BLUE), # J 形
([[0, 0, 1],
[1, 1, 1]], ORANGE) # L 形
]
# 创建窗口
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("俄罗斯方块 - Pygame")
clock = pygame.time.Clock()
# 字体
font = pygame.font.SysFont("arial", 24)
# --------------------------
# 游戏网格
# --------------------------
# 用一个 2D 列表保存网格信息:0 表示空,其他表示对应的颜色
grid = [[0] * COLS for _ in range(ROWS)]
def draw_text(surface, text, color, x, y):
"""在指定位置绘制文字"""
label = font.render(text, True, color)
surface.blit(label, (x, y))
def draw_grid(surface):
"""绘制当前网格状态"""
for r in range(ROWS):
for c in range(COLS):
val = grid[r][c]
rect = pygame.Rect(c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if val == 0:
pygame.draw.rect(surface, BLACK, rect, 0) # 空 -> 绘制黑色背景
pygame.draw.rect(surface, GRAY, rect, 1) # 网格线
else:
# 如果 val 非 0,则 val 存储的是颜色
pygame.draw.rect(surface, val, rect, 0)
pygame.draw.rect(surface, GRAY, rect, 1)
def check_lines():
"""检测并消除已填满的行,返回消除行数"""
full_lines = 0
for r in range(ROWS):
if 0 not in grid[r]: # 如果这一行没有空格
full_lines += 1
# 将该行之上的所有行往下移动
for rr in range(r, 0, -1):
grid[rr] = grid[rr - 1][:]
# 最上面那行设为空
grid[0] = [0] * COLS
return full_lines
class Tetromino:
"""
表示当前下落的方块
包含:
shape -> 形状(一个2D list)
color -> 颜色
row, col -> 在grid中的坐标(左上角)
"""
def __init__(self, shape, color):
self.shape = shape
self.color = color
self.row = 0
self.col = COLS // 2 - len(shape[0]) // 2
def width(self):
return len(self.shape[0])
def height(self):
return len(self.shape)
def can_move(self, dr, dc):
"""判断方块是否能移动dr, dc"""
new_row = self.row + dr
new_col = self.col + dc
for r in range(self.height()):
for c in range(self.width()):
if self.shape[r][c] != 0:
rr = new_row + r
cc = new_col + c
# 判断是否越界
if rr < 0 or rr >= ROWS or cc < 0 or cc >= COLS:
return False
# 判断是否和已有方块重叠
if grid[rr][cc] != 0:
return False
return True
def move(self, dr, dc):
"""执行移动"""
if self.can_move(dr, dc):
self.row += dr
self.col += dc
return True
return False
def rotate(self):
"""
顺时针旋转90度
先生成新形状,然后判断是否能放置
"""
rotated = list(zip(*self.shape[::-1])) # 转置 + 逆序 可实现顺时针旋转
new_shape = [list(row) for row in rotated]
# 临时保存原 shape
old_shape = self.shape
self.shape = new_shape
# 如果旋转后出界或冲突,则恢复原 shape
if not self.can_move(0, 0):
self.shape = old_shape
def lock(self):
"""
当方块无法继续移动后,锁定到网格
"""
for r in range(self.height()):
for c in range(self.width()):
if self.shape[r][c] != 0:
grid[self.row + r][self.col + c] = self.color
def new_tetromino():
"""随机生成一个新的方块对象"""
shape, color = random.choice(SHAPES_COLORS)
return Tetromino(shape, color)
def is_game_over():
"""判断顶端是否已堆满"""
for c in range(COLS):
if grid[0][c] != 0:
return True
return False
def main():
current_piece = new_tetromino()
next_piece = new_tetromino()
fall_time = 0
fall_speed = 0.5 # 每0.5秒下落一格
score = 0
running = True
while running:
dt = clock.tick(FPS) / 1000 # 以秒为单位的帧间隔
fall_time += dt
# 1) 事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_piece.move(0, -1)
elif event.key == pygame.K_RIGHT:
current_piece.move(0, 1)
elif event.key == pygame.K_DOWN:
# 快速下落
current_piece.move(1, 0)
elif event.key == pygame.K_UP:
# 旋转
current_piece.rotate()
# 2) 自动下落
if fall_time >= fall_speed:
if not current_piece.move(1, 0):
# 不能再下落 -> 锁定到 grid
current_piece.lock()
# 检查是否有行被消除
lines_cleared = check_lines()
score += lines_cleared * 10
# 判断游戏是否结束
if is_game_over():
running = False
else:
# 生成新的方块
current_piece = next_piece
next_piece = new_tetromino()
fall_time = 0
# 3) 绘制
screen.fill(BLACK)
draw_grid(screen)
# 预先“绘制”当前方块(仅在可见区域内)
for r in range(current_piece.height()):
for c in range(current_piece.width()):
if current_piece.shape[r][c] != 0:
rr = current_piece.row + r
cc = current_piece.col + c
if rr >= 0:
rect = pygame.Rect(cc * CELL_SIZE, rr * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, current_piece.color, rect, 0)
pygame.draw.rect(screen, GRAY, rect, 1)
draw_text(screen, f"Score: {score}", WHITE, 10, 10)
pygame.display.flip()
game_over(screen, score)
def game_over(surface, score):
"""游戏结束画面"""
surface.fill(GRAY)
draw_text(surface, "Game Over!", WHITE, WINDOW_WIDTH // 2 - 60, WINDOW_HEIGHT // 2 - 30)
draw_text(surface, f"Your Score: {score}", WHITE, WINDOW_WIDTH // 2 - 70, WINDOW_HEIGHT // 2 + 10)
pygame.display.flip()
pygame.time.wait(3000)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
核心逻辑解析
-
grid
用于存储当前游戏区域的状态grid[r][c] == 0
表示空格。- 其他值表示已经堆积在那儿的方块颜色(例如
(255,0,0)
对应红色等)。
-
方块(Tetromino) 类
- 通过
shape
(2D List)和color
来描述该方块是什么形状、什么颜色。 row, col
表示方块左上角在网格坐标系的位置。move(dr, dc)
会先检测移动后是否出界或与其他方块冲突;若可行再更新位置。rotate()
通过矩阵转置 + 逆序实现顺时针旋转;若旋转后冲突,则恢复原状。lock()
在方块无法下落后,将形状“锁定”到grid
上。
- 通过
-
行消除
check_lines()
判断每一行是否已被填满;若是,则把上方的行依次向下移动,并清空最上面的一行。
-
自动下落与输入控制
- 用一个计时器
fall_time
不断累加帧间隔,达到fall_speed
时让方块往下移动一次。 - 监听左右键和上下键,实现左右移动、加速下落与旋转。
- 用一个计时器
-
生成和切换方块
- 每次锁定当前方块后,都从预先生成的
next_piece
切换过来,并随机生成下一个next_piece
。 - 这样可以在界面显示“下一块”预览(若想添加此功能,只需在界面上额外绘制
next_piece
的形状)。
- 每次锁定当前方块后,都从预先生成的
5. 运行效果
6. 总结
通过这篇文章,我们用 Python + Pygame 实现了一个简化版的俄罗斯方块。这个项目涵盖了网格管理、方块碰撞与旋转、行检测与消除等常见的游戏逻辑。在此基础上,你完全可以自行改造并添加各种细节与高级功能,让游戏更贴近于真正的 Tetris。