百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

使用python编写俄罗斯方块小游戏并为每一句添加注释,方便学习

off999 2025-08-05 20:29 3 浏览 0 评论


先看下学习指导



# 俄罗斯方块游戏开发 - Python学习指导

## 项目概述

这个俄罗斯方块游戏是一个完整的Python项目,涵盖了以下重要的编程概念:

- 面向对象编程(OOP)

- 游戏开发基础

- 数据结构和算法

- 事件处理

- 图形界面编程

## 详细学习指导

### 1. 项目结构理解

```

俄罗斯方块/

├── tetris.py # 主游戏文件

├── requirements.txt # 依赖包列表

├── README.md # 项目说明

└── 学习指导.md # 本文件

```

### 2. 核心编程概念详解

#### 2.1 面向对象编程(OOP)

**什么是类和对象?**

```python

class Tetris: # 这是一个类(Class)

def __init__(self): # 构造函数

self.score = 0 # 实例变量


def get_new_piece(self): # 实例方法

return piece

# 创建对象(实例化)

game = Tetris() # game是Tetris类的一个对象

```

**学习要点:**

- **类(Class)**:是对象的模板,定义了对象的属性和方法

- **对象(Object)**:是类的实例,具有类定义的属性和方法

- **self**:指向当前对象实例,类似于"我自己"

- **构造函数 `__init__`**:创建对象时自动调用,用于初始化对象状态

#### 2.2 数据结构的应用

**二维列表(游戏网格)**

```python

# 创建20行10列的游戏网格

self.grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]

# 访问网格中的某个位置

self.grid[y][x] = color # 在第y行第x列放置颜色

# 检查某行是否完整

if all(self.grid[y][x] != 0 for x in range(GRID_WIDTH)):

# 这一行已满,可以消除

```

**字典(方块对象)**

```python

piece = {

'shape': TETRIS_SHAPES[0], # 方块形状

'rotation': 0, # 旋转状态

'x': 5, # X坐标

'y': 0, # Y坐标

'color': CYAN # 颜色

}

# 访问字典中的值

print(piece['x']) # 输出:5

piece['x'] += 1 # 向右移动一格

```

**学习要点:**

- **列表推导式**:`[表达式 for 变量 in 可迭代对象]`

- **二维列表**:列表的列表,用于表示网格状数据

- **字典**:键值对数据结构,用于存储相关联的数据

#### 2.3 游戏循环(Game Loop)

游戏开发的核心模式:

```python

while running: # 主循环

# 1. 处理输入(Input)

for event in pygame.event.get():

if event.type == pygame.KEYDOWN:

if event.key == pygame.K_LEFT:

# 处理左键按下


# 2. 更新游戏状态(Update)

if self.fall_time >= self.fall_speed:

# 方块下降

self.current_piece['y'] += 1


# 3. 渲染画面(Render)

self.screen.fill(BLACK)

self.draw_grid()

self.draw_piece(self.current_piece)

pygame.display.flip()

```

**学习要点:**

- **游戏循环**:输入→更新→渲染,不断重复

- **事件驱动**:响应用户的键盘、鼠标操作

- **帧率控制**:`clock.tick(60)` 控制每秒60帧

### 3. 关键算法详解

#### 3.1 碰撞检测算法

这是游戏中最重要的算法之一:

```python

def is_valid_position(self, piece, dx=0, dy=0, rotation=None):

"""检查方块位置是否有效"""

if rotation is None:

rotation = piece['rotation']


shape = piece['shape'][rotation] # 获取当前旋转状态的形状


# 遍历方块的每个单元格

for y, row in enumerate(shape):

for x, cell in enumerate(row):

if cell == '#': # 如果这个位置有方块

# 计算在游戏网格中的实际位置

new_x = piece['x'] + x + dx

new_y = piece['y'] + y + dy


# 检查是否超出边界

if (new_x < 0 or new_x >= GRID_WIDTH or new_y >= GRID_HEIGHT):

return False


# 检查是否与已有方块重叠

if new_y >= 0 and self.grid[new_y][new_x] != 0:

return False


return True # 位置有效

```

**算法思路:**

1. 获取方块的形状数据

2. 遍历形状中的每个单元格

3. 计算每个单元格在游戏网格中的位置

4. 检查边界和重叠

#### 3.2 行消除算法

```python

def clear_lines(self):

"""清除完整的行"""

lines_to_clear = [] # 存储需要清除的行号


# 第一步:找到所有完整的行

for y in range(GRID_HEIGHT):

# 检查这一行是否完全填满

if all(self.grid[y][x] != 0 for x in range(GRID_WIDTH)):

lines_to_clear.append(y)


# 第二步:清除完整的行

for y in lines_to_clear:

del self.grid[y] # 删除这一行

# 在顶部插入新的空行

self.grid.insert(0, [0 for _ in range(GRID_WIDTH)])


# 第三步:更新分数

lines_cleared = len(lines_to_clear)

if lines_cleared > 0:

score_values = [0, 100, 300, 500, 800] # 不同行数的分数

self.score += score_values[min(lines_cleared, 4)] * self.level

```

**算法思路:**

1. 扫描所有行,找出完整的行

2. 删除完整的行

3. 在顶部补充空行

4. 计算和更新分数

### 4. 方块数据表示

#### 4.1 方块形状定义

```python

# I形状(直线方块)

[['.....',

'..#..',

'..#..',

'..#..',

'..#..'], # 垂直状态

['.....',

'.....',

'####.',

'.....',

'.....']] # 水平状态

```

**设计思路:**

- 用字符串表示方块形状,'#'表示有方块,'.'表示空

- 每个方块可以有多个旋转状态

- 5x5的网格足以容纳所有方块形状

#### 4.2 坐标系统

```

游戏网格坐标系:

(0,0) (1,0) (2,0) ... (9,0)

(0,1) (1,1) (2,1) ... (9,1)

. . . .

. . . .

(0,19)(1,19)(2,19)... (9,19)

```

- X轴:从左到右,0到9

- Y轴:从上到下,0到19

- 方块的位置(x,y)表示方块左上角在网格中的位置

### 5. Pygame基础

#### 5.1 初始化和窗口创建

```python

pygame.init() # 初始化pygame

screen = pygame.display.set_mode((WIDTH, HEIGHT)) # 创建窗口


pygame.display.set_caption("俄罗斯方块") # 设置标题

clock = pygame.time.Clock() # 创建时钟对象

```

#### 5.2 绘制图形

```python

# 填充背景色

screen.fill(BLACK)

# 绘制矩形

pygame.draw.rect(screen, color, (x, y, width, height))

# 绘制线条

pygame.draw.line(screen, color, start_pos, end_pos)

# 更新显示

pygame.display.flip()

```

#### 5.3 事件处理

```python

for event in pygame.event.get():

if event.type == pygame.QUIT: # 关闭窗口

running = False

elif event.type == pygame.KEYDOWN: # 按键按下

if event.key == pygame.K_LEFT: # 左箭头键

# 处理左移

```

### 6. 编程技巧和最佳实践

#### 6.1 常量的使用

```python

# 好的做法:使用常量

WIDTH = 800

HEIGHT = 600

BLOCK_SIZE = 30

# 不好的做法:直接使用数字

screen = pygame.display.set_mode((800, 600)) # 魔法数字

```

#### 6.2 函数的单一职责

```python

# 好的做法:每个函数只做一件事

def draw_grid(self): # 只负责绘制网格

def draw_piece(self, piece): # 只负责绘制方块

def clear_lines(self): # 只负责清除行

# 不好的做法:一个函数做太多事情

def
update_and_draw_everything(self): # 功能太复杂

```

#### 6.3 代码注释

```python

# 好的注释:解释为什么这样做

def rotate_piece(self, piece):

"""旋转方块


通过切换到下一个旋转状态来实现旋转,

同时检查新状态是否有效以避免非法旋转。

"""

new_rotation = (piece['rotation'] + 1) % len(piece['shape'])

if self.is_valid_position(piece, rotation=new_rotation):

piece['rotation'] = new_rotation

```

### 7. 学习建议和练习

#### 7.1 循序渐进的学习步骤

1. **第一周**:理解基本概念

- 运行游戏,熟悉操作

- 阅读代码注释,理解整体结构

- 学习面向对象编程基础

2. **第二周**:深入核心算法

- 理解碰撞检测算法

- 学习方块数据表示方法

- 掌握坐标系统

3. **第三周**:掌握游戏循环

- 理解事件处理机制

- 学习pygame基础

- 掌握绘制方法

4. **第四周**:扩展和优化

- 尝试添加新功能

- 优化代码结构

- 学习调试技巧

#### 7.2 实践练习建议

1. **修改游戏参数**

```python

# 尝试修改这些值,观察游戏变化

BLOCK_SIZE = 40 # 改变方块大小

GRID_WIDTH = 12 # 改变游戏区域宽度

fall_speed = 200 # 改变下降速度

```

2. **添加新功能**

- 暂停功能(按P键暂停)

- 重新开始功能(按R键重新开始)

- 显示游戏时间

- 保存最高分

3. **美化界面**

- 改变颜色方案

- 添加背景图片

- 改进字体和布局

- 添加动画效果

#### 7.3 调试技巧

1. **使用print调试**

```python

def is_valid_position(self, piece, dx=0, dy=0):

print(f"检查位置: x={piece['x']+dx}, y={piece['y']+dy}") # 调试信息

# ... 其他代码

```

2. **分步测试**

- 先测试单个功能

- 逐步组合功能

- 使用简化的测试数据

3. **常见错误和解决方法**

- **IndexError**: 检查数组边界

- **KeyError**: 检查字典键是否存在

- **AttributeError**: 检查对象是否有该属性

### 8. 进阶学习方向

学完这个项目后,你可以继续学习:

1. **更复杂的游戏**

- 贪吃蛇

- 打砖块

- 简单的RPG游戏

2. **游戏开发框架**

- Pygame Zero(更简单的游戏开发)

- Arcade(现代2D游戏库)

- Panda3D(3D游戏开发)

3. **相关技术**

- 数据库操作(保存游戏数据)

- 网络编程(多人游戏)

- 人工智能(游戏AI)

- 图像处理(特效制作)

### 9. 总结

这个俄罗斯方块项目涵盖了Python编程的核心概念:

- **面向对象编程**:类、对象、方法、封装

- **数据结构**:列表、字典、二维数组

- **算法思维**:碰撞检测、行消除、位置验证

- **游戏开发**:游戏循环、事件处理、图形绘制

- **编程实践**:代码组织、注释、调试

通过学习和实践这个项目,你将建立起扎实的Python编程基础,为后续的学习和开发打下良好的基础。

记住:编程是一个实践性很强的技能,多动手、多思考、多实验是提高的关键!

以下是代码正文,每一句都添加了详细的注释,方便学习!

记得先提前安装pygame模块:pygame>=2.0.0
# 导入pygame游戏开发库,用于图形界面和游戏循环
import pygame
# 导入random库,用于随机生成方块
import random
# 导入sys库,用于程序退出
import sys

# 初始化pygame库,必须在使用pygame功能前调用
pygame.init()

# ==================== 游戏常量定义 ====================
WIDTH = 800  # 游戏窗口的宽度(像素)
HEIGHT = 700  # 游戏窗口的高度(像素,修复底部显示不全的问题)
BLOCK_SIZE = 30  # 每个方块的大小(像素)
GRID_WIDTH = 10  # 游戏区域的宽度(以方块为单位)
GRID_HEIGHT = 20  # 游戏区域的高度(以方块为单位)
GRID_X_OFFSET = 50  # 游戏区域距离窗口左边的偏移量(像素)
GRID_Y_OFFSET = 50  # 游戏区域距离窗口顶部的偏移量(像素)

# ==================== 颜色定义(RGB值) ====================
BLACK = (0, 0, 0)  # 黑色,用于背景
WHITE = (255, 255, 255)  # 白色,用于文字和网格线
RED = (255, 0, 0)  # 红色,用于Z形方块
GREEN = (0, 255, 0)  # 绿色,用于S形方块
BLUE = (0, 0, 255)  # 蓝色,用于J形方块
YELLOW = (255, 255, 0)  # 黄色,用于O形方块
ORANGE = (255, 165, 0)  # 橙色,用于L形方块
PURPLE = (128, 0, 128)  # 紫色,用于T形方块
CYAN = (0, 255, 255)  # 青色,用于I形方块
GRAY = (128, 128, 128)  # 灰色,用于网格线

# ==================== 俄罗斯方块的7种形状定义 ====================
# 每个形状用二维数组表示,'#'表示有方块,'.'表示空
# 每个形状可以有多个旋转状态(0度、90度、180度、270度)
TETRIS_SHAPES = [
    # I形状(直线方块)- 有2个旋转状态
    [['.....',  # 第一个状态:垂直
      '..#..',  # 中间一列有4个方块
      '..#..',
      '..#..',
      '..#..'],
     ['.....',  # 第二个状态:水平
      '.....',  # 中间一行有4个方块
      '####.',
      '.....',
      '.....']],
    
    # O形状(正方形方块)- 只有1个旋转状态(旋转后形状不变)
    [['.....',
      '.....',  # 2x2的正方形
      '.##..',
      '.##..',
      '.....']],
    
    # T形状(T字形方块)- 有4个旋转状态
    [['.....',  # 状态1:T字正立
      '.....',
      '.#...',  # 上面一个方块
      '###..',  # 下面一行三个方块
      '.....'],
     ['.....',  # 状态2:T字向右
      '.....',
      '.#...',  # 左边一个方块
      '.##..',  # 中间两个方块
      '.#...'],  # 右边一个方块
     ['.....',  # 状态3:T字倒立
      '.....',
      '.....',
      '###..',  # 上面一行三个方块
      '.#...'],  # 下面一个方块
     ['.....',  # 状态4:T字向左
      '.....',
      '.#...',  # 右边一个方块
      '##...',  # 中间两个方块
      '.#...']],  # 左边一个方块
    
    # S形状(S字形方块)- 有2个旋转状态
    [['.....',  # 状态1:水平S形
      '.....',
      '.##..',  # 右上两个方块
      '##...',  # 左下两个方块
      '.....'],
     ['.....',  # 状态2:垂直S形
      '.....',
      '.#...',  # 上方一个方块
      '.##..',  # 中间两个方块
      '..#..']],  # 下方一个方块
    
    # Z形状(Z字形方块)- 有2个旋转状态
    [['.....',  # 状态1:水平Z形
      '.....',
      '##...',  # 左上两个方块
      '.##..',  # 右下两个方块
      '.....'],
     ['.....',  # 状态2:垂直Z形
      '.....',
      '..#..',  # 上方一个方块
      '.##..',  # 中间两个方块
      '.#...']],  # 下方一个方块
    
    # J形状(J字形方块)- 有4个旋转状态
    [['.....',  # 状态1:J字正立
      '.....',
      '.#...',  # 上方一个方块
      '.#...',  # 中间一个方块
      '##...'],  # 下方两个方块(左转弯)
     ['.....',  # 状态2:J字向右
      '.....',
      '.....',
      '#....',  # 左边一个方块
      '###..'],  # 右边三个方块
     ['.....',  # 状态3:J字倒立
      '.....',
      '.##..',  # 上方两个方块(右转弯)
      '.#...',  # 中间一个方块
      '.#...'],  # 下方一个方块
     ['.....',  # 状态4:J字向左
      '.....',
      '.....',
      '###..',  # 左边三个方块
      '..#..']],  # 右边一个方块
    
    # L形状(L字形方块)- 有4个旋转状态
    [['.....',  # 状态1:L字正立
      '.....',
      '..#..',  # 上方一个方块
      '..#..',  # 中间一个方块
      '.##..'],  # 下方两个方块(右转弯)
     ['.....',  # 状态2:L字向右
      '.....',
      '.....',
      '###..',  # 左边三个方块
      '#....'],  # 右边一个方块
     ['.....',  # 状态3:L字倒立
      '.....',
      '##...',  # 上方两个方块(左转弯)
      '.#...',  # 中间一个方块
      '.#...'],  # 下方一个方块
     ['.....',  # 状态4:L字向左
      '.....',
      '.....',
      '..#..',  # 右边一个方块
      '###..']]  # 左边三个方块
]

# ==================== 形状颜色映射 ====================
# 每种形状对应的颜色,按照TETRIS_SHAPES的顺序排列
# 0:I形状-青色, 1:O形状-黄色, 2:T形状-紫色, 3:S形状-绿色, 4:Z形状-红色, 5:J形状-蓝色, 6:L形状-橙色
SHAPE_COLORS = [CYAN, YELLOW, PURPLE, GREEN, RED, BLUE, ORANGE]

# ==================== 俄罗斯方块游戏主类 ====================
class Tetris:
    """俄罗斯方块游戏主类"""
    
    def __init__(self):
        """初始化俄罗斯方块游戏""" 
        # ==================== 游戏窗口初始化 ====================
        # 创建游戏窗口,设置窗口大小为WIDTH x HEIGHT像素
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        # 设置窗口标题
        pygame.display.set_caption("俄罗斯方块 - Python学习版")
        # 创建时钟对象,用于控制游戏帧率
        self.clock = pygame.time.Clock()
        
        # ==================== 游戏状态变量初始化 ====================
        # 创建游戏网格,初始化为全0(空白)
        # 网格大小为GRID_HEIGHT行 x GRID_WIDTH列
        self.grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
        self.score = 0          # 当前得分
        self.level = 1          # 当前等级
        self.lines_cleared = 0  # 已消除的行数
        self.fall_time = 0      # 方块下落计时器
        self.fall_speed = 500   # 方块下落速度(毫秒)
        
        # ==================== 当前和下一个方块初始化 ====================
        # 获取第一个随机方块作为当前方块
        self.current_piece = self.get_new_piece()
        # 预生成下一个方块,用于预览显示
        self.next_piece = self.get_new_piece()
        
        # ==================== 字体设置初始化 ====================
        # 创建默认字体对象,用于显示英文文字(48像素大小)
        self.font = pygame.font.Font(None, 48)
        # 尝试加载中文字体,如果失败则使用默认字体作为备用
        try:
            # 使用系统宋体字体显示中文文字(36像素大小)
            self.font_chinese = pygame.font.SysFont('simsun', 36)
        except:
            # 如果中文字体加载失败,使用默认字体
            self.font_chinese = self.font
        
    def get_new_piece(self):
        """生成新的俄罗斯方块"""
        # 随机选择一个形状索引(0-6,对应7种不同的俄罗斯方块形状)
        shape_index = random.randint(0, len(TETRIS_SHAPES) - 1)
        # 返回一个包含方块所有信息的字典
        return {
            'shape': TETRIS_SHAPES[shape_index],    # 方块的形状数据(包含所有旋转状态)
            'rotation': 0,                          # 当前旋转状态(从0开始)
            'x': GRID_WIDTH // 2 - 2,              # 初始X坐标(水平居中)
            'y': 0,                                 # 初始Y坐标(从顶部开始)
            'color': SHAPE_COLORS[shape_index]      # 方块的颜色
        }
    
    def is_valid_position(self, piece, dx=0, dy=0, rotation=None):
        """检查方块位置是否有效"""
        # 如果没有指定旋转状态,使用当前旋转状态
        if rotation is None:
            rotation = piece['rotation']
        
        # 获取指定旋转状态下的方块形状
        shape = piece['shape'][rotation]
        
        # 遍历方块形状的每一行
        for y, row in enumerate(shape):
            # 遍历每一行的每一列
            for x, cell in enumerate(row):
                # 如果当前位置有方块(用'#'表示)
                if cell == '#':
                    # 计算方块在游戏网格中的实际坐标
                    new_x = piece['x'] + x + dx  # 当前X + 形状内偏移 + 移动偏移
                    new_y = piece['y'] + y + dy  # 当前Y + 形状内偏移 + 移动偏移
                    
                    # 检查是否超出游戏区域边界
                    if (new_x < 0 or new_x >= GRID_WIDTH or 
                        new_y >= GRID_HEIGHT):
                        return False  # 超出边界,位置无效
                    
                    # 检查是否与已放置的方块重叠
                    # new_y >= 0 确保不检查游戏区域上方的位置
                    if new_y >= 0 and self.grid[new_y][new_x] != 0:
                        return False  # 发生重叠,位置无效
        
        return True  # 所有检查通过,位置有效
    
    def place_piece(self, piece):
        """将方块放置到游戏区域"""
        # 获取当前旋转状态下的方块形状
        shape = piece['shape'][piece['rotation']]
        
        # 遍历方块形状的每一行
        for y, row in enumerate(shape):
            # 遍历每一行的每一列
            for x, cell in enumerate(row):
                # 如果当前位置有方块(用'#'表示)
                if cell == '#':
                    # 计算在游戏网格中的坐标
                    grid_x = piece['x'] + x  # 方块X坐标 + 形状内X偏移
                    grid_y = piece['y'] + y  # 方块Y坐标 + 形状内Y偏移
                    # 只有当Y坐标在游戏区域内时才放置方块
                    if grid_y >= 0:
                        # 将方块的颜色放置到游戏网格中
                        self.grid[grid_y][grid_x] = piece['color']
    
    def clear_lines(self):
        """清除完整的行"""
        # 存储需要清除的行号
        lines_to_clear = []
        
        # ==================== 查找完整行 ====================
        # 从上到下检查每一行
        for y in range(GRID_HEIGHT):
            # 检查当前行是否完全填满(所有位置都不为0)
            if all(self.grid[y][x] != 0 for x in range(GRID_WIDTH)):
                lines_to_clear.append(y)  # 添加到待清除列表
        
        # ==================== 清除完整行 ====================
        # 删除所有完整的行
        for y in lines_to_clear:
            del self.grid[y]  # 删除完整行
            # 在顶部插入新的空行(全为0)
            self.grid.insert(0, [0 for _ in range(GRID_WIDTH)])
        
        # ==================== 更新游戏状态 ====================
        lines_cleared = len(lines_to_clear)  # 本次清除的行数
        if lines_cleared > 0:
            # 更新总清除行数
            self.lines_cleared += lines_cleared
            # 分数计算:1行100分,2行300分,3行500分,4行800分
            score_values = [0, 100, 300, 500, 800]
            # 根据清除行数和当前等级计算得分
            self.score += score_values[min(lines_cleared, 4)] * self.level
            
            # 每清除10行提升一个等级
            self.level = self.lines_cleared // 10 + 1
            # 等级越高,下降速度越快(最快50毫秒)
            self.fall_speed = max(50, 500 - (self.level - 1) * 50)
    
    def rotate_piece(self, piece):
        """旋转方块"""
        # 计算下一个旋转状态(循环:0->1->2->3->0...)
        new_rotation = (piece['rotation'] + 1) % len(piece['shape'])
        # 检查新的旋转状态是否有效(不会碰撞或超出边界)
        if self.is_valid_position(piece, rotation=new_rotation):
            # 如果有效,更新方块的旋转状态
            piece['rotation'] = new_rotation
    
    def draw_grid(self):
        """绘制游戏网格"""
        # ==================== 绘制垂直网格线 ====================
        # 绘制GRID_WIDTH+1条垂直线(包括左右边界)
        for x in range(GRID_WIDTH + 1):
            # 计算当前垂直线的X坐标
            line_x = GRID_X_OFFSET + x * BLOCK_SIZE
            # 绘制从顶部到底部的垂直线
            pygame.draw.line(self.screen, GRAY,
                           (line_x, GRID_Y_OFFSET),  # 起点:顶部
                           (line_x, GRID_Y_OFFSET + GRID_HEIGHT * BLOCK_SIZE))  # 终点:底部
        
        # ==================== 绘制水平网格线 ====================
        # 绘制GRID_HEIGHT+1条水平线(包括上下边界)
        for y in range(GRID_HEIGHT + 1):
            # 计算当前水平线的Y坐标
            line_y = GRID_Y_OFFSET + y * BLOCK_SIZE
            # 绘制从左侧到右侧的水平线
            pygame.draw.line(self.screen, GRAY,
                           (GRID_X_OFFSET, line_y),  # 起点:左侧
                           (GRID_X_OFFSET + GRID_WIDTH * BLOCK_SIZE, line_y))  # 终点:右侧
    
    def draw_piece(self, piece):
        """绘制当前下落的方块"""
        # 获取当前旋转状态下的方块形状
        shape = piece['shape'][piece['rotation']]
        
        # 遍历方块形状的每一行
        for y, row in enumerate(shape):
            # 遍历每一行的每一列
            for x, cell in enumerate(row):
                # 如果当前位置有方块(用'#'表示)
                if cell == '#':
                    # 计算方块在屏幕上的绘制坐标
                    draw_x = GRID_X_OFFSET + (piece['x'] + x) * BLOCK_SIZE
                    draw_y = GRID_Y_OFFSET + (piece['y'] + y) * BLOCK_SIZE
                    
                    # 绘制方块(留1像素边距以显示网格线)
                    pygame.draw.rect(self.screen, piece['color'],
                                   (draw_x + 1, draw_y + 1, BLOCK_SIZE - 2, BLOCK_SIZE - 2))
    
    def draw_placed_blocks(self):
        """绘制已放置在游戏区域中的方块"""
        # 遍历游戏网格的每一行
        for y in range(GRID_HEIGHT):
            # 遍历每一行的每一列
            for x in range(GRID_WIDTH):
                # 如果当前位置有方块(不为0表示有颜色)
                if self.grid[y][x] != 0:
                    # 计算方块在屏幕上的绘制坐标
                    draw_x = GRID_X_OFFSET + x * BLOCK_SIZE
                    draw_y = GRID_Y_OFFSET + y * BLOCK_SIZE
                    
                    # 绘制方块(留1像素边距以显示网格线)
                    pygame.draw.rect(self.screen, self.grid[y][x],
                                   (draw_x + 1, draw_y + 1, BLOCK_SIZE - 2, BLOCK_SIZE - 2))
    
    def draw_ui(self):
        """绘制用户界面(分数、等级、行数、下一个方块预览)"""
        # ==================== 计算UI区域位置 ====================
        # 计算UI区域的起始X坐标(游戏区域右侧30像素处)
        ui_x = GRID_X_OFFSET + GRID_WIDTH * BLOCK_SIZE + 30
        
        # ==================== 绘制分数信息 ====================
        # 尝试使用中文字体渲染分数,失败则使用英文
        try:
            score_text = self.font_chinese.render(f"分数: {self.score}", True, WHITE)
        except:
            score_text = self.font.render(f"Score: {self.score}", True, WHITE)
        # 将分数文本绘制到屏幕上
        self.screen.blit(score_text, (ui_x, 50))
        
        # ==================== 绘制等级信息 ====================
        # 尝试使用中文字体渲染等级,失败则使用英文
        try:
            level_text = self.font_chinese.render(f"等级: {self.level}", True, WHITE)
        except:
            level_text = self.font.render(f"Level: {self.level}", True, WHITE)
        # 将等级文本绘制到屏幕上
        self.screen.blit(level_text, (ui_x, 100))
        
        # ==================== 绘制已清除行数信息 ====================
        # 尝试使用中文字体渲染行数,失败则使用英文
        try:
            lines_text = self.font_chinese.render(f"行数: {self.lines_cleared}", True, WHITE)
        except:
            lines_text = self.font.render(f"Lines: {self.lines_cleared}", True, WHITE)
        # 将行数文本绘制到屏幕上
        self.screen.blit(lines_text, (ui_x, 150))
        
        # ==================== 绘制下一个方块预览标题 ====================
        # 尝试使用中文字体渲染"下一个"标题,失败则使用英文
        try:
            next_text = self.font_chinese.render("下一个:", True, WHITE)
        except:
            next_text = self.font.render("Next:", True, WHITE)
        # 将标题文本绘制到屏幕上
        self.screen.blit(next_text, (ui_x, 200))
        
        # ==================== 绘制下一个方块预览图形 ====================
        # 获取下一个方块的第一个旋转状态(默认状态)
        shape = self.next_piece['shape'][0]
        # 遍历方块形状的每一行
        for y, row in enumerate(shape):
            # 遍历每一行的每一列
            for x, cell in enumerate(row):
                # 如果当前位置有方块(用'#'表示)
                if cell == '#':
                    # 计算预览方块的绘制坐标(相对于UI区域)
                    draw_x = ui_x + 20 + x * 20  # UI区域X + 20像素偏移 + 列索引*20像素
                    draw_y = 240 + y * 20         # 固定Y位置240 + 行索引*20像素
                    # 绘制预览方块(18x18像素,留2像素间距)
                    pygame.draw.rect(self.screen, self.next_piece['color'],
                                   (draw_x, draw_y, 18, 18))
    
    def is_game_over(self):
        """检查游戏是否结束"""
        # 游戏结束条件:新生成的方块无法放置在初始位置
        # 如果当前方块在初始位置就无效,说明游戏区域已满
        return not self.is_valid_position(self.current_piece)
    
    def run(self):
        """游戏主循环 - 处理事件、更新游戏状态、绘制画面"""
        # 游戏运行标志
        running = True
        
        # ==================== 主游戏循环 ====================
        while running:
            # 控制游戏帧率为60FPS,并获取上一帧的时间间隔(毫秒)
            dt = self.clock.tick(60)
            # 累加方块下落时间
            self.fall_time += dt
            
            # ==================== 事件处理 ====================
            # 处理所有pygame事件(键盘输入、窗口关闭等)
            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 self.is_valid_position(self.current_piece, dx=-1):
                            self.current_piece['x'] -= 1  # 向左移动一格
                    
                    # 右箭头键:向右移动方块
                    elif event.key == pygame.K_RIGHT:
                        # 检查右移是否有效(不会碰撞或超出边界)
                        if self.is_valid_position(self.current_piece, dx=1):
                            self.current_piece['x'] += 1  # 向右移动一格
                    
                    # 下箭头键:加速下降方块
                    elif event.key == pygame.K_DOWN:
                        # 检查下移是否有效(不会碰撞或到达底部)
                        if self.is_valid_position(self.current_piece, dy=1):
                            self.current_piece['y'] += 1  # 向下移动一格
                    
                    # 上箭头键:旋转方块
                    elif event.key == pygame.K_UP:
                        # 尝试旋转当前方块
                        self.rotate_piece(self.current_piece)
                    
                    # 空格键:直接下降到底部
                    elif event.key == pygame.K_SPACE:
                        # 持续下移直到无法继续下移
                        while self.is_valid_position(self.current_piece, dy=1):
                            self.current_piece['y'] += 1
            
            # ==================== 自动下降逻辑 ====================
            # 检查是否到了自动下降的时间
            if self.fall_time >= self.fall_speed:
                # 尝试让当前方块下降一格
                if self.is_valid_position(self.current_piece, dy=1):
                    self.current_piece['y'] += 1  # 继续下降
                else:
                    # 方块无法继续下降,进行放置和清理操作
                    self.place_piece(self.current_piece)      # 将方块放置到游戏区域
                    self.clear_lines()                        # 检查并清除完整的行
                    self.current_piece = self.next_piece     # 当前方块变为下一个方块
                    self.next_piece = self.get_new_piece()   # 生成新的下一个方块
                    
                    # 检查游戏是否结束
                    if self.is_game_over():
                        print(f"游戏结束!最终分数: {self.score}")
                        running = False  # 退出游戏循环
                
                # 重置下降计时器
                self.fall_time = 0
            
            # ==================== 绘制游戏画面 ====================
            # 清空屏幕(填充黑色背景)
            self.screen.fill(BLACK)
            # 绘制游戏网格线
            self.draw_grid()
            # 绘制已放置的方块
            self.draw_placed_blocks()
            # 绘制当前下落的方块
            self.draw_piece(self.current_piece)
            # 绘制用户界面(分数、等级等信息)
            self.draw_ui()
            
            # 更新显示(将绘制的内容显示到屏幕上)
            pygame.display.flip()
        
        # ==================== 游戏结束清理 ====================
        # 退出pygame
        pygame.quit()
        # 退出程序
        sys.exit()

# ==================== 程序入口点 ====================
if __name__ == "__main__":
    # 显示游戏欢迎信息和操作说明
    print("欢迎来到俄罗斯方块游戏!")
    print("操作说明:")
    print("← → : 左右移动")      # 左右箭头键控制方块左右移动
    print("↓ : 加速下降")        # 下箭头键加速方块下降
    print("↑ : 旋转方块")        # 上箭头键旋转方块
    print("空格 : 直接下降到底部")  # 空格键让方块直接下降到底部
    print("")                    # 空行分隔
    
    # 创建游戏实例并开始运行
    game = Tetris()
    game.run()

相关推荐

Python函数参数和返回值类型:让你的代码更清晰、更健壮

在Python开发中,你是否遇到过这些抓狂时刻?同事写的函数参数类型全靠猜调试两小时发现传了字符串给数值计算函数重构代码时不知道函数返回的是列表还是字典今天教你两招,彻底解决类型混乱问题!让你的...

有公司内部竟然禁用了python开发,软件开发何去何从?

今天有网友在某社交平台发文:有公司内部竟然禁止了python开发!帖子没几行,评论却炸锅了。有的说“太正常,Python本就不适合做大项目”,还有的反驳“飞书全员用Python”。暂且不说这家公司...

写 Python 七年才发现的七件事:真正提高生产力的脚本思路

如果你已经用Python写了不少脚本,却总觉得代码只是“能跑”,这篇文章或许会刷新你对这门语言的认知。以下七个思路全部来自一线实战,没有花哨的概念,只有可落地的工具与习惯。它们曾帮我省下大量无意义...

用Python写一个A*搜索算法含注释说明

大家好!我是幻化意识流。今天我们用Python写一个A*搜索算法的代码,我做了注释说明,欢迎大家一起学习:importheapq#定义搜索节点类,包括当前状态、从初始状态到该状态的代价g、从该状态...

使用python制作一个贪吃蛇游戏,并为每一句添加注释方便学习

今天来设计一个贪吃蛇的经典小游戏。先介绍下核心代码功能(源代码请往最后面拉):游戏功能:-四个难度等级:简单(8FPS)、中等(12FPS)、困难(18FPS)、专家(25FPS)-美...

Python 之父 Guido van Rossum 宣布退休

Python之父GuidovanRossum在推特公布了自己从Dropbox公司离职的消息,并表示已经退休。他还提到自己在Dropbox担任工程师期间学到了很多东西——Python的类型注解(T...

4 个早该掌握的 Python 类型注解技巧

在Python的开发过程中,类型注解常常被忽视。但当面对一段缺乏类型提示、逻辑复杂的代码时,理解和维护成本会迅速上升,极易陷入“阅读地狱”。本文整理了4个关于Python类型注解的重要技巧...

让你的Python代码更易读:7个提升函数可读性的实用技巧

如果你正在阅读这篇文章,很可能你已经用Python编程有一段时间了。今天,让我们聊聊可以提升你编程水平的一件事:编写易读的函数。请想一想:我们花在阅读代码上的时间大约是写代码的10倍。所以,每当你创建...

Python异常模块和包

异常当检测到一个错误时,Python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的“异常”,也就是我们常说的BUG例如:以`r`方式打开一个不存在的文件。f=open('...

别再被 return 坑了!一文吃透 Python return 语句常见错误与调试方法

Pythonreturn语句常见错误与调试方法(结构化详解)一.语法错误:遗漏return或返回值类型错误错误场景pythondefadd(a,b):print(a+b)...

Python数据校验不再难:Pydantic库的工程化实践指南

在FastAPI框架横扫Python后端开发领域的今天,其默认集成的Pydantic库正成为处理数据验证的黄金标准。这个看似简单的库究竟隐藏着哪些让开发者爱不释手的能力?本文将通过真实项目案例,带您解...

python防诈骗的脚本带注释信息

以下是一个简单但功能完整的防诈骗脚本,包含URL检测、文本分析和风险评估功能。代码结构清晰,带有详细注释,适合作为个人或家庭防诈骗工具使用。这个脚本具有以下功能:文本诈骗风险分析:检测常见诈骗关键...

Python判断语句

布尔类型和比较运算符布尔类型的定义:布尔类型只有两个值:True和False可以通过定义变量存储布尔类型数据:变量名称=布尔类型值(True/False)布尔类型不仅可以自行定义,同时也可通过...

使用python编写俄罗斯方块小游戏并为每一句添加注释,方便学习

先看下学习指导#俄罗斯方块游戏开发-Python学习指导##项目概述这个俄罗斯方块游戏是一个完整的Python项目,涵盖了以下重要的编程概念:-面向对象编程(OOP)-游戏开发基础-数据...

Python十大技巧:不掌握这些,你可能一直在做无用功!

在编程的世界里,掌握一门语言只是起点,如何写出优雅、高效的代码才是真功夫。Python作为最受欢迎的编程语言之一,拥有简洁明了的语法,但要想真正精通这门语言,还需要掌握一些实用的高级技巧。一、列表推导...

取消回复欢迎 发表评论: