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

news2025/2/2 1:10:19

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.coloradmin.cn/o/2289541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

deepseek大模型本机部署

2024年1月20日晚&#xff0c;中国DeepSeek发布了最新推理模型DeepSeek-R1&#xff0c;引发广泛关注。这款模型不仅在性能上与OpenAI的GPT-4相媲美&#xff0c;更以开源和创新训练方法&#xff0c;为AI发展带来了新的可能性。 本文讲解如何在本地部署deepseek r1模型。deepseek官…

常见“栈“相关题目

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; 优选算法专题 目录 1047.删除字符串中的所有相邻重复项 844.比较含退格的字符串 227.基本计算器 II 394.字符串解码 946.验证栈序列 104…

QT实现有限元软件操作界面

本系列文章致力于实现“手搓有限元&#xff0c;干翻Ansys的目标”&#xff0c;基本框架为前端显示使用QT实现交互&#xff0c;后端计算采用Visual Studio C。 本篇将二维矩形截面梁单元&#xff08;Rect_Beam2D2Node&#xff09;组成的钢结构桥作为案例来展示软件功能。 也可以…

软件工程经济学-日常作业+大作业

目录 一、作业1 作业内容 解答 二、作业2 作业内容 解答 三、作业3 作业内容 解答 四、大作业 作业内容 解答 1.建立层次结构模型 (1)目标层 (2)准则层 (3)方案层 2.构造判断矩阵 (1)准则层判断矩阵 (2)方案层判断矩阵 3.层次单排序及其一致性检验 代码 …

Go学习:Go语言中if、switch、for语句与其他编程语言中相应语句的格式区别

Go语言中的流程控制语句逻辑结构与其他编程语言类似&#xff0c;格式有些不同。Go语言的流程控制中&#xff0c;包括if、switch、for、range、goto等语句&#xff0c;没有while循环。 目录 1. if 语句 2. switch语句 3. for语句 4. range语句 5. goto语句&#xff08;不常用…

14-8C++STL的queue容器

一、queue容器 (1)queue容器的简介 queue为队列容器&#xff0c;“先进先出”的容器 (2)queue对象的构造 queue<T>q; queue<int>que Int;//存放一个int的queue容器 queue<string>queString;//存放一个string的queue容器 (3)queue容器的push()与pop()方…

【B站保姆级视频教程:Jetson配置YOLOv11环境(四)cuda cudnn tensorrt配置】

Jetson配置YOLOv11环境&#xff08;4&#xff09;cuda cudnn tensorrt配置 文章目录 0. 简介1. cuda配置&#xff1a;添加cuda环境变量2. cudnn配置3. TensorRT Python环境配置3.1 系统自带Python环境中的TensorRT配置3.2 Conda 虚拟Python环境中的TensorRT配置 0. 简介 官方镜…

信号模块--simulink操作

位置simulink/sourses 常用的模块 功能&#xff1a;常数模块&#xff0c;提供一个常数 数据设置可以是一维或多维 一维数据设置 多维数据设置&#xff08;例三维数据设置&#xff09; 方波脉冲模块 模块用于按固定间隔生成方波脉冲信号 振幅就是方波的幅度&#xff0c;0到…

强化学习笔记(3)——基于值函数的方法和策略梯度方法

分为两大类方法&#xff1a; 基于值函数的方法&#xff08;Temporal Difference Methods, TD Methods&#xff09; 策略梯度方法&#xff08;Policy Gradient Methods&#xff09;。 二者不同之处&#xff1a; 通过值函数来间接表达隐式的策略&#xff0c;一个是直接迭代优化策…

新年新挑战:如何用LabVIEW开发跨平台应用

新的一年往往伴随着各种新的项目需求&#xff0c;而跨平台应用开发无疑是当前备受瞩目的发展趋势。在众多开发工具中&#xff0c;LabVIEW 以其独特的图形化编程方式和强大的功能&#xff0c;为开发跨平台应用提供了有效的途径。本文将深入探讨如何运用 LabVIEW 开发能够在不同操…

事务04之死锁,锁底层和隔离机制原理

死锁和事务底层原理 文章目录 死锁和事务底层原理一&#xff1a;MySQL中的死锁现象1&#xff1a;何为死锁1.1&#xff1a;死锁的概念1.2&#xff1a;死锁产生的四个必要条件&#xff1a; 2&#xff1a;MySQL的死锁2.1&#xff1a;死锁的触发2.2&#xff1a;MySQL的死锁如何解决…

Golang 并发机制-2:Golang Goroutine 和竞争条件

在今天的软件开发中&#xff0c;我们正在使用并发的概念&#xff0c;它允许一次执行多个任务。在Go编程中&#xff0c;理解Go例程是至关重要的。本文试图详细解释什么是例程&#xff0c;它们有多轻&#xff0c;通过简单地使用“go”关键字创建它们&#xff0c;以及可能出现的竞…

【4Day创客实践入门教程】Day0 创想启程——课程与项目预览

Day0 创想启程——课程与项目预览 目录 Day0 创想启程——课程与项目预览前言学习内容基本的单片机开发技能简单的焊接技能简单的MicroPython程序 后记 Day1 工具箱构建——开发环境的构建Day2 探秘微控制器——单片机与MicroPython初步Day3 实战演练——桌面迷你番茄钟Day4 迈…

洛谷P3372 【模板】线段树 1以及分块

【模板】线段树 1 题目描述 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; 将某区间每一个数加上 k k k。求出某区间每一个数的和。 输入格式 第一行包含两个整数 n , m n, m n,m&#xff0c;分别表示该数列数字的个数和操作的总个数。 …

(动态规划基础 打家劫舍)leetcode 198

已知h2和h1&#xff0c;用已知推出未知 推是求答案&#xff0c;回溯是给答案 这里图片给出dfs暴力&#xff0c;再进行记录答案完成记忆化搜索&#xff0c;再转为dp数组 #include<iostream> #include<vector> #include<algorithm> //nums:2,1,1,2 //dp:2,2,…

Python 梯度下降法(四):Adadelta Optimize

文章目录 Python 梯度下降法&#xff08;四&#xff09;&#xff1a;Adadelta Optimize一、数学原理1.1 介绍1.2 实现流程 二、代码实现2.1 函数代码2.2 总代码 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法&#xff08;四&#xff09;&#xff1a;Adadelta Opt…

旅行的意义:“诗与远方”和在旅途中找寻真我

原文链接&#xff1a;旅行的意义&#xff1a;“诗与远方”和在旅途中找寻真我 困在格子间&#xff0c;心向远方 清晨&#xff0c;闹钟催促&#xff0c;打工人挣扎起床出门。地铁拥挤&#xff0c;工作繁忙&#xff0c;加班成常态&#xff0c;下班时夜幕已深&#xff0c;满心疲惫…

leetcode——将有序数组转化为二叉搜索树(java)

给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9] 也将被视为正确答…

前端js高级25.1.30

原型&#xff1a;函数的组成结构 通过这个图我们需要知道。 假设我们创建了一个Foo函数。 规则&#xff1a;Function.protoType是函数显示原型。__proto__是隐式对象。 Function、Object、Foo函数的__proto__指向了Function.protoType说明。这三个都依托function函数来创建。…

【后端开发】字节跳动青训营之性能分析工具pprof

性能分析工具pprof 一、测试程序介绍二、pprof工具安装与使用2.1 pprof工具安装2.2 pprof工具使用 资料链接&#xff1a; 项目代码链接实验指南pprof使用指南 一、测试程序介绍 package mainimport ("log""net/http"_ "net/http/pprof" // 自…