俄罗斯方块是一款经典的益智游戏,玩家需要通过旋转和移动方块,使其在底部形成完整的行,从而消除该行并获得分数。本文将介绍如何使用 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_WIDTH
和SCREEN_HEIGHT
:屏幕的宽度和高度。 -
PLAY_WIDTH
和PLAY_HEIGHT
:游戏区域的宽度和高度。 -
BLOCK_SIZE
:每个方块的大小。 -
TOP_LEFT_X
和TOP_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 和游戏开发的一个很好的示例。