🎮 Python实战:从零开始撸一个俄罗斯方块,代码逻辑大白话讲透!(附完整代码)

长文警告。本文章比较长,学习内容比较丰富,学不进去的换个短点的。

0. 前言:老古董的情怀 (≧∇≦)ノ

兄弟们!姐妹们!今天咱们不整那些花里胡哨的AI,也不搞那些看不懂的算法。咱们来聊聊童年回忆——俄罗斯方块!🎮

┌─────────────────────────────┐
│  还记得这个吗?              │
│                              │
│    ▓▓▓▓                      │
│    ▓  ▓                      │
│    ▓  ▓▓▓                    │
│                              │
│  满满的都是回忆啊!😭        │
└─────────────────────────────┘

还记得当年拿着那种黑白掌机,或者在那个只能打电话发短信的手机里,玩得不亦乐乎的日子吗?那时候觉得这游戏真神奇,简简单单几个方块,怎么就能让人上头呢?

童年记忆:
┌──────┐
│ 🎮   │ ← 掌机
│  ▓▓  │
│  ▓   │
│  ▓▓▓ │
└──────┘
   ↓
一玩就是一下午 ⏰

其实,俄罗斯方块就是编程界的"Hello World"进阶版。它不复杂,但包含了游戏开发的几乎所有核心逻辑:渲染、事件处理、状态更新、碰撞检测、数据结构

今天我就带大家用Python和Pygame,从零开始,一步一步把这个"老古董"给它复刻出来!

放心,我不讲那些晦涩的术语,咱们用大白话,敲代码就像搭积木一样简单!(๑•̀ㅂ•́)و✧

1. 搭建舞台:画个框框装得下梦想 🎭

1.1 游戏是啥?就是"画"和"变"

不管多复杂的游戏,核心就两件事:

  1. 画出来:让玩家看见 👀
  2. 变起来:让玩家去操作、去影响 🎮
    俄罗斯方块最基础的部分,就是那个长方形的游戏区域。你可以把它想象成一块菜地 🌱,或者一个柜子 🗄️。咱们的积木(方块)就要往这个柜子里塞。
想象一下:
┌─────────────────┐
│                 │ ← 空柜子
│                 │
│                 │
│                 │
│                 │
└─────────────────┘
   ↓
要往里面塞积木 🧱

1.2 别急着写逻辑,先画个窗口 🖼️

这一步咱们只做一件事:打开一个黑乎乎的窗口,里面画上格子。这就像装修房子,先得把毛坯房弄好,后面才能摆家具。🏠
代码先上:

import pygame
import sys
# 初始化 pygame,这就像开机键,按一下才能用 🔘
pygame.init()
# 定义颜色常量,RGB值,不用死记,知道大概就行
BLACK = (0, 0, 0)       # 黑色,背景色 ⬛
WHITE = (255, 255, 255) # 白色,格子线 ⬜
GRAY = (50, 50, 50)     # 灰色,有点深度的背景
# 屏幕尺寸
WIDTH = 400  # 宽 📏
HEIGHT = 500 # 高 📏
BLOCK_SIZE = 20  # 每个格子的大小,20x20像素
# 计算一下地图有多少格
GRID_WIDTH = WIDTH // BLOCK_SIZE   # 20格
GRID_HEIGHT = HEIGHT // BLOCK_SIZE # 25格
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("我的第一个俄罗斯方块 🎮")
# 游戏主循环,这就是游戏的"心脏",一刻不停地跳动 💓
running = True
clock = pygame.time.Clock() # 控制游戏速度,别跑太快了 🐢
while running:
    # 1. 监听事件:比如你点了关闭窗口,或者按了键盘 👂
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False # 关灯,下班!🌙
    # 2. 画面绘制 🎨
    screen.fill(BLACK) # 先刷一层黑漆
    # 画网格线,就像在田里画垄沟 🌾
    for x in range(0, WIDTH, BLOCK_SIZE):
        pygame.draw.line(screen, GRAY, (x, 0), (x, HEIGHT))
    for y in range(0, HEIGHT, BLOCK_SIZE):
        pygame.draw.line(screen, GRAY, (0, y), (WIDTH, y))
    # 3. 刷新显示,把画好的东西翻出来给人看 👀
    pygame.display.flip()
    # 控制帧率,60帧每秒,流畅不卡顿 🎬
    clock.tick(60)
pygame.quit()
sys.exit()

运行一下,你会看到一个带网格的黑窗口。看着是不是有点感觉了?这就是咱们的"战场"!⚔️

效果图大概是这样:
┌───┬───┬───┬───┬───┐
│   │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
└───┴───┴───┴───┴───┘
   网格地图完成!✅

2. 定义主角:方块是个什么东西?🧱

2.1 方块不仅是图片,是"数据" 💾

很多新手觉得:“方块不就是一张图片吗?”。
错!图片只是它的皮囊 👔。它的灵魂是数据!💭

新手眼中的方块:
┌────────┐
│  🖼️    │ ← 一张图片
│  图片  │
└────────┘
高手眼中的方块:
┌────────┐
│  📊    │ ← 数据结构
│  坐标  │
│  形状  │
│  颜色  │
└────────┘

想象一下,咱们玩的是一个填格子的游戏。屏幕上的每个小格子,要么是"空"的,要么是"被占用"的。
那么,一个正在下落的"L"形方块,其实就可以用一串数字(坐标)来表示。

2.2 用"列表"画方块 📝

我们来看看经典的"L"形方块。我们可以用一个简单的二维列表来描述它的形状:

L形方块(假设用1表示有积木,0表示空):
┌───┬───┐
│ 1 │ 0 │
├───┼───┤
│ 1 │ 0 │
├───┼───┤
│ 1 │ 1 │
└───┴───┘
用列表表示:
[
  [1, 0],
  [1, 0],
  [1, 1]
]

这就像咱们小时候玩的像素画 🎨,或者十字绣图纸 🧵。1就是绣花针扎下去的地方,0就是留白。

像素画风格:
┌─────────────────┐
│  ▓░             │
│  ▓░             │
│  ▓▓             │
│                 │
│  L形方块!✨    │
└─────────────────┘

2.3 设计方块类 🏗️

咱们来写个类,把方块"造"出来。这个类要记住:

  • 它是谁?(形状 Shape)
  • 它在哪?
  • 它穿什么衣服?(颜色 Color)
# 形状定义,咱们用经典的几种
# 这里的字符串 '.' 代表空,'0' 代表实心 💎
SHAPES = [
    [
        ['.....',
         '.....',
         '..0..',
         '.000.',
         '.....'], # T形 🔷
        ['.....',
         '..0..',
         '..00.',
         '..0..',
         '.....'],
        # ... 其他旋转形态可以继续加,为了篇幅先省略
    ],
    # 这里可以加 I, J, L, O, S, Z 等形状
    # 为了演示,我们暂时只定义一个简单的 T 形列表
]
class Tetromino:
    def __init__(self, x, y):
        self.x = x  # 在地图网格中的横坐标 ↔️
        self.y = y  # 在地图网格中的纵坐标 ↕️
        self.type = 0 # 假设第0个形状是T形 🔷
        self.color = (0, 255, 0) # 绿色 💚
        self.rotation = 0 # 当前的旋转状态索引 🔄
        
    def image(self):
        """获取当前方块形状的图像数据"""
        return SHAPES[self.type][self.rotation]

大白话解释 (ง •_•)ง:
这就好比我们捏了个泥人。

  • x, y 告诉我们泥人摆在桌子的哪个位置 📍
  • typerotation 告诉我们泥人摆什么姿势(是站着还是倒立)🤸
  • image() 就是给咱们画这个泥人的图纸 📋
泥人示意图:
┌──────────────┐
│   🗺️ 图纸    │
│   ┌─────┐    │
│   │ ◯   │    │
│   │ │   │    │
│   └─────┘    │
│              │
│   位置:(3,5)│
└──────────────┘

3. 核心逻辑:地图、碰撞与"空气墙" 🧱

这是最关键的部分,也是新手最容易晕的地方。咱们用个形象的比喻:走迷宫 🌀。

3.1 地图就是迷宫 🗺️

游戏区域GRID就是一个巨大的迷宫。

  • 0 代表路,可以走 🚶
  • 1 代表墙,撞上去就game over或者走不动 🧱
地图示意:
┌─────────────────┐
│ 0 0 0 0 0 0 0 0 │
│ 0 0 0 0 0 0 0 0 │
│ 0 0 1 1 0 0 0 0 │ ← 这里有墙!
│ 0 0 1 0 0 0 0 0 │
│ 0 0 1 1 0 0 0 0 │
│ 1 1 1 1 1 1 1 1 │ ← 地面全是墙
└─────────────────┘

当我们控制方块移动时,其实是在问系统:“我下一步能不能走?” 🤔

3.2 碰撞检测:能不能走?🚦

这是游戏开发的灵魂——碰撞检测 💥。
每当我们想移动方块,我们就要预先判断:移动后的位置,会不会碰到墙壁?
会不会碰到已经堆在地上的积木?

如果会碰到,那就不让动!(╯°□°)╯︵ ┻━┻

def valid_space(piece, grid):
    """
    判断方块在当前位置是否合法(没撞墙,没撞其他积木)
    piece: 当前方块对象
    grid: 当前的地图数据(二维列表)
    """
    # 1. 获取方块的形状数据 📋
    shape = piece.image()
    
    # 2. 遍历方块里的每一个小格子 🔍
    for y, row in enumerate(shape):
        for x, cell in enumerate(row):
            if cell == '0': # 如果是实心格子 💎
                # 计算这个格子实际在地图上的位置
                board_x = piece.x + x
                board_y = piece.y + y
                
                # 判断1:撞墙了没?(超出地图边界)🧱
                if board_x < 0 or board_x >= GRID_WIDTH or board_y >= GRID_HEIGHT:
                    return False
                
                # 判断2:撞到"尸体"了没?(地图上该位置已有积木)💀
                # 注意:board_y < 0 是允许的,因为方块刚出来时可能还在屏幕外一点点
                if board_y >= 0 and grid[board_y][board_x] != (0,0,0): # 假设(0,0,0)是空
                    return False
    return True
碰撞检测流程:
┌──────────────────┐
│  我想往右移动    │
│      →→→        │
└──────────────────┘
        ↓
┌──────────────────┐
│  系统检查:      │
│  前面有墙吗?🧱  │
└──────────────────┘
        ↓
    ┌───────┐
    │ 没有!│
    └───────┘
        ↓
┌──────────────────┐
│  允许移动!✅    │
└──────────────────┘

大白话解释 (´・ω・`):
这就像你晚上起床上厕所,得先摸摸前面有没有墙,有墙就别硬闯,会撞头的!>_<

3.3 方块下落:地心引力 🌍

方块会自己往下掉,这个怎么实现呢?其实就是在每帧里,让y坐标自动加1。

# 在游戏主循环里
if fall_time >= fall_speed:  # 时间到了,该下落了 ⏰
    fall_time = 0
    piece.y += 1  # 往下走一格 ⬇️
    if not valid_space(piece, grid):  # 走不了了!
        piece.y -= 1  # 退回去!
        # 锁定方块,变成地图的一部分 🔒
        lock_piece(piece, grid)
下落过程:
第1帧:          第2帧:          第3帧:
┌────────┐      ┌────────┐      ┌────────┐
│        │      │        │      │        │
│   ▓    │      │        │      │        │
│   ▓    │  →   │   ▓    │  →   │        │
│   ▓    │      │   ▓    │      │   ▓    │
│        │      │   ▓    │      │   ▓    │
└────────┘      └────────┘      └────────┘
                              撞到底了!💥

4. 让方块听话:键盘控制 ⌨️

4.1 左右移动 ↔️

# 在事件处理循环里
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_LEFT:  # 按了左键 ⬅️
        piece.x -= 1  # 往左走
        if not valid_space(piece, grid):  # 撞墙了!
            piece.x += 1  # 退回来
    
    elif event.key == pygame.K_RIGHT:  # 按了右键 ➡️
        piece.x += 1  # 往右走
        if not valid_space(piece, grid):  # 撞墙了!
            piece.x -= 1  # 退回来
移动示意图:
┌────────────┐
│ ←左移  右移→│
│   ┌───┐    │
│   │ ▓ │    │
│   │ ▓▓│    │
│   └───┘    │
└────────────┘

4.2 旋转 🔄

旋转稍微复杂一点点,因为旋转后形状会变。我们要预先检查旋转后的形状会不会撞墙。

elif event.key == pygame.K_UP:  # 按了上键,旋转 🔼
    original_rotation = piece.rotation
    piece.rotation = (piece.rotation + 1) % len(SHAPES[piece.type])  # 换个姿势
    
    if not valid_space(piece, grid):  # 旋转后撞墙了!
        piece.rotation = original_rotation  # 换回去!
旋转变身!✨
    ┌───┐         ┌─────┐
    │   │         │     │
    │ ▓ │   →     │ ▓▓▓ │
    │ ▓▓│         │   ▓ │
    └───┘         └─────┘
    
   T形旋转90°

大白话解释 (◠‿◠):
旋转就像你在被窝里翻身,得先看看旁边有没有人(墙壁),有人的话就别翻了,会打到别人的!👊

4.3 加速下落 ⬇️

elif event.key == pygame.K_DOWN:  # 按了下键,加速 ⏬
    piece.y += 1
    if not valid_space(piece, grid):
        piece.y -= 1
        lock_piece(piece, grid)
正常下落:        加速下落:
  ▓                ▓
  ▓                
  ▓                ▓
                   ▓
                   ▓
  ↓                 ↓
 慢慢来          嗖的一下!💨

5. 消行得分:爽感的来源 🎉

5.1 什么叫"消行"?🤔

当一整行都被填满时,这行就会消失,上面的行往下掉,玩家得分。这是俄罗斯方块最爽的时刻!(≧▽≦)

消行前:
┌─────────────┐
│ 0 0 0 0 0 0 │
│ 1 1 1 1 1 1 │ ← 这行满了!
│ 0 0 1 0 0 0 │
└─────────────┘
消行后:
┌─────────────┐
│ 0 0 0 0 0 0 │ ← 上面的掉下来
│ 0 0 0 0 0 0 │
│ 0 0 1 0 0 0 │
└─────────────┘
   得分!+100 🎊

5.2 消行代码实现 💻

def clear_rows(grid):
    """
    检查并消除满行
    返回消除了几行
    """
    # 1. 找到所有满行 🔍
    full_rows = []
    for y in range(GRID_HEIGHT):
        if all(cell != (0,0,0) for cell in grid[y]):  # 这行全满了!
            full_rows.append(y)
    
    # 2. 删除满行,在上面添加空行 🗑️
    for y in full_rows:
        del grid[y]  # 删除这一行
        grid.insert(0, [(0,0,0) for _ in range(GRID_WIDTH)])  # 顶部加一行空
    
    return len(full_rows)
消行动画:
┌───────────────┐
│               │
│  ████████████ │ ← 满行!
│      ▓▓       │
│               │
└───────────────┘
        ↓
    💥 爆炸特效
        ↓
┌───────────────┐
│               │
│               │ ← 空了
│      ▓▓       │
│               │
└───────────────┘
   爽!٩(๑❛ᴗ❛๑)۶

6. 游戏结束:悲剧时刻 😭

6.1 什么时候算游戏结束?🏁

当新的方块出生时,发现出生点已经被占用了,那就说明——游戏结束

def check_game_over(piece, grid):
    """检查游戏是否结束"""
    if not valid_space(piece, grid):  # 新方块刚出来就撞墙!
        return True
    return False
游戏结束场景:
┌─────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ ← 堆到顶了!
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
└─────────────────┘
   GAME OVER! 💀

大白话解释 (╥﹏╥):
这就像你往杯子里倒水,倒着倒着溢出来了,那就说明——杯子满了,别倒了!

7. 完整代码大放送 🎁

好了,说了这么多,是时候上完整代码了!我把前面的逻辑都整合起来,加上了所有7种方块,还有颜色、计分等功能。

import pygame
import sys
import random
# 初始化 🔘
pygame.init()
# 常量定义 📏
WIDTH = 400
HEIGHT = 500
BLOCK_SIZE = 20
GRID_WIDTH = WIDTH // BLOCK_SIZE
GRID_HEIGHT = HEIGHT // BLOCK_SIZE
# 颜色定义 🎨
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (50, 50, 50)
COLORS = [
    (0, 255, 255),   # 青色 - I形 💎
    (255, 255, 0),   # 黄色 - O形 💛
    (128, 0, 128),   # 紫色 - T形 💜
    (255, 165, 0),   # 橙色 - L形 🧡
    (0, 0, 255),     # 蓝色 - J形 💙
    (0, 255, 0),     # 绿色 - S形 💚
    (255, 0, 0)      # 红色 - Z形 ❤️
]
# 形状定义(经典7种)🔷
SHAPES = [
    # I形
    [['.....',
      '.....',
      '0000.',
      '.....',
      '.....'],
     ['..0..',
      '..0..',
      '..0..',
      '..0..',
      '.....']],
    # O形
    [['.....',
      '.00..',
      '.00..',
      '.....',
      '.....']],
    # T形
    [['.....',
      '..0..',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '..0..',
      '.....'],
     ['.....',
      '..0..',
      '.00..',
      '..0..',
      '.....']],
    # L形
    [['.....',
      '..0..',
      '..0..',
      '..00.',
      '.....'],
     ['.....',
      '...0.',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '.00..',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '.0...',
      '.....']],
    # J形
    [['.....',
      '..0..',
      '..0..',
      '.00..',
      '.....'],
     ['.....',
      '.0...',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..00.',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '...0.',
      '.....']],
    # S形
    [['.....',
      '..00.',
      '.00..',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '...0.',
      '.....']],
    # Z形
    [['.....',
      '.00..',
      '..00.',
      '.....',
      '.....'],
     ['.....',
      '...0.',
      '..00.',
      '..0..',
      '.....']]
]
class Tetromino:
    """方块类 🧱"""
    def __init__(self, x, y, shape_type):
        self.x = x
        self.y = y
        self.type = shape_type
        self.color = COLORS[shape_type]
        self.rotation = 0
        
    def image(self):
        """获取当前形状 📋"""
        return SHAPES[self.type][self.rotation]
    
    def rotate(self):
        """旋转 🔄"""
        self.rotation = (self.rotation + 1) % len(SHAPES[self.type])
class Tetris:
    """游戏主类 🎮"""
    def __init__(self):
        self.grid = [[BLACK for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
        self.current_piece = None
        self.next_piece = None
        self.score = 0
        self.level = 1
        self.fall_speed = 500  # 毫秒
        self.fall_time = 0
        self.game_over = False
        
        self.new_piece()
    
    def new_piece(self):
        """生成新方块 ✨"""
        if self.next_piece is None:
            self.next_piece = Tetromino(GRID_WIDTH // 2 - 2, 0, random.randint(0, 6))
        
        self.current_piece = self.next_piece
        self.next_piece = Tetromino(GRID_WIDTH // 2 - 2, 0, random.randint(0, 6))
        
        # 检查游戏结束 💀
        if not self.valid_space(self.current_piece):
            self.game_over = True
    
    def valid_space(self, piece, dx=0, dy=0, dr=0):
        """碰撞检测 🚦"""
        new_x = piece.x + dx
        new_y = piece.y + dy
        new_rotation = (piece.rotation + dr) % len(SHAPES[piece.type])
        shape = SHAPES[piece.type][new_rotation]
        
        for y, row in enumerate(shape):
            for x, cell in enumerate(row):
                if cell == '0':
                    board_x = new_x + x
                    board_y = new_y + y
                    
                    # 边界检查 🧱
                    if board_x < 0 or board_x >= GRID_WIDTH or board_y >= GRID_HEIGHT:
                        return False
                    
                    # 碰撞检查 💥
                    if board_y >= 0 and self.grid[board_y][board_x] != BLACK:
                        return False
        return True
    
    def lock_piece(self):
        """锁定方块 🔒"""
        shape = self.current_piece.image()
        for y, row in enumerate(shape):
            for x, cell in enumerate(row):
                if cell == '0':
                    board_x = self.current_piece.x + x
                    board_y = self.current_piece.y + y
                    if 0 <= board_y < GRID_HEIGHT and 0 <= board_x < GRID_WIDTH:
                        self.grid[board_y][board_x] = self.current_piece.color
        
        # 消行 🧹
        self.clear_rows()
        
        # 新方块 ✨
        self.new_piece()
    
    def clear_rows(self):
        """消除满行 🎉"""
        rows_to_clear = []
        for y in range(GRID_HEIGHT):
            if all(cell != BLACK for cell in self.grid[y]):
                rows_to_clear.append(y)
        
        for y in rows_to_clear:
            del self.grid[y]
            self.grid.insert(0, [BLACK for _ in range(GRID_WIDTH)])
        
        # 计分 💰
        scores = [0, 100, 300, 500, 800]  # 0行, 1行, 2行, 3行, 4行
        self.score += scores[len(rows_to_clear)]
        
        # 升级 ⬆️
        self.level = self.score // 1000 + 1
        self.fall_speed = max(100, 500 - (self.level - 1) * 50)
    
    def update(self, dt):
        """更新游戏状态 🔄"""
        if self.game_over:
            return
        
        self.fall_time += dt
        if self.fall_time >= self.fall_speed:
            self.fall_time = 0
            if self.valid_space(self.current_piece, dy=1):
                self.current_piece.y += 1
            else:
                self.lock_piece()
    
    def move(self, dx):
        """左右移动 ↔️"""
        if self.valid_space(self.current_piece, dx=dx):
            self.current_piece.x += dx
    
    def rotate(self):
        """旋转 🔄"""
        if self.valid_space(self.current_piece, dr=1):
            self.current_piece.rotate()
    
    def drop(self):
        """直接下落 ⬇️"""
        while self.valid_space(self.current_piece, dy=1):
            self.current_piece.y += 1
        self.lock_piece()
def draw_grid(screen, game):
    """绘制地图 🗺️"""
    for y in range(GRID_HEIGHT):
        for x in range(GRID_WIDTH):
            rect = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
            pygame.draw.rect(screen, game.grid[y][x], rect)
            pygame.draw.rect(screen, GRAY, rect, 1)
def draw_piece(screen, piece):
    """绘制当前方块 🧱"""
    shape = piece.image()
    for y, row in enumerate(shape):
        for x, cell in enumerate(row):
            if cell == '0':
                rect = pygame.Rect(
                    (piece.x + x) * BLOCK_SIZE,
                    (piece.y + y) * BLOCK_SIZE,
                    BLOCK_SIZE, BLOCK_SIZE
                )
                pygame.draw.rect(screen, piece.color, rect)
                pygame.draw.rect(screen, WHITE, rect, 2)
def draw_info(screen, game, font):
    """绘制信息 📊"""
    # 分数 💰
    score_text = font.render(f"Score: {game.score}", True, WHITE)
    screen.blit(score_text, (10, 10))
    
    # 等级 ⬆️
    level_text = font.render(f"Level: {game.level}", True, WHITE)
    screen.blit(level_text, (10, 40))
    
    # 游戏结束 💀
    if game.game_over:
        game_over_text = font.render("GAME OVER!", True, (255, 0, 0))
        rect = game_over_text.get_rect(center=(WIDTH // 2, HEIGHT // 2))
        pygame.draw.rect(screen, BLACK, rect.inflate(20, 20))
        screen.blit(game_over_text, rect)
def main():
    """主函数 🎬"""
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("俄罗斯方块 🎮")
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 36)
    
    game = Tetris()
    
    running = True
    while running:
        dt = clock.tick(60)
        
        # 事件处理 👂
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            
            if event.type == pygame.KEYDOWN and not game.game_over:
                if event.key == pygame.K_LEFT:
                    game.move(-1)  # ⬅️
                elif event.key == pygame.K_RIGHT:
                    game.move(1)   # ➡️
                elif event.key == pygame.K_UP:
                    game.rotate()  # 🔄
                elif event.key == pygame.K_DOWN:
                    game.drop()    # ⬇️
        
        # 更新 🔄
        game.update(dt)
        
        # 绘制 🎨
        screen.fill(BLACK)
        draw_grid(screen, game)
        draw_piece(screen, game.current_piece)
        draw_info(screen, game, font)
        
        pygame.display.flip()
    
    pygame.quit()
    sys.exit()
if __name__ == "__main__":
    main()

8. 运行效果展示 ✨

运行上面的代码,你会看到:

┌─────────────────────────────┐
│  Score: 500  Level: 1       │
│  ┌───────────────────────┐  │
│  │                       │  │
│  │                       │  │
│  │         ▓▓            │  │
│  │         ▓▓            │  │
│  │       ▓▓▓▓            │  │
│  │     ▓▓▓▓▓▓            │  │
│  │   ▓▓▓▓▓▓▓▓            │  │
│  │ ▓▓▓▓▓▓▓▓▓▓            │  │
│  └───────────────────────┘  │
│                             │
│  操作说明:                 │
│  ← → 移动  ↑ 旋转  ↓ 快速下落 │
└─────────────────────────────┘

恭喜你!你已经完成了一个完整的俄罗斯方块游戏!🎉🎉🎉

成功截图:
┌────────────────────┐
│                    │
│   ✅ 任务完成!    │
│                    │
│   🎊 恭喜你!      │
│   🎮 你已经掌握了  │
│   🧱 俄罗斯方块的  │
│   💻 核心逻辑!    │
│                    │
└────────────────────┘

9. 总结:咱们学到了什么?📚

通过这个项目,我们学会了:

知识清单:
┌──────────────────────┐
│ ✅ Pygame基础使用     │
│ ✅ 游戏循环的概念     │
│ ✅ 碰撞检测的实现     │
│ ✅ 数据结构的设计     │
│ ✅ 事件处理机制       │
│ ✅ 游戏状态管理       │
└──────────────────────┘
技能树:
🎮 游戏开发
  ├── 🖼️ 渲染系统
  ├── 🎯 事件系统
  ├── 🔄 更新系统
  └── 💥 碰撞系统

大白话总结 (◠‿◠✿):
俄罗斯方块就像编程界的"麻雀",麻雀虽小,五脏俱全。你把它搞懂了,其他游戏的基本套路也就通了!

10. 进阶建议:还能怎么玩?🚀

如果你想继续挑战,可以试试:

进阶路线图:
┌─────────────────────────────┐
│ 🎵 添加音效和背景音乐       │
│ 🎨 美化界面,添加皮肤系统   │
│ 📊 添加最高分记录           │
│ 🤖 实现AI自动玩             │
│ 🌐 添加联机对战功能         │
│ 📱 移植到手机端             │
└─────────────────────────────┘
难度等级:
⭐⭐☆☆☆  添加音效
⭐⭐⭐☆☆  美化界面
⭐⭐⭐⭐☆  AI自动玩
⭐⭐⭐⭐⭐  联机对战

11. 常见问题解答 ❓

Q1: 为什么我的方块旋转后位置不对?🤔

A: 可能是旋转中心没设置好。每种方块的旋转中心不太一样,需要微调。

旋转中心:
┌─────────┐
│    ●    │ ← 中心点
│   ▓▓▓   │
│    ▓    │
└─────────┘

Q2: 如何让游戏更流畅?🎬

A: 注意控制帧率,不要让游戏跑太快。用clock.tick(60)锁定60帧。

Q3: 能不能用键盘长按来连续移动?⌨️

A: 当然可以!需要额外处理pygame.KEYDOWN和持续按键的逻辑。

12. 最后的话 💬

写到这里,我想起了当年第一次玩俄罗斯方块的情景。那时候觉得这个游戏好简单,但是越玩越上瘾。现在用代码把它实现出来,才发现简单的背后其实藏着很多精巧的设计

游戏截图:
┌────────────────────┐
│                    │
│   🎮 俄罗斯方块    │
│                    │
│   简单,却不简单   │
│                    │
│   这就是编程的     │
│   魅力所在!✨    │
│                    │
└────────────────────┘

希望这篇教程能帮到你!如果你有什么问题,欢迎留言讨论!记得点赞收藏哦!(๑•̀ㅂ•́)و✧

祝你编程愉快!Happy Coding! 🎉

┌───────────────────────────┐
│                           │
│   感谢阅读!🙏            │
│                           │
│   如果觉得有帮助,        │
│   记得点赞+收藏+转发!    │
│                           │
│   你的支持是我最大的      │
│   动力!💪               │
│                           │
│   下期再见!👋            │
│                           │
└───────────────────────────┘
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐