当前位置: 首页 > article >正文

使用Pygame制作“俄罗斯方块”游戏

1. 前言

俄罗斯方块(Tetris) 是一款由方块下落、行消除等核心规则构成的经典益智游戏:

  • 每次从屏幕顶部出现一个随机的方块(由若干小方格组成),玩家可以左右移动或旋转该方块,让它合适地堆叠在底部或其他方块之上。
  • 当某一行被填满时,该行会被消除,并给玩家增加分数。
  • 若方块叠加到顶部无法容纳新的方块,就表示游戏结束。

在本篇中,我们将使用 PythonPygame 来实现一个简化版的俄罗斯方块,主要演示核心流程,帮助你掌握更多 2D 游戏编程的常见技巧。


2. 开发环境

  1. Python 3.x
  2. Pygame:如果尚未安装,请执行
    pip install pygame
    
  3. 支持图形界面的桌面操作系统(Windows、macOS 或大部分 Linux)。

3. 游戏思路与要点

俄罗斯方块主要有以下几个关键逻辑点:

  1. 网格与方块

    • 整个游戏区域可被拆分为若干行列(比如 10 列 × 20 行)。
    • 下落的“方块”通常由四个小方格(称为 Tetromino)组成,常见形状有 I、O、T、S、Z、J、L 七种。
  2. 方块的移动和旋转

    • 在每帧或固定时间间隔,让方块自动向下移动一次。
    • 监听玩家按键:左右移动、加速下落、旋转方块等。
    • 在方块运动前要检测是否与已有方块发生碰撞或超出边界;若无法移动,则保持在原处。
  3. 行检测与消除

    • 每次放置完一个方块后,需要检查游戏网格中是否有某一行被填满(全部不是空)。
    • 若有满行,则消除该行,并将上方的所有行整体下移相应的行数,为玩家增加分数。
  4. 游戏结束检测

    • 当新的方块在顶部生成时,若该区域已被占满而无法容纳,即表示游戏结束。
  5. 随机性与关卡速度

    • 生成方块形状时,通常随机从七种基本形状中选择。
    • 随着分数的提升,可以让方块下落的速度越来越快,增强挑战性。

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()

核心逻辑解析

  1. grid 用于存储当前游戏区域的状态

    • grid[r][c] == 0 表示空格。
    • 其他值表示已经堆积在那儿的方块颜色(例如 (255,0,0) 对应红色等)。
  2. 方块(Tetromino) 类

    • 通过 shape(2D List)和 color 来描述该方块是什么形状、什么颜色。
    • row, col 表示方块左上角在网格坐标系的位置。
    • move(dr, dc) 会先检测移动后是否出界或与其他方块冲突;若可行再更新位置。
    • rotate() 通过矩阵转置 + 逆序实现顺时针旋转;若旋转后冲突,则恢复原状。
    • lock() 在方块无法下落后,将形状“锁定”到 grid 上。
  3. 行消除

    • check_lines() 判断每一行是否已被填满;若是,则把上方的行依次向下移动,并清空最上面的一行。
  4. 自动下落与输入控制

    • 用一个计时器 fall_time 不断累加帧间隔,达到 fall_speed 时让方块往下移动一次。
    • 监听左右键和上下键,实现左右移动、加速下落与旋转。
  5. 生成和切换方块

    • 每次锁定当前方块后,都从预先生成的 next_piece 切换过来,并随机生成下一个 next_piece
    • 这样可以在界面显示“下一块”预览(若想添加此功能,只需在界面上额外绘制 next_piece 的形状)。

5. 运行效果

image.png


6. 总结

通过这篇文章,我们用 Python + Pygame 实现了一个简化版的俄罗斯方块。这个项目涵盖了网格管理、方块碰撞与旋转、行检测与消除等常见的游戏逻辑。在此基础上,你完全可以自行改造并添加各种细节与高级功能,让游戏更贴近于真正的 Tetris。


http://www.kler.cn/a/526878.html

相关文章:

  • [EAI-026] DeepSeek-VL2 技术报告解读
  • 团体程序设计天梯赛-练习集——L1-028 判断素数
  • SpringBoot 日志
  • Eureka 服务注册和服务发现的使用
  • HttpClient学习
  • C++中的类与对象(中)
  • Spring Boot项目如何使用MyBatis实现分页查询及其相关原理
  • AJAX案例——图片上传个人信息操作
  • C++中vector追加vector
  • elasticsearch的常见面试题?
  • 亚博microros小车-原生ubuntu支持系列:15 激光雷达巡逻
  • 机器学习7-全连接神经网络3-过拟合与超参数
  • 信号模块--simulink操作
  • [Effective C++]条款53-55 杂项讨论
  • Linux第104步_基于AP3216C之I2C实验
  • Python学习之旅:进阶阶段(七)数据结构-计数器(collections.Counter)
  • TCP编程
  • 【Linux】日志设计模式与实现
  • DeepSeek Janus-Pro:多模态AI模型的突破与创新
  • 集群部署时的分布式 Session 如何实现?
  • VUE组件如何开发
  • jmap命令详解
  • 一维二维前缀和、差分,c++
  • 二叉树的遍历
  • pytorch实现变分自编码器
  • git 删除子模块 submodule 的步骤