1. 导入库与初始化

import pygame
import random
import sys

# 初始化Pygame
pygame.init()

这段代码首先导入了必要的库:pygame用于游戏开发,random用于随机生成方块,sys用于系统操作。然后通过pygame.init()初始化Pygame库,启动其所有模块,为后续后续游戏开发做好准备。

2. 游戏配置参数

# 配置参数
CELL_SIZE = 30  # 每个网格的像素大小
COLS, ROWS = 10, 20  # 游戏区域的列数和行数
SIDEBAR_WIDTH = 150  # 侧边栏宽度
WINDOW_WIDTH, WINDOW_HEIGHT = CELL_SIZE * COLS + SIDEBAR_WIDTH, CELL_SIZE * ROWS
FPS = 30

这里定义了游戏的核心配置参数:

  • CELL_SIZE:每个网格的像素大小,决定了方块的视觉尺寸
  • COLS, ROWS:游戏区域的列数和行数,采用标准俄罗斯方块的10×20布局
  • SIDEBAR_WIDTH:右侧边栏宽度,用于显示分数和游戏信息
  • WINDOW_WIDTH, WINDOW_HEIGHT:计算窗口总尺寸(游戏区+侧边栏)
  • FPS:游戏帧率,控制画面刷新速度

3. 颜色定义

# 定义颜色
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)
CYAN = (0, 255, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
ORANGE = (255, 165, 0)
WHITE = (255, 255, 255)
SIDEBAR_COLOR = (50, 50, 50)
HIGHLIGHT_COLOR = (100, 100, 255)

定义了游戏中使用的所有颜色,采用RGB值表示:

  • 基础色:黑、白、灰用于网格线
  • 方块色:为7种不同形状的方块块分配了独特颜色(青色、黄色、洋红色等)
  • 界面色:侧边栏背景色和高亮色,用于区分不同UI区域

4. 方块形状定义

SHAPES_COLORS = [
    ([[1, 1, 1, 1]], CYAN),  # I形
    ([[1, 1], [1, 1]], YELLOW),  # O形
    ([[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形
]

这是俄罗斯方块的核心定义,每个元素是一个元组,包含:

  • 形状矩阵:用二维列表表示,1表示方块实体,0表示空白
  • 对应颜色:每个形状状有固定颜色,符合传统俄罗斯方块的配色方案

共定义了7种经典俄罗斯方块形状:I形、O形、T形、S形、Z形、J形和L形。

5. 创建游戏窗口

# 创建窗口
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("俄罗斯方块 - Pygame")
clock = pygame.time.Clock()
  • pygame.display.set_mode()创建游戏窗口,尺寸使用之前计算的WINDOW_WIDTHWINDOW_HEIGHT
  • pygame.display.set_caption()设置窗口标题
  • clock = pygame.time.Clock()创建时钟对象,用于控制游戏帧率

6. 字体初始化

# 初始化中文字体
try:
    # 尝试使用系统中文字体
    font_title = pygame.font.SysFont("simhei", 28)    # 标题字体
    font_large = pygame.font.SysFont("simhei", 24)    # 大字体
    font_medium = pygame.font.SysFont("simhei", 20)   # 中等字体
    font_small = pygame.font.SysFont("simhei", 18)    # 小字体
except:
    # 如果找不到中文字体,使用默认字体
    font_title = pygame.font.Font(None, 28)
    font_large = pygame.font.Font(None, 24)
    font_medium = pygame.font.Font(None, 20)
    font_small = pygame.font.Font(None, 18)

这段代码处理中文显示问题:

  • 尝试加载系统中的"simhei"(黑体)字体,创建不同大小的字体对象
  • 如果加载失败(如系统中没有该字体),则使用Pygame默认字体
  • 定义了四种不同大小的字体,分别用于标题、分数、操作说明等不同UI元素

7. 游戏网格初始化

# 游戏网格初始化
grid = [[0] * COLS for _ in range(ROWS)]

创建一个ROWS×COLS的二维列表作为游戏主网格:

  • 0表示该位置为空
  • 后续会用颜色值填充,表示该位置被方块占据
  • 这个网格记录了所有已落下并固定的方块位置

8. 绘制网格函数

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)
                pygame.draw.rect(surface, GRAY, rect, width=1)
            else:
                pygame.draw.rect(surface, val, rect)

draw_grid()函数负责绘制游戏区域的网格:

  • 遍历网格中的每个单元格
  • 计算每个单元格在屏幕上的位置(c * CELL_SIZE, r * CELL_SIZE
  • 如果单元格为空(值为0),绘制黑色背景和灰色边框
  • 如果单元格被方块占据(值为颜色),则用对应颜色填充

9. 绘制当前方块函数

def draw_piece(surface, piece):
    """绘制当前下落的方块"""
    shape, color = piece.shape, piece.color
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = (piece.col + c) * CELL_SIZE
                y = (piece.row + r) * CELL_SIZE
                rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
                pygame.draw.rect(surface, color, rect)
                pygame.draw.rect(surface, GRAY, rect, width=1)

draw_piece()函数绘制正在下落的方块:

  • 接收方块对象,获取其形状、颜色和位置信息
  • 遍历方块形状矩阵,只绘制值为1的部分(实际方块)
  • 计算每个方块单元在屏幕上的位置(基于方块的基准位置(piece.row, piece.col)
  • 绘制彩色方块并添加灰色边框,增强视觉效果

10. 绘制侧边栏函数

def draw_sidebar(surface, score, next_piece):
    """绘制侧边栏"""
    # 绘制侧边栏背景
    sidebar_rect = pygame.Rect(CELL_SIZE * COLS, 0, SIDEBAR_WIDTH, WINDOW_HEIGHT)
    pygame.draw.rect(surface, SIDEBAR_COLOR, sidebar_rect)

    # 绘制分隔线
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS, 0),
                    (CELL_SIZE * COLS, WINDOW_HEIGHT), 2)

    # 绘制标题
    title_text = font_title.render("俄罗斯方块", True, HIGHLIGHT_COLOR)
    title_rect = title_text.get_rect(center=(CELL_SIZE * COLS + SIDEBAR_WIDTH // 2, 25))
    surface.blit(title_text, title_rect)

    # 绘制分数区域(省略部分代码)
    # 绘制下一个方块区域(省略部分代码)
    # 绘制操作说明区域(省略部分代码)
    # 绘制游戏提示(省略部分代码)

draw_sidebar()函数绘制右侧信息面板:

  • 首先绘制侧边栏背景和与游戏区的分隔线
  • 包含多个功能区域:标题、分数显示、下一个方块预览、操作说明和游戏提示
  • 使用不同大小的字体和布局,使信息层次清晰
  • 通过surface.blit()方法将文本和图形绘制到屏幕上

11. 绘制下一个方块预览

def draw_next_piece(surface, piece, start_y):
    """绘制下一个方块预览"""
    shape, color = piece.shape, piece.color
    # 计算预览区域中心位置
    preview_x = CELL_SIZE * COLS + SIDEBAR_WIDTH // 2 - len(shape[0]) * CELL_SIZE // 2

    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = preview_x + c * CELL_SIZE
                y = start_y + r * CELL_SIZE
                rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
                pygame.draw.rect(surface, color, rect)
                pygame.draw.rect(surface, GRAY, rect, width=1)

这个函数专门用于在侧边栏绘制下一个要出现的方块预览:

  • 计算预览区域的中心位置,使方块居中显示
  • 遍历方块形状矩阵,绘制每个实体部分
  • 与主游戏区的方块绘制逻辑类似,但位置固定在侧边栏内

12. 碰撞检测函数

def check_collision(piece, test_shape=None):
    """检测方块是否与边界或已有方块发生碰撞"""
    shape = test_shape if test_shape is not None else piece.shape
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = piece.col + c
                y = piece.row + r
                if x < 0 or x >= COLS or y >= ROWS:
                    return True
                if y >= 0 and grid[y][x]:
                    return True
    return False

碰撞检测是俄罗斯方块的核心逻辑之一:

  • 检查方块是否与游戏边界(左、右、下)碰撞
  • 检查方块是否与已落在网格中的方块碰撞
  • test_shape参数用于旋转检测(先测试旋转后的形状是否会碰撞)
  • 遍历方块的每个实体部分,只要有一个部分碰撞就返回True

13. 方块合并到网格函数

def merge_piece_to_grid(piece):
    """将当前方块合并到游戏网格中"""
    shape, color = piece.shape, piece.color
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = piece.col + c
                y = piece.row + r
                if 0 <= y < ROWS and 0 <= x < COLS:
                    grid[y][x] = color

当方块无法继续下落时(已落地),需要将其合并到主网格中:

  • 遍历方块的每个实体部分
  • 将方块的颜色值写入到网格的对应位置
  • 后续绘制网格时会显示这些固定的方块

14. 方块旋转函数

def rotate_matrix(matrix):
    """旋转矩阵90度(顺时针)"""
    # 先转置矩阵,然后水平翻转
    return [list(row) for row in zip(*matrix[::-1])]

这个函数实现矩阵的90度顺时针旋转,用于方块旋转:

  • matrix[::-1]将矩阵上下翻转
  • zip(*matrix)实现矩阵转置
  • 最终将结果转换为列表,得到旋转后的形状矩阵

15. 消除满行函数

def clear_lines():
    """清除满行并返回清除的行数"""
    lines_cleared = 0
    # 从底部开始检查每一行
    for r in range(ROWS - 1, -1, -1):
        # 检查当前行是否已满
        if all(grid[r][c] != 0 for c in range(COLS)):
            # 删除这一行
            del grid[r]
            # 在顶部添加一个空行
            grid.insert(0, [0] * COLS)
            lines_cleared += 1
            # 因为删除了一行,所以需要重新检查当前行
            r += 1
    return lines_cleared

消除满行是俄罗斯方块的核心玩法:

  • 从底部向上检查每一行是否被填满(all(grid[r][c] != 0)
  • 若某行已满,删除该行并在顶部添加一个空行
  • 每消除一行,计数器加1,并调整循环索引(因为删除行后行数变化)
  • 返回消除的总行数,用于计算分数

16. 分数计算函数

def calculate_score(lines_cleared):
    """根据消除行数计算分数"""
    if lines_cleared == 1:
        return 100
    elif lines_cleared == 2:
        return 300
    elif lines_cleared == 3:
        return 500
    elif lines_cleared == 4:
        return 800
    return 0

根据消除的行数计算得分,采用经典的非线性计分方式:

  • 消除1行:100分
  • 消除2行:300分(多于1行的2倍)
  • 消除3行:500分
  • 消除4行(Tetris):800分
    这种计分方式鼓励玩家一次消除更多行。

17. 速度计算函数

def calculate_speed(score):
    """根据分数计算下落速度"""
    # 基础速度为500毫秒,每1000分减少50毫秒,最低100毫秒
    speed = max(500 - (score // 1000) * 50, 100)
    return speed

随着分数增加,方块下落速度会加快,增加游戏难度:

  • 基础速度为500毫秒(每500ms下落一格)
  • 每得1000分,速度增加50ms(下落更快)
  • 最低速度限制为100ms,防止速度过快无法操作

18. 硬降函数

def hard_drop(piece):
    """硬降:方块直接落到最底部"""
    while piece.move(dr=1):
        pass

实现"硬降"功能,让方块直接落到当前位置的最底部:

  • 通过循环不断调用方块的下移方法
  • 直到下移失败(碰撞),此时方块已到达底部
  • 这是高级玩家常用的技巧,可以快速放置方块并获得额外分数

19. 方块类定义

class Tetromino:
    """表示当前下落的方块"""
    def __init__(self):
        self.shape_data = random.choice(SHAPES_COLORS)
        self.shape = self.shape_data[0]
        self.color = self.shape_data[1]
        self.row = -len(self.shape) + 1
        self.col = COLS // 2 - len(self.shape[0]) // 2

    def move(self, dr=1, dc=0):
        """移动方块"""
        self.row += dr
        self.col += dc
        if check_collision(self):
            self.row -= dr
            self.col -= dc
            return False
        return True

    def rotate(self):
        """旋转方块"""
        # O形方块不需要旋转
        if self.color == YELLOW:
            return True

        original_shape = self.shape
        self.shape = rotate_matrix(self.shape)

        # 如果旋转后发生碰撞,则恢复原状
        if check_collision(self):
            self.shape = original_shape
            return False
        return True

Tetromino类封装了方块的所有属性和行为:

  • __init__:随机选择一种方块形状,初始化位置(顶部居中)
  • move():移动方块(dr为垂直方向,dc为水平方向),结合碰撞检测确保移动合法
  • rotate():旋转方块(O形除外),如果旋转后碰撞则恢复原状(旋转无效)

20. 主游戏函数

def main():
    running = True
    current_piece = Tetromino()
    next_piece = Tetromino()  # 下一个方块
    fall_time = 0
    score = 0
    fall_speed = calculate_speed(score)  # 根据分数计算初始速度
    fast_drop = False  # 快速下落标志

    try:
        while running:
            delta_time = clock.tick(FPS)
            fall_time += delta_time

            # 事件处理(省略部分代码)
            
            # 自动下落逻辑(省略部分代码)
            
            # 绘制游戏元素
            screen.fill(BLACK)
            draw_grid(screen)
            draw_piece(screen, current_piece)
            draw_sidebar(screen, score, next_piece)
            pygame.display.flip()

    except Exception as e:
        print(f"游戏发生错误: {e}")
        pygame.quit()
        sys.exit()

main()函数是游戏的主循环,控制整个游戏流程:

  • 初始化游戏状态:当前方块、下一个方块、分数、下落速度等
  • 事件处理:监听键盘和鼠标事件,处理用户输入
  • 自动下落:根据时间和速度参数,控制方块自动下落
  • 碰撞处理:当方块落地后,合并到网格并检查是否有满行
  • 状态更新:消除满行后更新分数和下落速度
  • 绘制刷新:每帧重新绘制所有游戏元素并刷新屏幕

21. 程序入口

if __name__ == "__main__":
    main()

标准的Python程序入口:当脚本直接运行时,调用main()函数启动游戏;如果作为模块导入,则不自动运行。

通过以上模块的协同工作,实现了一个功能完整的俄罗斯方块游戏,包含方块移动、旋转、消除、计分等核心玩法,以及友好的用户界面。

运行结果

在这里插入图片描述

完整代码

import pygame
import random
import sys

# 初始化Pygame
pygame.init()

# 配置参数
CELL_SIZE = 30  # 每个网格的像素大小
COLS, ROWS = 10, 20  # 游戏区域的列数和行数
SIDEBAR_WIDTH = 150  # 侧边栏宽度
WINDOW_WIDTH, WINDOW_HEIGHT = CELL_SIZE * COLS + SIDEBAR_WIDTH, CELL_SIZE * ROWS
FPS = 30

# 定义颜色
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)
CYAN = (0, 255, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
ORANGE = (255, 165, 0)
WHITE = (255, 255, 255)
SIDEBAR_COLOR = (50, 50, 50)
HIGHLIGHT_COLOR = (100, 100, 255)

SHAPES_COLORS = [
    ([[1, 1, 1, 1]], CYAN),  # I形
    ([[1, 1], [1, 1]], YELLOW),  # O形
    ([[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()

# 初始化中文字体
try:
    # 尝试使用系统中文字体
    font_title = pygame.font.SysFont("simhei", 28)    # 标题字体
    font_large = pygame.font.SysFont("simhei", 24)    # 大字体
    font_medium = pygame.font.SysFont("simhei", 20)   # 中等字体
    font_small = pygame.font.SysFont("simhei", 18)    # 小字体
except:
    # 如果找不到中文字体,使用默认字体
    font_title = pygame.font.Font(None, 28)
    font_large = pygame.font.Font(None, 24)
    font_medium = pygame.font.Font(None, 20)
    font_small = pygame.font.Font(None, 18)

# 游戏网格初始化
grid = [[0] * COLS for _ in range(ROWS)]

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)
                pygame.draw.rect(surface, GRAY, rect, width=1)
            else:
                pygame.draw.rect(surface, val, rect)

def draw_piece(surface, piece):
    """绘制当前下落的方块"""
    shape, color = piece.shape, piece.color
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = (piece.col + c) * CELL_SIZE
                y = (piece.row + r) * CELL_SIZE
                rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
                pygame.draw.rect(surface, color, rect)
                pygame.draw.rect(surface, GRAY, rect, width=1)

def draw_sidebar(surface, score, next_piece):
    """绘制侧边栏"""
    # 绘制侧边栏背景
    sidebar_rect = pygame.Rect(CELL_SIZE * COLS, 0, SIDEBAR_WIDTH, WINDOW_HEIGHT)
    pygame.draw.rect(surface, SIDEBAR_COLOR, sidebar_rect)

    # 绘制分隔线
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS, 0),
                    (CELL_SIZE * COLS, WINDOW_HEIGHT), 2)

    # 绘制标题
    title_text = font_title.render("俄罗斯方块", True, HIGHLIGHT_COLOR)
    title_rect = title_text.get_rect(center=(CELL_SIZE * COLS + SIDEBAR_WIDTH // 2, 25))
    surface.blit(title_text, title_rect)

    # 绘制分数区域
    score_y = 60
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS + 10, score_y - 10),
                    (WINDOW_WIDTH - 10, score_y - 10), 1)

    score_label = font_large.render("当前分数", True, WHITE)
    surface.blit(score_label, (CELL_SIZE * COLS + 20, score_y))

    score_value = font_large.render(str(score), True, HIGHLIGHT_COLOR)
    surface.blit(score_value, (CELL_SIZE * COLS + 30, score_y + 30))

    # 绘制下一个方块区域
    next_y = 130
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS + 10, next_y - 10),
                    (WINDOW_WIDTH - 10, next_y - 10), 1)

    next_label = font_large.render("下一个方块", True, WHITE)
    surface.blit(next_label, (CELL_SIZE * COLS + 20, next_y))

    # 绘制下一个方块预览
    if next_piece:
        draw_next_piece(surface, next_piece, next_y + 40)

    # 绘制操作说明区域
    controls_y = 250
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS + 10, controls_y - 10),
                    (WINDOW_WIDTH - 10, controls_y - 10), 1)

    controls_label = font_large.render("操作说明", True, WHITE)
    surface.blit(controls_label, (CELL_SIZE * COLS + 20, controls_y))

    controls = [
        "← → : 左右移动",
        "↑   : 旋转方块",
        "↓   : 软降",
        "空格 : 硬降"
    ]

    for i, text in enumerate(controls):
        ctrl_text = font_medium.render(text, True, WHITE)
        surface.blit(ctrl_text, (CELL_SIZE * COLS + 20, controls_y + 35 + i * 25))

    # 绘制游戏提示
    tip_y = controls_y + 35 + len(controls) * 25 + 20
    pygame.draw.line(surface, GRAY,
                    (CELL_SIZE * COLS + 10, tip_y - 10),
                    (WINDOW_WIDTH - 10, tip_y - 10), 1)

    tip_label = font_large.render("游戏提示", True, WHITE)
    surface.blit(tip_label, (CELL_SIZE * COLS + 20, tip_y))

    tips = [
        "填满一行消除",
        "消除越多得分越高"
    ]

    for i, text in enumerate(tips):
        tip_text = font_small.render(text, True, WHITE)
        surface.blit(tip_text, (CELL_SIZE * COLS + 20, tip_y + 35 + i * 22))

def draw_next_piece(surface, piece, start_y):
    """绘制下一个方块预览"""
    shape, color = piece.shape, piece.color
    # 计算预览区域中心位置
    preview_x = CELL_SIZE * COLS + SIDEBAR_WIDTH // 2 - len(shape[0]) * CELL_SIZE // 2

    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = preview_x + c * CELL_SIZE
                y = start_y + r * CELL_SIZE
                rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
                pygame.draw.rect(surface, color, rect)
                pygame.draw.rect(surface, GRAY, rect, width=1)

def check_collision(piece, test_shape=None):
    """检测方块是否与边界或已有方块发生碰撞"""
    shape = test_shape if test_shape is not None else piece.shape
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = piece.col + c
                y = piece.row + r
                if x < 0 or x >= COLS or y >= ROWS:
                    return True
                if y >= 0 and grid[y][x]:
                    return True
    return False

def merge_piece_to_grid(piece):
    """将当前方块合并到游戏网格中"""
    shape, color = piece.shape, piece.color
    for r, row in enumerate(shape):
        for c, cell in enumerate(row):
            if cell:
                x = piece.col + c
                y = piece.row + r
                if 0 <= y < ROWS and 0 <= x < COLS:
                    grid[y][x] = color

def rotate_matrix(matrix):
    """旋转矩阵90度(顺时针)"""
    # 先转置矩阵,然后水平翻转
    return [list(row) for row in zip(*matrix[::-1])]

def clear_lines():
    """清除满行并返回清除的行数"""
    lines_cleared = 0
    # 从底部开始检查每一行
    for r in range(ROWS - 1, -1, -1):
        # 检查当前行是否已满
        if all(grid[r][c] != 0 for c in range(COLS)):
            # 删除这一行
            del grid[r]
            # 在顶部添加一个空行
            grid.insert(0, [0] * COLS)
            lines_cleared += 1
            # 因为删除了一行,所以需要重新检查当前行(原来的下一行)
            r += 1
    return lines_cleared

def calculate_score(lines_cleared):
    """根据消除行数计算分数"""
    if lines_cleared == 1:
        return 100
    elif lines_cleared == 2:
        return 300
    elif lines_cleared == 3:
        return 500
    elif lines_cleared == 4:
        return 800
    return 0

def calculate_speed(score):
    """根据分数计算下落速度"""
    # 基础速度为500毫秒,每1000分减少50毫秒,最低100毫秒
    speed = max(500 - (score // 1000) * 50, 100)
    return speed

def hard_drop(piece):
    """硬降:方块直接落到最底部"""
    while piece.move(dr=1):
        pass

class Tetromino:
    """表示当前下落的方块"""
    def __init__(self):
        self.shape_data = random.choice(SHAPES_COLORS)
        self.shape = self.shape_data[0]
        self.color = self.shape_data[1]
        self.row = -len(self.shape) + 1
        self.col = COLS // 2 - len(self.shape[0]) // 2

    def move(self, dr=1, dc=0):
        """移动方块"""
        self.row += dr
        self.col += dc
        if check_collision(self):
            self.row -= dr
            self.col -= dc
            return False
        return True

    def rotate(self):
        """旋转方块"""
        # O形方块不需要旋转
        if self.color == YELLOW:
            return True

        original_shape = self.shape
        self.shape = rotate_matrix(self.shape)

        # 如果旋转后发生碰撞,则恢复原状
        if check_collision(self):
            self.shape = original_shape
            return False
        return True

def main():
    running = True
    current_piece = Tetromino()
    next_piece = Tetromino()  # 下一个方块
    fall_time = 0
    score = 0
    fall_speed = calculate_speed(score)  # 根据分数计算初始速度
    fast_drop = False  # 快速下落标志

    try:
        while running:
            delta_time = clock.tick(FPS)
            fall_time += delta_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:
                        current_piece.move(dc=-1)
                    elif event.key == pygame.K_RIGHT:
                        current_piece.move(dc=1)
                    elif event.key == pygame.K_DOWN:
                        # 快速下落
                        fast_drop = True
                    elif event.key == pygame.K_UP:
                        # 旋转方块
                        current_piece.rotate()
                    elif event.key == pygame.K_SPACE:
                        # 硬降
                        hard_drop(current_piece)
                elif event.type == pygame.KEYUP:
                    if event.key == pygame.K_DOWN:
                        # 取消快速下落
                        fast_drop = False

            # 根据是否快速下落调整下落速度
            current_fall_speed = fall_speed // 10 if fast_drop else fall_speed

            # 自动下落
            if fall_time > current_fall_speed:
                if not current_piece.move(dr=1):
                    merge_piece_to_grid(current_piece)
                    # 清除满行
                    lines_cleared = clear_lines()
                    if lines_cleared > 0:
                        # 计算并添加分数
                        score_increase = calculate_score(lines_cleared)
                        score += score_increase
                        print(f"消除了 {lines_cleared} 行! 获得 {score_increase} 分,总分: {score}")
                        # 根据新分数重新计算速度
                        fall_speed = calculate_speed(score)

                    # 更新当前方块和下一个方块
                    current_piece = next_piece
                    next_piece = Tetromino()

                    if check_collision(current_piece):
                        running = False  # 游戏结束
                fall_time = 0

            screen.fill(BLACK)
            draw_grid(screen)
            draw_piece(screen, current_piece)
            draw_sidebar(screen, score, next_piece)
            pygame.display.flip()

    except Exception as e:
        print(f"游戏发生错误: {e}")
        pygame.quit()
        sys.exit()

if __name__ == "__main__":
    main()

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐