[Python] 纯文本查看 复制代码
import pygame
import random
import math
import os
import sys
from typing import List, Tuple, Optional, Dict, Any
from pygame import gfxdraw
import json
# 初始化 Pygame
pygame.init()
pygame.mixer.init()
# 获取当前文件所在目录
folder_name = os.path.dirname(__file__)
# 加载背景音乐
bg_music = os.path.join(folder_name, '丛林.ogg')
try:
pygame.mixer.music.load(bg_music)
pygame.mixer.music.play(-1)
pygame.mixer.music.set_volume(0.3) # 降低背景音乐音量
except Exception as e:
print(f"加载背景音乐失败: {e}")
# 屏幕尺寸
SCREEN_WIDTH = 1100
SCREEN_HEIGHT = 900
GRID_SIZE = 35
GRID_WIDTH = 10
GRID_HEIGHT = 20
SIDEBAR_WIDTH = 300
HEADER_HEIGHT = 100
# 色彩方案 - 霓虹未来风
COLORS = {
'bg_dark': (10, 10, 20),
'bg_grid': (20, 20, 35),
'game_area_line': (230, 230, 230, 100),
'grid_line': (40, 40, 60, 100),
'text_primary': (240, 240, 255),
'text_secondary': (180, 180, 220),
'text_accent': (100, 220, 255),
'panel_bg': (25, 25, 45, 200),
'button_bg': (50, 50, 80),
'button_hover': (70, 70, 110),
'shadow': (0, 0, 0, 150),
'glow': (100, 200, 255, 50)
}
# 方块颜色 (霓虹色)
BLOCK_COLORS = [
(0, 240, 240), # I - 青色
(30, 144, 255), # J - 亮蓝
(255, 165, 0), # L - 橙色
(255, 255, 0), # O - 黄色
(50, 205, 50), # S - 绿色
(186, 85, 211), # T - 紫色
(255, 69, 0) # Z - 红色
]
# 方块发光颜色
BLOCK_GLOW = [
(100, 255, 255, 100), # I
(100, 180, 255, 100), # J
(255, 200, 100, 100), # L
(255, 255, 100, 100), # O
(100, 255, 100, 100), # S
(200, 100, 255, 100), # T
(255, 100, 100, 100) # Z
]
# 方块形状定义
SHAPES = [
[[1, 1, 1, 1]], # I
[[1, 0, 0], [1, 1, 1]], # J
[[0, 0, 1], [1, 1, 1]], # L
[[1, 1], [1, 1]], # O
[[0, 1, 1], [1, 1, 0]], # S
[[0, 1, 0], [1, 1, 1]], # T
[[1, 1, 0], [0, 1, 1]] # Z
]
# 方块初始位置
SHAPE_START_POS = [3, 0]
# 字体
def load_font(size, bold=False):
"""加载字体,支持多种回退方案"""
fonts = [
"Microsoft YaHei UI", "simhei", "simsun", "Segoe UI", "Arial", "Helvetica",
"DroidSansFallback", "sans-serif"
]
for font_name in fonts:
try:
if bold:
return pygame.font.SysFont(font_name, size, bold=True)
else:
return pygame.font.SysFont(font_name, size)
except:
continue
return pygame.font.Font(None, size)
# 粒子系统
class Particle:
"""粒子特效"""
def __init__(self, x, y, color, particle_type="sparkle"):
self.x = x
self.y = y
self.color = color
self.type = particle_type
self.life = 1.0
if particle_type == "sparkle":
self.size = random.randint(2, 6)
self.speed_x = random.uniform(-2, 2)
self.speed_y = random.uniform(-3, -1)
self.decay = random.uniform(0.02, 0.05)
self.gravity = 0.2
elif particle_type == "trail":
self.size = random.randint(1, 3)
self.speed_x = random.uniform(-0.5, 0.5)
self.speed_y = random.uniform(-0.2, 0.2)
self.decay = random.uniform(0.01, 0.03)
self.gravity = 0.05
def update(self):
self.x += self.speed_x
self.y += self.speed_y
self.speed_y += self.gravity
self.life -= self.decay
return self.life > 0
def draw(self, surface):
alpha = int(self.life * 255)
if self.type == "sparkle":
# 绘制发光粒子
s = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA)
gfxdraw.filled_circle(s, self.size, self.size, self.size,
(*self.color[:3], alpha))
gfxdraw.aacircle(s, self.size, self.size, self.size,
(*self.color[:3], alpha))
surface.blit(s, (int(self.x) - self.size, int(self.y) - self.size))
else:
pygame.draw.circle(surface, (*self.color[:3], alpha),
(int(self.x), int(self.y)), self.size)
# 方块类
class Tetromino:
def __init__(self, x, y):
self.x = x
self.y = y
self.shape_idx = random.randint(0, len(SHAPES) - 1)
self.color_idx = self.shape_idx
self.rotation = 0
self.last_move_time = pygame.time.get_ticks()
self.move_delay = 600
self.drop_time = pygame.time.get_ticks()
self.last_rotate_time = 0
self.ghost_y = 0
# 旋转状态
self.rotate_state = 0
self.rotate_progress = 0
self.rotate_target = 0
# 移动效果
self.move_offset = 0
self.wobble = 0
@property
def shape(self):
shape = SHAPES[self.shape_idx]
rotated = shape
for _ in range(self.rotation % 4):
rotated = list(zip(*reversed(rotated)))
return rotated
def calculate_ghost(self, grid):
"""计算阴影位置"""
ghost = Tetromino(self.x, self.y)
ghost.shape_idx = self.shape_idx
ghost.rotation = self.rotation
ghost.color_idx = self.color_idx
while not ghost.collision(grid):
ghost.y += 1
ghost.y -= 1
return ghost.y
def rotate(self, grid):
"""旋转方块"""
old_rotation = self.rotation
self.rotation = (self.rotation + 1) % 4
# 尝试墙壁踢
if self.collision(grid):
# 向右移动尝试
self.x += 1
if self.collision(grid):
self.x -= 2
if self.collision(grid):
self.x += 1
# 向左移动尝试
if self.x > 0:
self.x -= 1
if self.collision(grid):
self.x += 1
self.rotation = old_rotation
return False
self.last_rotate_time = pygame.time.get_ticks()
return True
def move(self, dx, dy, grid):
"""移动方块"""
self.x += dx
self.y += dy
if self.collision(grid):
self.x -= dx
self.y -= dy
return False
# 移动效果
if dx != 0:
self.move_offset = dx * 3
return True
def hard_drop(self, grid):
"""硬降到底部"""
distance = 0
while self.move(0, 1, grid):
distance += 1
return distance
def collision(self, grid):
"""碰撞检测"""
shape = self.shape
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
board_x = self.x + x
board_y = self.y + y
# 检查x坐标是否在网格内
if board_x < 0 or board_x >= GRID_WIDTH:
return True
# 检查y坐标是否超出底部
if board_y >= GRID_HEIGHT:
return True
# 检查是否与已固定的方块碰撞(只检查在网格内的位置)
if board_y >= 0 and grid[board_y][board_x] is not None:
return True
return False
def draw(self, surface, x_offset, y_offset, is_ghost=False, particles=None):
"""绘制方块"""
shape = self.shape
block_size = GRID_SIZE
glow_size = block_size + 6
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
# 计算位置
pos_x = x_offset + (self.x + x) * block_size
pos_y = y_offset + (self.y + y) * block_size
# 添加移动偏移
if self.move_offset != 0:
pos_x += self.move_offset
self.move_offset *= 0.8
if abs(self.move_offset) < 0.5:
self.move_offset = 0
# 添加摆动效果
if self.wobble != 0:
wobble_offset = math.sin(self.wobble) * 2
pos_y += wobble_offset
self.wobble *= 0.9
if abs(self.wobble) < 0.1:
self.wobble = 0
if is_ghost:
# 绘制阴影方块
self._draw_ghost_block(surface, pos_x, pos_y, block_size)
else:
# 绘制主方块
self._draw_main_block(surface, pos_x, pos_y, block_size, particles)
# 添加方块粒子
if particles is not None and not is_ghost:
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell and random.random() < 0.1:
pos_x = x_offset + (self.x + x) * block_size + block_size // 2
pos_y = y_offset + (self.y + y) * block_size + block_size // 2
particles.append(Particle(pos_x, pos_y, BLOCK_GLOW[self.color_idx], "trail"))
def _draw_ghost_block(self, surface, x, y, size):
"""绘制阴影方块"""
# 外发光
glow_surf = pygame.Surface((size + 8, size + 8), pygame.SRCALPHA)
color = (*BLOCK_COLORS[self.color_idx][:3], 80)
pygame.draw.rect(glow_surf, color, (0, 0, size + 8, size + 8),
border_radius=4)
surface.blit(glow_surf, (x - 4, y - 4))
# 内透明方块
inner_surf = pygame.Surface((size - 4, size - 4), pygame.SRCALPHA)
color = (*BLOCK_COLORS[self.color_idx][:3], 30)
pygame.draw.rect(inner_surf, color, (0, 0, size - 4, size - 4),
border_radius=3)
surface.blit(inner_surf, (x + 2, y + 2))
# 边框
pygame.draw.rect(surface, (*BLOCK_COLORS[self.color_idx][:3], 100),
(x, y, size, size), 1, border_radius=3)
def _draw_main_block(self, surface, x, y, size, particles):
"""绘制主方块"""
color = BLOCK_COLORS[self.color_idx]
glow_color = BLOCK_GLOW[self.color_idx]
# 底部阴影
shadow_offset = 3
shadow_surf = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.rect(shadow_surf, (0, 0, 0, 100),
(shadow_offset, shadow_offset, size, size),
border_radius=5)
surface.blit(shadow_surf, (x, y))
# 主方块
pygame.draw.rect(surface, color, (x, y, size, size),
border_radius=5)
# 3D效果 - 高光
highlight_surf = pygame.Surface((size, size), pygame.SRCALPHA)
# 左上高光
pygame.draw.polygon(highlight_surf, (255, 255, 255, 60),
[(0, 0), (size, 0), (0, size)])
# 右下阴影
pygame.draw.polygon(highlight_surf, (0, 0, 0, 30),
[(size, 0), (0, size), (size, size)])
surface.blit(highlight_surf, (x, y))
# 内发光
inner_size = size - 6
inner_surf = pygame.Surface((inner_size, inner_size), pygame.SRCALPHA)
pygame.draw.rect(inner_surf, (*color[:3], 100),
(0, 0, inner_size, inner_size), border_radius=3)
surface.blit(inner_surf, (x + 3, y + 3))
# 发光边框
pygame.draw.rect(surface, (255, 255, 255, 150),
(x, y, size, size), 2, border_radius=5)
# 高光点
highlight_size = 8
highlight_surf = pygame.Surface((highlight_size, highlight_size), pygame.SRCALPHA)
pygame.draw.circle(highlight_surf, (255, 255, 255, 150),
(highlight_size//2, highlight_size//2), highlight_size//2)
surface.blit(highlight_surf, (x + 5, y + 5))
# 添加粒子效果
if particles is not None and random.random() < 0.05:
particles.append(Particle(
x + size//2, y + size//2,
glow_color, "sparkle"
))
# 游戏主类
class TetrisGame:
def __init__(self):
# 初始化屏幕
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT),
pygame.DOUBLEBUF | pygame.HWSURFACE)
pygame.display.set_caption("🎮 俄罗斯方块霓虹特别版")
# 设置图标
try:
icon_surf = pygame.Surface((32, 32))
icon_surf.fill(BLOCK_COLORS[0])
pygame.display.set_icon(icon_surf)
except:
pass
self.clock = pygame.time.Clock()
self.font_large = load_font(48, True)
self.font_medium = load_font(28)
self.font_small = load_font(20)
# 计算游戏区域位置
self.grid_left = (SCREEN_WIDTH - SIDEBAR_WIDTH - GRID_WIDTH * GRID_SIZE) // 2
self.grid_top = HEADER_HEIGHT + 20
# 游戏状态
self.reset_game()
# 创建半透明表面
self.panel_surf = pygame.Surface((SIDEBAR_WIDTH - 40, SCREEN_HEIGHT - 100),
pygame.SRCALPHA)
self.panel_surf.fill(COLORS['panel_bg'])
# 背景动画
self.bg_particles = []
self.bg_stars = []
self._init_background()
# 音效
self.sounds = self._init_sounds()
# 游戏状态
self.state = "playing" # playing, paused, gameover
self.last_state_change = 0
# 动画
self.animations = []
# 调试信息
self.debug_info = []
def reset_game(self):
"""重置游戏状态"""
self.grid = [[None for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
self.current_piece = Tetromino(GRID_WIDTH // 2 - 1, 0)
self.next_piece = Tetromino(0, 0)
self.hold_piece = None
self.can_hold = True
self.game_over = False
self.score = 0
self.level = 1
self.lines_cleared = 0
self.total_pieces = 0
self.combo = 0
self.last_clear_time = 0
self.particles = []
self.clear_effects = []
# 计算阴影
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
def _init_background(self):
"""初始化背景"""
# 创建星空背景
for _ in range(100):
x = random.randint(0, SCREEN_WIDTH)
y = random.randint(0, SCREEN_HEIGHT)
size = random.randint(1, 3)
speed = random.uniform(0.1, 0.5)
self.bg_stars.append({
'x': x, 'y': y, 'size': size, 'speed': speed,
'brightness': random.uniform(0.5, 1.0)
})
def _init_sounds(self):
"""初始化音效"""
sounds = {}
try:
# 单行消除音效
clear_sound_path = os.path.join(folder_name, 'clear.ogg')
if os.path.exists(clear_sound_path):
sounds['clear'] = pygame.mixer.Sound(clear_sound_path)
sounds['clear'].set_volume(0.5)
print(f"✓ 加载单行消除音效: {clear_sound_path}")
else:
print(f"✗ 找不到单行消除音效: {clear_sound_path}")
# 多行消除/连击音效
combo_sound_path = os.path.join(folder_name, 'combo.ogg')
if os.path.exists(combo_sound_path):
sounds['combo'] = pygame.mixer.Sound(combo_sound_path)
sounds['combo'].set_volume(0.6)
print(f"✓ 加载连击音效: {combo_sound_path}")
else:
print(f"✗ 找不到连击音效: {combo_sound_path}")
except Exception as e:
print(f"加载音效时出错: {e}")
print("游戏将继续运行,但没有音效")
return sounds
def spawn_new_piece(self):
"""生成新方块"""
self.current_piece = self.next_piece
self.current_piece.x = GRID_WIDTH // 2 - 1
self.current_piece.y = 0
self.next_piece = Tetromino(0, 0)
self.can_hold = True
# 计算新方块的阴影
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
# 检查游戏是否结束
if self.current_piece.collision(self.grid):
self.game_over = True
self.state = "gameover"
return False
self.total_pieces += 1
return True
def hold_current_piece(self):
"""暂存当前方块"""
if not self.can_hold:
return
if self.hold_piece is None:
self.hold_piece = Tetromino(0, 0)
self.hold_piece.shape_idx = self.current_piece.shape_idx
self.hold_piece.color_idx = self.current_piece.color_idx
self.spawn_new_piece()
else:
# 交换当前方块和暂存方块
temp_idx = self.current_piece.shape_idx
temp_color = self.current_piece.color_idx
self.current_piece.shape_idx = self.hold_piece.shape_idx
self.current_piece.color_idx = self.hold_piece.color_idx
self.current_piece.rotation = 0
self.current_piece.x = GRID_WIDTH // 2 - 1
self.current_piece.y = 0
self.hold_piece.shape_idx = temp_idx
self.hold_piece.color_idx = temp_color
self.can_hold = False
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
def clear_lines(self):
"""清除已满的行"""
lines_to_clear = []
# 调试:打印网格状态
self.debug_info = []
for y in range(GRID_HEIGHT):
row_count = sum(1 for cell in self.grid[y] if cell is not None)
if row_count > 0:
self.debug_info.append(f"行{y}: {row_count}/10")
if row_count == GRID_WIDTH:
lines_to_clear.append(y)
if not lines_to_clear:
self.combo = 0
return 0
# 计算连击
current_time = pygame.time.get_ticks()
if current_time - self.last_clear_time < 2000: # 2秒内
self.combo += 1
else:
self.combo = 1
self.last_clear_time = current_time
# 创建消除特效
for line in lines_to_clear:
self._create_clear_effect(line)
# 播放消除音效
cleared = len(lines_to_clear)
self._play_clear_sound(cleared)
# 创建多行消除特效
if cleared >= 2:
self._create_multi_line_effect(cleared, lines_to_clear)
# 计分
self.lines_cleared += cleared
# 计分规则
points_per_line = [0, 100, 300, 500, 800]
base_points = points_per_line[cleared] * self.level
# 连击加成
combo_bonus = self.combo * 50 * self.level
# 全清奖励
is_all_clear = all(cell is None for row in self.grid for cell in row)
all_clear_bonus = 2000 if is_all_clear else 0
self.score += base_points + combo_bonus + all_clear_bonus
# 升级
self.level = self.lines_cleared // 10 + 1
self.current_piece.move_delay = max(50, 600 - (self.level - 1) * 50)
# 移除行
for line in sorted(lines_to_clear, reverse=True):
del self.grid[line]
self.grid.insert(0, [None for _ in range(GRID_WIDTH)])
return cleared
def _play_clear_sound(self, lines_cleared):
"""播放消除音效"""
if not self.sounds:
return
try:
if lines_cleared == 1:
# 单行消除
if 'clear' in self.sounds:
self.sounds['clear'].play()
elif lines_cleared >= 2:
# 多行消除/连击
if 'combo' in self.sounds:
# 根据消除行数调整音量和音高
sound = self.sounds['combo']
# 计算音量 - 消除行数越多音量越大
volume = min(0.8, 0.5 + (lines_cleared - 1) * 0.1)
sound.set_volume(volume)
# 尝试改变音高
try:
# 简单的音高调整:通过播放速度
from pygame import mixer
# 重新创建音效以实现音高变化
original_array = pygame.sndarray.array(sound)
if original_array is not None:
# 计算音高因子 - 消除行数越多音高越高
pitch_factor = 1.0 + (lines_cleared - 2) * 0.1
# 创建新的音效
new_sound = pygame.mixer.Sound(original_array)
new_sound.set_volume(volume)
new_sound.play()
except:
# 如果音高调整失败,直接播放
sound.play()
except Exception as e:
print(f"播放音效时出错: {e}")
def _create_clear_effect(self, line):
"""创建行消除特效"""
for x in range(GRID_WIDTH):
if self.grid[line][x] is not None:
color_idx = self.grid[line][x]
center_x = self.grid_left + x * GRID_SIZE + GRID_SIZE // 2
center_y = self.grid_top + line * GRID_SIZE + GRID_SIZE // 2
# 创建粒子
for _ in range(15):
angle = random.uniform(0, math.pi * 2)
speed = random.uniform(2, 5)
particle = Particle(
center_x, center_y,
BLOCK_GLOW[color_idx],
"sparkle"
)
particle.speed_x = math.cos(angle) * speed
particle.speed_y = math.sin(angle) * speed
particle.gravity = 0.1
particle.decay = random.uniform(0.01, 0.03)
self.clear_effects.append(particle)
# 创建爆炸动画
for _ in range(5):
size = random.randint(20, 40)
self.animations.append({
'type': 'explosion',
'x': center_x,
'y': center_y,
'size': size,
'max_size': size * 2,
'color': BLOCK_COLORS[color_idx],
'life': 1.0,
'decay': 0.05
})
def _create_multi_line_effect(self, lines_cleared, lines_to_clear):
"""创建多行消除特效"""
center_x = SCREEN_WIDTH // 2
center_y = SCREEN_HEIGHT // 2
# 根据消除行数设置不同的颜色和文字
if lines_cleared == 2:
text = f"DOUBLE!"
color = (100, 200, 255) # 蓝色
size = 50
elif lines_cleared == 3:
text = f"TRIPLE!"
color = (100, 255, 100) # 绿色
size = 60
elif lines_cleared == 4:
text = f"TETRIS!"
color = (255, 100, 100) # 红色
size = 70
else:
text = f"COMBO x{lines_cleared}!"
color = (255, 255, 100) # 黄色
size = 80
# 创建多行消除动画
self.animations.append({
'type': 'multi_line',
'text': text,
'x': center_x,
'y': center_y,
'size': size,
'color': color,
'life': 1.0,
'decay': 0.02
})
# 添加粒子特效
for _ in range(30 * lines_cleared):
angle = random.uniform(0, math.pi * 2)
speed = random.uniform(3, 8)
particle = Particle(
center_x, center_y,
(*color, 200),
"sparkle"
)
particle.speed_x = math.cos(angle) * speed
particle.speed_y = math.sin(angle) * speed
particle.gravity = 0.1
particle.decay = random.uniform(0.01, 0.03)
self.clear_effects.append(particle)
def lock_piece(self):
"""锁定当前方块 - 修复版本"""
shape = self.current_piece.shape
placed_blocks = 0
# 先检查所有方块的位置
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
# 计算方块在网格中的实际位置
board_x = self.current_piece.x + x
board_y = self.current_piece.y + y
# 确保位置在网格范围内
if (0 <= board_x < GRID_WIDTH and
0 <= board_y < GRID_HEIGHT):
# 检查这个位置是否已经被占用
if self.grid[board_y][board_x] is None:
self.grid[board_y][board_x] = self.current_piece.color_idx
placed_blocks += 1
else:
# 这个位置已经被占用,记录错误
print(f"错误: 位置 ({board_x}, {board_y}) 已被占用!")
print(f"当前方块位置: ({self.current_piece.x}, {self.current_piece.y})")
print(f"方块形状: {shape}")
# 确保至少放置了一个方块
if placed_blocks == 0:
print("警告: 没有放置任何方块!")
# 添加锁定粒子
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
board_x = self.current_piece.x + x
board_y = self.current_piece.y + y
if (0 <= board_x < GRID_WIDTH and
0 <= board_y < GRID_HEIGHT):
center_x = self.grid_left + board_x * GRID_SIZE + GRID_SIZE // 2
center_y = self.grid_top + board_y * GRID_SIZE + GRID_SIZE // 2
for _ in range(5):
self.particles.append(Particle(
center_x, center_y,
BLOCK_GLOW[self.current_piece.color_idx],
"sparkle"
))
cleared = self.clear_lines()
if cleared > 0:
# 添加连击特效
if self.combo > 1:
self._create_combo_effect()
if not self.spawn_new_piece():
self.game_over = True
def _create_combo_effect(self):
"""创建连击特效"""
center_x = SCREEN_WIDTH // 2
center_y = SCREEN_HEIGHT // 2
# 连击文字
combo_text = f"COMBO x{self.combo}!"
# 根据连击次数设置不同的颜色
if self.combo == 2:
color = (100, 200, 255) # 蓝色
size = 40
elif self.combo == 3:
color = (100, 255, 100) # 绿色
size = 45
elif self.combo == 4:
color = (255, 100, 100) # 红色
size = 50
elif self.combo >= 5:
color = (255, 255, 100) # 黄色
size = 55
else:
color = (255, 100, 255) # 紫色
size = 40
self.animations.append({
'type': 'combo',
'text': combo_text,
'x': center_x,
'y': center_y - 100, # 在屏幕上方显示,避免与多行消除重叠
'size': size + self.combo * 2,
'color': color,
'life': 1.0,
'decay': 0.02
})
# 播放连击音效
if self.combo > 1 and 'combo' in self.sounds:
try:
# 连击次数越多,音量越大
volume = min(1.0, 0.5 + (self.combo - 1) * 0.1)
self.sounds['combo'].set_volume(volume)
self.sounds['combo'].play()
except:
pass
def update(self):
"""更新游戏状态"""
current_time = pygame.time.get_ticks()
# 更新背景星星
for star in self.bg_stars:
star['y'] += star['speed']
if star['y'] > SCREEN_HEIGHT:
star['y'] = 0
star['x'] = random.randint(0, SCREEN_WIDTH)
# 更新粒子
for particle in self.particles[:]:
if not particle.update():
self.particles.remove(particle)
for particle in self.clear_effects[:]:
if not particle.update():
self.clear_effects.remove(particle)
# 更新动画
for anim in self.animations[:]:
anim['life'] -= anim.get('decay', 0.05)
if anim['life'] <= 0:
self.animations.remove(anim)
else:
if anim['type'] == 'explosion':
anim['size'] += 5
elif anim['type'] in ['combo', 'multi_line']:
anim['y'] -= 2
# 让文字逐渐变大然后缩小
if anim['life'] > 0.5:
anim['size'] += 0.5
else:
anim['size'] -= 0.5
# 自动下落
if not self.game_over and self.state == "playing":
if current_time - self.current_piece.drop_time > self.current_piece.move_delay:
if not self.current_piece.move(0, 1, self.grid):
self.lock_piece()
self.current_piece.drop_time = current_time
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
def draw(self):
"""绘制游戏画面"""
# 绘制背景
self.screen.fill(COLORS['bg_dark'])
# 绘制星空
for star in self.bg_stars:
brightness = int(star['brightness'] * 255)
color = (brightness, brightness, brightness)
pygame.draw.circle(self.screen, color,
(int(star['x']), int(star['y'])), star['size'])
# 绘制标题
title = self.font_large.render("俄罗斯方块霓虹版", True, COLORS['text_accent'])
title_shadow = self.font_large.render("俄罗斯方块霓虹版", True, (0, 0, 0))
self.screen.blit(title_shadow, (SCREEN_WIDTH//2 - title.get_width()//2 + 2, 32))
self.screen.blit(title, (SCREEN_WIDTH//2 - title.get_width()//2, 30))
# 绘制游戏区域
self._draw_game_area()
# 绘制侧边栏
self._draw_sidebar()
# 绘制特效
self._draw_effects()
# 绘制调试信息
self._draw_debug_info()
# 绘制游戏状态
if self.state == "paused":
self._draw_pause_screen()
elif self.state == "gameover":
self._draw_game_over_screen()
pygame.display.flip()
def _draw_game_area(self):
"""绘制游戏区域"""
# 绘制游戏区域背景
game_area = pygame.Surface((GRID_WIDTH * GRID_SIZE + 20,
GRID_HEIGHT * GRID_SIZE + 20), pygame.SRCALPHA)
game_area.fill((0, 0, 0, 100))
pygame.draw.rect(game_area, COLORS['game_area_line'],
(0, 0, GRID_WIDTH * GRID_SIZE + 20, GRID_HEIGHT * GRID_SIZE + 20),
2, border_radius=10)
self.screen.blit(game_area, (self.grid_left - 10, self.grid_top - 10))
# 绘制网格背景
grid_bg = pygame.Surface((GRID_WIDTH * GRID_SIZE, GRID_HEIGHT * GRID_SIZE),
pygame.SRCALPHA)
grid_bg.fill((*COLORS['bg_grid'][:3], 100))
self.screen.blit(grid_bg, (self.grid_left, self.grid_top))
# 绘制网格线
for x in range(GRID_WIDTH + 1):
pygame.draw.line(self.screen, COLORS['grid_line'],
(self.grid_left + x * GRID_SIZE, self.grid_top),
(self.grid_left + x * GRID_SIZE,
self.grid_top + GRID_HEIGHT * GRID_SIZE))
for y in range(GRID_HEIGHT + 1):
pygame.draw.line(self.screen, COLORS['grid_line'],
(self.grid_left, self.grid_top + y * GRID_SIZE),
(self.grid_left + GRID_WIDTH * GRID_SIZE,
self.grid_top + y * GRID_SIZE))
# 绘制已固定的方块
self._draw_fixed_blocks()
# 绘制阴影方块
if not self.game_over and self.state == "playing":
ghost = Tetromino(self.current_piece.x, self.current_piece.y)
ghost.shape_idx = self.current_piece.shape_idx
ghost.rotation = self.current_piece.rotation
ghost.color_idx = self.current_piece.color_idx
ghost.y = self.current_piece.ghost_y
ghost.draw(self.screen, self.grid_left, self.grid_top, True)
# 绘制当前方块
if not self.game_over and self.state == "playing":
self.current_piece.draw(self.screen, self.grid_left, self.grid_top,
particles=self.particles)
def _draw_fixed_blocks(self):
"""绘制已固定的方块"""
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if self.grid[y][x] is not None:
color_idx = self.grid[y][x]
pos_x = self.grid_left + x * GRID_SIZE
pos_y = self.grid_top + y * GRID_SIZE
# 绘制方块
color = BLOCK_COLORS[color_idx]
pygame.draw.rect(self.screen, color,
(pos_x, pos_y, GRID_SIZE, GRID_SIZE),
border_radius=4)
# 3D效果
pygame.draw.rect(self.screen, (255, 255, 255, 50),
(pos_x, pos_y, GRID_SIZE, GRID_SIZE), 2, border_radius=4)
# 内发光
inner_size = GRID_SIZE - 6
inner_surf = pygame.Surface((inner_size, inner_size), pygame.SRCALPHA)
pygame.draw.rect(inner_surf, (*color[:3], 150),
(0, 0, inner_size, inner_size), border_radius=2)
self.screen.blit(inner_surf, (pos_x + 3, pos_y + 3))
def _draw_sidebar(self):
"""绘制侧边栏"""
sidebar_x = SCREEN_WIDTH - SIDEBAR_WIDTH + 20
# 绘制面板背景
panel = pygame.Surface((SIDEBAR_WIDTH - 40, SCREEN_HEIGHT - 100), pygame.SRCALPHA)
panel.fill(COLORS['panel_bg'])
pygame.draw.rect(panel, COLORS['text_secondary'],
(0, 0, SIDEBAR_WIDTH - 40, SCREEN_HEIGHT - 100), 2, border_radius=10)
self.screen.blit(panel, (sidebar_x, 80))
y_offset = 100
# 分数
self._draw_panel_text(f"分数: {self.score:,}", sidebar_x + 20, y_offset)
y_offset += 50
# 等级
self._draw_panel_text(f"等级: {self.level}", sidebar_x + 20, y_offset)
y_offset += 50
# 已消除行数
self._draw_panel_text(f"消除行数: {self.lines_cleared}", sidebar_x + 20, y_offset)
y_offset += 50
# 连击
if self.combo > 1:
combo_text = self.font_small.render(f"连击: x{self.combo}", True,
(255, 100, 100))
self.screen.blit(combo_text, (sidebar_x + 20, y_offset))
y_offset += 40
# 下一个方块
self._draw_panel_text("下一个:", sidebar_x + 20, y_offset)
y_offset += 40
# 绘制下一个方块预览
self._draw_next_piece_preview(sidebar_x + 50, y_offset)
y_offset += 120
# 暂存方块
self._draw_panel_text("暂存:", sidebar_x + 20, y_offset)
y_offset += 40
# 绘制暂存方块
self._draw_hold_piece_preview(sidebar_x + 50, y_offset)
y_offset += 120
# 操作说明
self._draw_controls(sidebar_x + 20, y_offset)
def _draw_panel_text(self, text, x, y, color=None):
"""绘制面板文本"""
if color is None:
color = COLORS['text_primary']
text_surface = self.font_small.render(text, True, color)
self.screen.blit(text_surface, (x, y))
def _draw_next_piece_preview(self, x, y):
"""绘制下一个方块预览"""
preview_surf = pygame.Surface((120, 100), pygame.SRCALPHA)
preview_surf.fill((0, 0, 0, 50))
pygame.draw.rect(preview_surf, COLORS['text_secondary'],
(0, 0, 120, 100), 2, border_radius=8)
# 绘制下一个方块
shape = self.next_piece.shape
shape_width = len(shape[0]) if shape else 0
shape_height = len(shape)
preview_x = (120 - shape_width * 20) // 2
preview_y = (100 - shape_height * 20) // 2
for sy, row in enumerate(shape):
for sx, cell in enumerate(row):
if cell:
block_x = preview_x + sx * 20
block_y = preview_y + sy * 20
color = BLOCK_COLORS[self.next_piece.color_idx]
pygame.draw.rect(preview_surf, color,
(block_x, block_y, 18, 18), border_radius=3)
pygame.draw.rect(preview_surf, (255, 255, 255, 150),
(block_x, block_y, 18, 18), 1, border_radius=3)
self.screen.blit(preview_surf, (x, y))
def _draw_hold_piece_preview(self, x, y):
"""绘制暂存方块预览"""
preview_surf = pygame.Surface((120, 100), pygame.SRCALPHA)
preview_surf.fill((0, 0, 0, 50))
pygame.draw.rect(preview_surf, COLORS['text_secondary'],
(0, 0, 120, 100), 2, border_radius=8)
if self.hold_piece:
shape = self.hold_piece.shape
shape_width = len(shape[0]) if shape else 0
shape_height = len(shape)
preview_x = (120 - shape_width * 20) // 2
preview_y = (100 - shape_height * 20) // 2
for sy, row in enumerate(shape):
for sx, cell in enumerate(row):
if cell:
block_x = preview_x + sx * 20
block_y = preview_y + sy * 20
color = BLOCK_COLORS[self.hold_piece.color_idx]
pygame.draw.rect(preview_surf, color,
(block_x, block_y, 18, 18), border_radius=3)
pygame.draw.rect(preview_surf, (255, 255, 255, 150),
(block_x, block_y, 18, 18), 1, border_radius=3)
self.screen.blit(preview_surf, (x, y))
def _draw_controls(self, x, y):
"""绘制操作说明"""
controls = [
"by xhlbudd@52pojie",
"← → : 左右移动",
"↑ : 旋转",
"↓ : 加速下落",
"空格 : 硬降到底部",
"C : 暂存方块",
"P : 暂停/继续",
"R : 重新开始",
"ESC : 退出游戏"
]
for i, text in enumerate(controls):
color = COLORS['text_accent'] if i == 0 else COLORS['text_secondary']
text_surf = self.font_small.render(text, True, color)
self.screen.blit(text_surf, (x, y + i * 25))
def _draw_debug_info(self):
"""绘制调试信息"""
if self.debug_info:
debug_y = 50
for info in self.debug_info[:5]: # 只显示前5行
debug_text = self.font_small.render(info, True, (255, 255, 0))
self.screen.blit(debug_text, (20, debug_y))
debug_y += 25
def _draw_effects(self):
"""绘制特效"""
# 绘制粒子
for particle in self.particles:
particle.draw(self.screen)
for particle in self.clear_effects:
particle.draw(self.screen)
# 绘制动画
for anim in self.animations:
if anim['type'] == 'explosion':
self._draw_explosion(anim)
elif anim['type'] == 'combo':
self._draw_combo_text(anim)
elif anim['type'] == 'multi_line':
self._draw_multi_line_text(anim)
def _draw_explosion(self, anim):
"""绘制爆炸特效"""
alpha = int(anim['life'] * 255)
size = anim['size']
# 创建爆炸表面
explosion = pygame.Surface((size * 2, size * 2), pygame.SRCALPHA)
# 绘制多个同心圆
for i in range(3):
radius = int(size * (1 - i * 0.3))
color = (*anim['color'][:3], alpha // (i + 1))
pygame.draw.circle(explosion, color, (size, size), radius)
self.screen.blit(explosion, (anim['x'] - size, anim['y'] - size))
def _draw_combo_text(self, anim):
"""绘制连击文字"""
alpha = int(anim['life'] * 255)
font = load_font(int(anim['size']), True)
text = font.render(anim['text'], True, (*anim['color'], alpha))
text_shadow = font.render(anim['text'], True, (0, 0, 0, alpha))
text_rect = text.get_rect(center=(anim['x'], anim['y']))
shadow_rect = text_shadow.get_rect(center=(anim['x'] + 3, anim['y'] + 3))
self.screen.blit(text_shadow, shadow_rect)
self.screen.blit(text, text_rect)
def _draw_multi_line_text(self, anim):
"""绘制多行消除文字"""
alpha = int(anim['life'] * 255)
font = load_font(int(anim['size']), True)
# 绘制文字
text = font.render(anim['text'], True, (*anim['color'], alpha))
text_shadow = font.render(anim['text'], True, (0, 0, 0, alpha))
text_rect = text.get_rect(center=(anim['x'], anim['y']))
shadow_rect = text_shadow.get_rect(center=(anim['x'] + 4, anim['y'] + 4))
self.screen.blit(text_shadow, shadow_rect)
self.screen.blit(text, text_rect)
# 绘制发光效果
if alpha > 100:
glow_size = int(anim['size'] * 1.5)
glow_font = load_font(glow_size, True)
glow_text = glow_font.render(anim['text'], True, (*anim['color'], alpha // 3))
glow_rect = glow_text.get_rect(center=(anim['x'], anim['y']))
self.screen.blit(glow_text, glow_rect)
def _draw_pause_screen(self):
"""绘制暂停屏幕"""
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
self.screen.blit(overlay, (0, 0))
pause_text = self.font_large.render("游戏暂停", True, COLORS['text_accent'])
pause_shadow = self.font_large.render("游戏暂停", True, (0, 0, 0))
self.screen.blit(pause_shadow, (SCREEN_WIDTH//2 - pause_text.get_width()//2 + 2, 302))
self.screen.blit(pause_text, (SCREEN_WIDTH//2 - pause_text.get_width()//2, 300))
continue_text = self.font_medium.render("按 P 键继续游戏", True, COLORS['text_primary'])
self.screen.blit(continue_text, (SCREEN_WIDTH//2 - continue_text.get_width()//2, 380))
def _draw_game_over_screen(self):
"""绘制游戏结束屏幕"""
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 200))
self.screen.blit(overlay, (0, 0))
game_over = self.font_large.render("游戏结束!", True, (255, 100, 100))
game_over_shadow = self.font_large.render("游戏结束!", True, (0, 0, 0))
self.screen.blit(game_over_shadow, (SCREEN_WIDTH//2 - game_over.get_width()//2 + 2, 252))
self.screen.blit(game_over, (SCREEN_WIDTH//2 - game_over.get_width()//2, 250))
score_text = self.font_medium.render(f"最终分数: {self.score:,}", True, COLORS['text_primary'])
self.screen.blit(score_text, (SCREEN_WIDTH//2 - score_text.get_width()//2, 320))
restart_text = self.font_medium.render("按 R 键重新开始", True, COLORS['text_accent'])
self.screen.blit(restart_text, (SCREEN_WIDTH//2 - restart_text.get_width()//2, 380))
def handle_events(self):
"""处理事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
return False
elif event.key == pygame.K_r:
self.reset_game()
self.state = "playing"
elif event.key == pygame.K_p:
if self.state == "playing":
self.state = "paused"
elif self.state == "paused":
self.state = "playing"
if not self.game_over and self.state == "playing":
if event.key == pygame.K_c:
self.hold_current_piece()
elif event.key == pygame.K_LEFT:
self.current_piece.move(-1, 0, self.grid)
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
elif event.key == pygame.K_RIGHT:
self.current_piece.move(1, 0, self.grid)
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
elif event.key == pygame.K_UP:
self.current_piece.rotate(self.grid)
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
elif event.key == pygame.K_DOWN:
if self.current_piece.move(0, 1, self.grid):
self.current_piece.ghost_y = self.current_piece.calculate_ghost(self.grid)
elif event.key == pygame.K_SPACE:
distance = self.current_piece.hard_drop(self.grid)
if distance > 0:
# 添加硬降特效
for _ in range(distance * 2):
self.particles.append(Particle(
self.grid_left + self.current_piece.x * GRID_SIZE + GRID_SIZE // 2,
self.grid_top + self.current_piece.y * GRID_SIZE + GRID_SIZE // 2,
BLOCK_GLOW[self.current_piece.color_idx],
"sparkle"
))
self.lock_piece()
return True
def run(self):
"""运行游戏主循环"""
print("🎮 霓虹俄罗斯方块 已启动!")
print("=" * 50)
print("操作说明:")
print(" ← → : 左右移动方块")
print(" ↑ : 旋转方块")
print(" ↓ : 加速下落")
print(" 空格 : 硬降到底部")
print(" C : 暂存当前方块")
print(" P : 暂停/继续游戏")
print(" R : 重新开始游戏")
print(" ESC : 退出游戏")
print("=" * 50)
print("游戏特点:")
print(" • 霓虹未来风格界面")
print(" • 粒子特效和动画")
print(" • 阴影预测和暂存功能")
print(" • 连击系统和分数加成")
print(" • 背景星空动画")
print(" • 音效系统(单行/多行消除)")
print(" • 多行消除特效(DOUBLE/TRIPLE/TETRIS)")
print(" • 调试信息显示(左上角)")
print("=" * 50)
running = True
while running:
running = self.handle_events()
self.update()
self.draw()
self.clock.tick(60)
pygame.quit()
sys.exit()
# 主程序入口
if __name__ == "__main__":
# 检查依赖
try:
import pygame
except ImportError:
print("错误: 需要安装 pygame 库")
print("请运行: pip install pygame")
sys.exit(1)
# 运行游戏
game = TetrisGame()
game.run()