Python实现经典俄罗斯方块游戏开发指南
完整代码在文章底部
一、环境准备
pip install pygame
二、核心功能实现
1. 游戏元素定义
- 七种经典方块:I、L、J、O、S、T、Z型
- 颜色系统:每个方块类型对应不同颜色
- 网格系统:10x20的游戏区域
2. 核心算法解析
方块旋转算法
# 矩阵转置+逆序实现旋转
self.shape = [list(row) for row in zip(*self.shape[::-1])]
碰撞检测
def check_collision(self, grid, dx=0, dy=0):
# 边界检测和已有方块检测
...
消行算法
def clear_lines(self):
# 遍历所有行,移除满行并补充新空行
...
3. 游戏控制逻辑
- 方向键:左右移动
- 上方向键:旋转方块
- 空格键:硬降到底
- 自动下落速度:0.5秒/格
三、代码结构说明
1. 主要类说明
类名 | 功能描述 |
---|---|
Tetromino | 处理单个方块的形状、颜色和旋转 |
Tetris | 管理游戏状态和核心逻辑 |
2. 关键方法
move_down()
: 处理方块下落逻辑lock_piece()
: 锁定已落地方块hard_drop()
: 快速下落实现
四、运行效果
- 游戏区域:300x600像素
- 得分规则:消1行100分,消2行400分,消3行900分,消4行1600分
- 预览功能:显示下一个即将出现的方块
五、扩展开发建议
- 难度系统:随着分数增加加快下落速度
- 音效系统:添加消除音效和背景音乐
- 排行榜功能:记录历史最高分
- 特效系统:添加消除动画效果
- 手柄支持:扩展控制器输入
六、常见问题解答
- 方块旋转出界:通过碰撞检测自动修正位置
- 画面闪烁问题:使用双缓冲技术
pygame.display.flip()
- 控制响应延迟:优化事件处理循环
实现效果示意图:
项目完整代码:
import pygame
import random
import time
# 初始化Pygame
pygame.init()
# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
COLORS = [
(0, 255, 255), # I型-青色
(255, 165, 0), # L型-橙色
(0, 0, 255), # J型-蓝色
(255, 255, 0), # O型-黄色
(0, 255, 0), # S型-绿色
(255, 0, 255), # T型-紫色
(255, 0, 0) # Z型-红色
]
# 游戏设置
WIDTH = 300
HEIGHT = 600
BLOCK_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
# 方块形状定义(4x4矩阵)
SHAPES = [
[[1, 1, 1, 1]], # I型
[[1, 0, 0], [1, 1, 1]], # L型
[[0, 0, 1], [1, 1, 1]], # J型
[[1, 1], [1, 1]], # O型
[[0, 1, 1], [1, 1, 0]], # S型
[[0, 1, 0], [1, 1, 1]], # T型
[[1, 1, 0], [0, 1, 1]] # Z型
]
class Tetromino:
def __init__(self, x, y):
self.shape = random.choice(SHAPES)
self.color = COLORS[SHAPES.index(self.shape)]
self.x = x
self.y = y
self.rotation = 0
def rotate(self):
# 保存原始状态用于回滚
original_shape = self.shape
original_x = self.x
# 矩阵转置+逆序实现旋转
self.shape = [list(row) for row in zip(*self.shape[::-1])]
# 旋转后自动校正位置避免越界
offset_x = 0
while self.check_collision() and offset_x < 3: # 最多尝试调整3次
if self.x < GRID_WIDTH // 2:
# 尝试右移
self.x += 1
offset_x += 1
else:
# 尝试左移
self.x -= 1
offset_x += 1
# 如果调整后仍碰撞则恢复原状
if self.check_collision():
self.shape = original_shape
self.x = original_x
def check_collision(self, grid=None, dx=0, dy=0):
if grid is None:
grid = [[0] * GRID_WIDTH for _ in range(GRID_HEIGHT)]
for y, row in enumerate(self.shape):
for x, cell in enumerate(row):
if cell:
new_x = self.x + x + dx
new_y = self.y + y + dy
# 边界检查
if new_x < 0 or new_x >= GRID_WIDTH:
return True
if new_y >= GRID_HEIGHT:
return True
# 已有方块检查
if new_y >= 0 and grid[new_y][new_x]:
return True
return False
class Tetris:
def __init__(self):
self.grid = [[0] * GRID_WIDTH for _ in range(GRID_HEIGHT)]
self.current_piece = None
self.next_piece = Tetromino(0, 0)
self.score = 0
self.spawn_new_piece()
def spawn_new_piece(self):
self.current_piece = self.next_piece
self.next_piece = Tetromino(0, 0)
self.current_piece.x = GRID_WIDTH // 2 - len(self.current_piece.shape[0]) // 2
self.current_piece.y = 0
# 生成即碰撞说明游戏结束
if self.current_piece.check_collision(self.grid):
print("Game Over! Score:", self.score)
pygame.quit()
exit()
def move_down(self):
if not self.current_piece.check_collision(self.grid, dy=1):
self.current_piece.y += 1
return True
self.lock_piece()
return False
def lock_piece(self):
# 将当前方块写入网格
for y, row in enumerate(self.current_piece.shape):
for x, cell in enumerate(row):
if cell:
gy = self.current_piece.y + y
gx = self.current_piece.x + x
if 0 <= gy < GRID_HEIGHT and 0 <= gx < GRID_WIDTH: # 防止索引越界
self.grid[gy][gx] = self.current_piece.color
self.clear_lines()
self.spawn_new_piece()
def clear_lines(self):
lines_cleared = 0
new_grid = []
for row in self.grid:
if 0 not in row:
lines_cleared += 1
else:
new_grid.append(row)
self.score += lines_cleared ** 2 * 100
# 补充新行到顶部
self.grid = [[0] * GRID_WIDTH for _ in range(lines_cleared)] + new_grid
def move_horizontal(self, dx):
if not self.current_piece.check_collision(self.grid, dx=dx):
self.current_piece.x += dx
def hard_drop(self):
while self.move_down():
pass
# 初始化游戏
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("俄罗斯方块")
clock = pygame.time.Clock()
game = Tetris()
def draw_grid():
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
color = game.grid[y][x]
if color:
pygame.draw.rect(screen, color,
(x * BLOCK_SIZE + 1, y * BLOCK_SIZE + 1,
BLOCK_SIZE - 2, BLOCK_SIZE - 2))
def draw_current_piece():
for y, row in enumerate(game.current_piece.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, game.current_piece.color,
((game.current_piece.x + x) * BLOCK_SIZE + 1,
(game.current_piece.y + y) * BLOCK_SIZE + 1,
BLOCK_SIZE - 2, BLOCK_SIZE - 2))
running = True
fall_time = 0
fall_speed = 0.5
while running:
screen.fill(BLACK)
current_time = time.time()
delta_time = current_time - fall_time
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
game.move_horizontal(-1)
elif event.key == pygame.K_RIGHT:
game.move_horizontal(1)
elif event.key == pygame.K_UP:
game.current_piece.rotate()
elif event.key == pygame.K_DOWN:
game.move_down()
elif event.key == pygame.K_SPACE:
game.hard_drop()
if delta_time >= fall_speed:
game.move_down()
fall_time = current_time
draw_grid()
draw_current_piece()
pygame.display.flip()
clock.tick(60)
pygame.quit()