俄罗斯方块游戏的 Python 实现

        俄罗斯方块是一款经典的益智游戏,玩家需要通过旋转和移动方块,使其在底部形成完整的行,从而消除该行并获得分数。本文将介绍如何使用 Python 和 Pygame 库实现俄罗斯方块游戏。

游戏代码实现

        以下是一个完整的俄罗斯方块游戏代码实现:

import pygame
import random

# 游戏常量
SCREEN_WIDTH = 800  # 屏幕宽度
SCREEN_HEIGHT = 700  # 屏幕高度
PLAY_WIDTH = 300  # 游戏区域宽度
PLAY_HEIGHT = 600  # 游戏区域高度
BLOCK_SIZE = 30  # 每个方块的大小

TOP_LEFT_X = (SCREEN_WIDTH - PLAY_WIDTH) // 2  # 游戏区域左上角的 X 坐标
TOP_LEFT_Y = SCREEN_HEIGHT - PLAY_HEIGHT - 50  # 游戏区域左上角的 Y 坐标

# 方块形状定义,每个形状用一个二维数组表示,'0' 表示方块的位置
S = [['.....',
      '.....',
      '..00.',
      '.00..',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '...0.',
      '.....']]

Z = [['.....',
      '.....',
      '.00..',
      '..00.',
      '.....'],
     ['.....',
      '..0..',
      '.00..',
      '.0...',
      '.....']]

I = [['..0..',
      '..0..',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '0000.',
      '.....',
      '.....',
      '.....']]

O = [['.....',
      '.....',
      '.00..',
      '.00..',
      '.....']]

J = [['.....',
      '.0...',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..00.',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '...0.',
      '.....'],
     ['.....',
      '..0..',
      '..0..',
      '.00..',
      '.....']]

L = [['.....',
      '...0.',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..0..',
      '..00.',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '.0...',
      '.....'],
     ['.....',
      '.00..',
      '..0..',
      '..0..',
      '.....']]

T = [['.....',
      '..0..',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '..0..',
      '.....'],
     ['.....',
      '..0..',
      '.00..',
      '..0..',
      '.....']]

# 所有方块形状和对应的颜色
SHAPES = [S, Z, I, O, J, L, T]
COLORS = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 165, 0), (0, 0, 255), (128, 0, 128)]

# 定义一个方块类
class Piece:
    def __init__(self, x, y, shape):
        self.x = x  # 方块的水平位置
        self.y = y  # 方块的垂直位置
        self.shape = shape  # 方块的形状
        self.color = COLORS[SHAPES.index(shape)]  # 根据形状获取对应的颜色
        self.rotation = 0  # 方块的旋转状态

# 创建游戏网格,locked_pos 是已固定的方块位置和颜色
def create_grid(locked_pos={}):
    grid = [[(0,0,0) for _ in range(10)] for _ in range(20)]  # 初始化一个 10x20 的黑色网格
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            if (x, y) in locked_pos:  # 如果该位置有固定方块
                grid[y][x] = locked_pos[(x,y)]  # 设置为对应的颜色
    return grid

# 将方块形状转换为实际的游戏坐标
def convert_shape_format(shape):
    positions = []
    format = shape.shape[shape.rotation % len(shape.shape)]  # 获取当前旋转状态下的形状
    
    for i, line in enumerate(format):
        row = list(line)
        for j, column in enumerate(row):
            if column == '0':  # 如果是方块的位置
                positions.append((shape.x + j, shape.y + i))  # 计算实际坐标
    
    for i, pos in enumerate(positions):
        positions[i] = (pos[0] - 2, pos[1] - 4)  # 调整坐标以适配游戏区域
    
    return positions

# 检查方块是否在有效空间内
def valid_space(shape, grid):
    accepted_pos = [[(j, i) for j in range(10) if grid[i][j] == (0,0,0)] for i in range(20)]  # 获取所有空位置
    accepted_pos = [j for sub in accepted_pos for j in sub]  # 展平列表
    
    formatted = convert_shape_format(shape)  # 获取方块的实际坐标
    
    for pos in formatted:
        if pos not in accepted_pos:  # 如果方块的某个位置不在空位置中
            if pos[1] > -1:  # 并且位置在游戏区域内
                return False  # 返回无效
    return True  # 返回有效

# 检查游戏是否失败
def check_lost(positions):
    for pos in positions:  # 遍历所有已固定方块的位置
        x, y = pos
        if y < 1:  # 如果有方块超出游戏区域顶部
            return True  # 游戏失败
    return False

# 获取一个随机方块
def get_shape():
    return Piece(5, 0, random.choice(SHAPES))  # 在中间顶部生成一个随机方块

# 在屏幕中央绘制文字
def draw_text_middle(surface, text, size, color):
    font = pygame.font.SysFont("comicsans", size, bold=True)  # 设置字体
    label = font.render(text, 1, color)  # 渲染文字
    
    # 计算文字位置,使其居中
    surface.blit(label, (TOP_LEFT_X + PLAY_WIDTH/2 - (label.get_width()/2), TOP_LEFT_Y + PLAY_HEIGHT/2 - label.get_height()/2))

# 绘制游戏网格线
def draw_grid(surface, grid):
    sx = TOP_LEFT_X  # 游戏区域的左上角 X 坐标
    sy = TOP_LEFT_Y  # 游戏区域的左上角 Y 坐标
    
    for i in range(len(grid)):
        pygame.draw.line(surface, (128,128,128), (sx, sy + i*BLOCK_SIZE), (sx + PLAY_WIDTH, sy + i*BLOCK_SIZE))  # 绘制水平线
        for j in range(len(grid[i])):
            pygame.draw.line(surface, (128,128,128), (sx + j*BLOCK_SIZE, sy), (sx + j*BLOCK_SIZE, sy + PLAY_HEIGHT))  # 绘制垂直线

# 清除完成的行
def clear_rows(grid, locked):
    inc = 0  # 清除的行数
    for i in range(len(grid)-1, -1, -1):  # 从底部向上遍历
        row = grid[i]
        if (0,0,0) not in row:  # 如果该行没有空位置
            inc += 1  # 增加清除的行数
            ind = i  # 记录当前行号
            for j in range(len(row)):
                try:
                    del locked[(j,i)]  # 删除该行的所有方块
                except:
                    continue
    
    if inc > 0:  # 如果有行被清除
        for key in sorted(list(locked), key=lambda x: x[1])[::-1]:  # 遍历所有固定方块
            x, y = key
            if y < ind:  # 如果方块在被清除行的上方
                newKey = (x, y + inc)  # 更新方块的坐标
                locked[newKey] = locked.pop(key)  # 移动方块
    
    return inc  # 返回清除的行数

# 绘制下一个方块
def draw_next_shape(shape, surface):
    font = pygame.font.SysFont('comicsans', 30)  # 设置字体
    label = font.render('Next Shape', 1, (255,255,255))  # 渲染文字
    
    sx = TOP_LEFT_X + PLAY_WIDTH + 50  # 下一个方块的显示位置 X 坐标
    sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100  # 下一个方块的显示位置 Y 坐标
    format = shape.shape[shape.rotation % len(shape.shape)]  # 获取当前旋转状态下的形状
    
    for i, line in enumerate(format):
        row = list(line)
        for j, column in enumerate(row):
            if column == '0':  # 如果是方块的位置
                pygame.draw.rect(surface, shape.color, (sx + j*BLOCK_SIZE, sy + i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)  # 绘制方块
    
    surface.blit(label, (sx + 10, sy - 30))  # 显示文字

# 绘制游戏窗口
def draw_window(surface, grid, score=0):
    surface.fill((0,0,0))  # 填充背景为黑色
    
    pygame.font.init()  # 初始化字体
    font = pygame.font.SysFont('comicsans', 60)  # 设置字体
    label = font.render('Tetris', 1, (255,255,255))  # 渲染标题文字
    
    surface.blit(label, (TOP_LEFT_X + PLAY_WIDTH / 2 - (label.get_width() / 2), 30))  # 显示标题
    
    # 当前分数
    font = pygame.font.SysFont('comicsans', 30)  # 设置字体
    label = font.render('Score: ' + str(score), 1, (255,255,255))  # 渲染分数文字
    
    sx = TOP_LEFT_X + PLAY_WIDTH + 50  # 分数显示位置 X 坐标
    sy = TOP_LEFT_Y + PLAY_HEIGHT/2 - 100  # 分数显示位置 Y 坐标
    
    surface.blit(label, (sx + 20, sy + 160))  # 显示分数
    
    # 绘制游戏区域
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            pygame.draw.rect(surface, grid[y][x], (TOP_LEFT_X + x*BLOCK_SIZE, TOP_LEFT_Y + y*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)  # 绘制方块
    
    pygame.draw.rect(surface, (255,0,0), (TOP_LEFT_X, TOP_LEFT_Y, PLAY_WIDTH, PLAY_HEIGHT), 5)  # 绘制游戏区域边框
    
    draw_grid(surface, grid)  # 绘制网格线

# 主游戏函数
def main():
    locked_positions = {}  # 已固定方块的位置和颜色
    grid = create_grid(locked_positions)  # 创建游戏网格
    
    change_piece = False  # 是否需要切换方块
    run = True  # 游戏运行状态
    current_piece = get_shape()  # 当前方块
    next_piece = get_shape()  # 下一个方块
    clock = pygame.time.Clock()  # 创建时钟对象
    fall_time = 0  # 方块下落时间
    fall_speed = 0.27  # 方块下落速度
    level_time = 0  # 级别时间
    score = 0  # 分数
    
    pygame.init()  # 初始化 Pygame
    win = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))  # 创建游戏窗口
    pygame.display.set_caption('Tetris')  # 设置窗口标题
    
    while run:
        grid = create_grid(locked_positions)  # 更新游戏网格
        fall_time += clock.get_rawtime()  # 获取经过的时间
        level_time += clock.get_rawtime()  # 更新级别时间
        clock.tick()  # 更新时钟
        
        # 每隔 5 秒加快下落速度
        if level_time/1000 > 5:
            level_time = 0  # 重置级别时间
            if fall_speed > 0.12:  # 如果速度大于最小速度
                fall_speed -= 0.005  # 减少速度
        
        # 方块下落逻辑
        if fall_time/1000 > fall_speed:
            fall_time = 0  # 重置下落时间
            current_piece.y += 1  # 方块下移
            if not(valid_space(current_piece, grid)) and current_piece.y > 0:  # 如果下移后位置无效
                current_piece.y -= 1  # 恢复位置
                change_piece = True  # 标记需要切换方块
        
        # 处理用户输入
        for event in pygame.event.get():
            if event.type == pygame.QUIT:  # 如果用户关闭窗口
                run = False  # 退出游戏
            
            if event.type == pygame.KEYDOWN:  # 如果用户按下按键
                if event.key == pygame.K_LEFT:  # 如果是左箭头
                    current_piece.x -= 1  # 方块左移
                    if not(valid_space(current_piece, grid)):  # 如果位置无效
                        current_piece.x += 1  # 恢复位置
                if event.key == pygame.K_RIGHT:  # 如果是右箭头
                    current_piece.x += 1  # 方块右移
                    if not(valid_space(current_piece, grid)):  # 如果位置无效
                        current_piece.x -= 1  # 恢复位置
                if event.key == pygame.K_DOWN:  # 如果是下箭头
                    current_piece.y += 1  # 方块下移
                    if not(valid_space(current_piece, grid)):  # 如果位置无效
                        current_piece.y -= 1  # 恢复位置
                if event.key == pygame.K_UP:  # 如果是上箭头
                    current_piece.rotation += 1  # 方块旋转
                    if not(valid_space(current_piece, grid)):  # 如果旋转后位置无效
                        current_piece.rotation -= 1  # 恢复旋转状态
        
        # 将当前方块绘制到网格中
        shape_pos = convert_shape_format(current_piece)  # 获取方块的实际坐标
        
        for i in range(len(shape_pos)):
            x, y = shape_pos[i]
            if y > -1:  # 如果坐标在游戏区域内
                grid[y][x] = current_piece.color  # 设置对应的颜色
        
        # 切换方块逻辑
        if change_piece:
            for pos in shape_pos:
                p = (pos[0], pos[1])
                locked_positions[p] = current_piece.color  # 将方块固定到网格中
            current_piece = next_piece  # 当前方块变为下一个方块
            next_piece = get_shape()  # 获取新的下一个方块
            change_piece = False  # 重置切换标志
            score += clear_rows(grid, locked_positions) * 10  # 清除完成的行并更新分数
        
        # 绘制游戏窗口和下一个方块
        draw_window(win, grid, score)
        draw_next_shape(next_piece, win)
        pygame.display.update()  # 更新屏幕显示
        
        # 检查游戏是否失败
        if check_lost(locked_positions):
            draw_text_middle(win, "GAME OVER!", 80, (255,255,255))  # 显示游戏结束文字
            pygame.display.update()  # 更新屏幕显示
            pygame.time.delay(1500)  # 延迟 1.5 秒
            run = False  # 退出游戏

    pygame.display.quit()  # 退出 Pygame 显示模块

# 如果直接运行该脚本,则启动游戏
if __name__ == '__main__':
    main()

代码说明

游戏常量

  • SCREEN_WIDTHSCREEN_HEIGHT:屏幕的宽度和高度。

  • PLAY_WIDTHPLAY_HEIGHT:游戏区域的宽度和高度。

  • BLOCK_SIZE:每个方块的大小。

  • TOP_LEFT_XTOP_LEFT_Y:游戏区域左上角的位置。

方块形状

  • 定义了多种方块形状,每个形状用一个二维数组表示,'0' 表示方块的位置。

Piece 类

  • 表示一个方块,包含方块的位置、形状、颜色和旋转状态。

游戏函数

  • create_grid:创建游戏网格。

  • convert_shape_format:将方块形状转换为实际的游戏坐标。

  • valid_space:检查方块是否在有效空间内。

  • check_lost:检查游戏是否失败。

  • get_shape:获取一个随机方块。

  • draw_text_middle:在屏幕中央绘制文字。

  • draw_grid:绘制游戏网格线。

  • clear_rows:清除完成的行。

  • draw_next_shape:绘制下一个方块。

  • draw_window:绘制游戏窗口。

主游戏循环

  • 初始化游戏窗口和相关变量。

  • 处理用户输入和方块的移动、旋转等操作。

  • 检查游戏是否失败,并更新游戏状态。

运行游戏

要运行该游戏,需要确保已安装 Pygame 库。可以通过以下命令安装:

bash复制

pip install pygame

然后运行代码即可启动游戏。

总结

通过上述代码实现,我们成功地创建了一个简单的俄罗斯方块游戏。玩家可以通过键盘控制方块的移动和旋转,完成行的消除并获得分数。该游戏代码结构清晰,易于理解和扩展,是学习 Pygame 和游戏开发的一个很好的示例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sky88601

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值