# main.py (增强版复古俄罗斯方块)
# 版本: 2.5 (稳定版)
# 开发工程师: aistudio Gemini
# 日期: 2025-06-23
# 更新日志:
# - [移除] 为保证游戏稳定性，彻底移除了导致按键锁死的“暂存(Hold)”功能。
# - [修复] 将所有可配置参数整合到顶部的“配置中心”，方便用户自定义。
# - [调整] 增加了游戏结束画面按钮的垂直间距，优化布局。
# - [修复] 再次重构事件处理逻辑，彻底解决主菜单和游戏内侧边栏按钮无法点击的问题。
# - [修复] 将“影子”按钮的文字改为英文，以解决因字体不支持中文而导致的乱码问题。
# - [新增] 增加“影子方块”开关功能。
# - [调整] 将“旋转”功能改回空格键，“硬降落”移至“上箭头(↑)”键。

import pygame
import random
import sys
import os
from collections import deque

# =================================================================================
# ====================== ⭐️ 游戏核心配置中心 (可在此处调整) ⭐️ =====================
# =================================================================================
CONFIG = {
    # --- 界面文本与字体 (UI Text & Fonts) ---
    "title_text": "Retro Tetris",
    "font_sizes": {
        "large": 26,  # 大号字体，用于标题
        "medium": 24, # 中号字体 (未使用，备用)
        "small": 16,  # 小号字体，用于UI文本
    },

    # --- 布局与尺寸 (Layout & Sizing) ---
    "scale": 1.2,                 # 全局缩放比例
    "base_block_size": 30,        # 方块的基础像素尺寸
    "sidebar_width": 220,         # 侧边栏宽度
    "padding": 30,                # 窗口内边距
    "gap_between_areas": 30,      # 游戏区与侧边栏的间距
    "game_over_buttons_y_offset": 80, # 游戏结束画面按钮与上方文字的垂直距离

    # --- 游戏难度与手感 (Gameplay & Feel) ---
    "initial_fall_speed": 800,      # 初始下落速度(毫秒)，数字越大越慢
    "speed_increase_per_level": 70, # 每升一级，速度加快多少毫秒
    "lock_delay": 500,              # 方块触底后的锁定延迟(毫秒)
    "move_sideways_delay": 180,     # 长按左右键的初始延迟(毫秒)
    "move_sideways_interval": 40,   # 长按左右键的移动间隔(毫秒)
}
# =================================================================================

# --- 从配置中心计算全局常量 ---
SCALE = CONFIG['scale']
GRID_WIDTH, GRID_HEIGHT = 10, 20
BASE_BLOCK_SIZE = CONFIG['base_block_size']
BLOCK_SIZE = int(BASE_BLOCK_SIZE * SCALE)
SIDEBAR_WIDTH = int(CONFIG['sidebar_width'] * SCALE)
PADDING = int(CONFIG['padding'] * SCALE)
GAP_BETWEEN_AREAS = int(CONFIG['gap_between_areas'] * SCALE)
GAME_AREA_WIDTH = GRID_WIDTH * BLOCK_SIZE
GAME_AREA_HEIGHT = GRID_HEIGHT * BLOCK_SIZE
SCREEN_WIDTH = GAME_AREA_WIDTH + SIDEBAR_WIDTH + PADDING * 2 + GAP_BETWEEN_AREAS
SCREEN_HEIGHT = GAME_AREA_HEIGHT + PADDING * 2
GAME_AREA_X, GAME_AREA_Y = PADDING, PADDING

# --- 颜色定义 ---
BLACK = (20, 20, 30)
WHITE = (224, 224, 224)
GRID_COLOR = (40, 40, 50)
GHOST_COLOR_ALPHA = 80
BUTTON_COLOR = (45, 45, 60)
BUTTON_HOVER_COLOR = (80, 80, 100)
OVERLAY_COLOR = (0, 0, 0, 180)

# --- 方块与计分定义 ---
TETROMINO_DATA = {
    'I': {'shape': [[1, 1, 1, 1]], 'color': (30, 180, 210)},
    'O': {'shape': [[1, 1], [1, 1]], 'color': (230, 200, 40)},
    'T': {'shape': [[0, 1, 0], [1, 1, 1]], 'color': (180, 60, 220)},
    'J': {'shape': [[1, 0, 0], [1, 1, 1]], 'color': (50, 100, 230)},
    'L': {'shape': [[0, 0, 1], [1, 1, 1]], 'color': (220, 130, 30)},
    'S': {'shape': [[0, 1, 1], [1, 1, 0]], 'color': (80, 200, 90)},
    'Z': {'shape': [[1, 1, 0], [0, 1, 1]], 'color': (220, 50, 80)}
}
SCORE_VALUES = {0: 0, 1: 100, 2: 300, 3: 500, 4: 800}
LINES_PER_LEVEL = 10

# --- 资源路径处理 ---
def resource_path(relative_path):
    try: base_path = sys._MEIPASS
    except Exception: base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

# =================================================================================
# ======================== ⭐️ 资源加载模块 ⭐️ ===========================
# =================================================================================
class Assets:
    def __init__(self):
        self.large_font = self._load_font(CONFIG['font_sizes']['large'])
        self.medium_font = self._load_font(CONFIG['font_sizes']['medium'])
        self.small_font = self._load_font(CONFIG['font_sizes']['small'])
        self.sounds = self._load_sounds()
        self.icon = self._load_icon()
        self.set_sound_volumes()

    def _load_font(self, size):
        font_path = resource_path(os.path.join('assets', 'fonts', 'press-start-2p.ttf'))
        try:
            return pygame.font.Font(font_path, int(size * SCALE))
        except pygame.error:
            print(f"Warning: Font file not found at {font_path}. Using default font.")
            return pygame.font.Font(None, int(size * SCALE * 1.2))

    def _load_sounds(self):
        sounds = {}
        sound_files = {
            'drop': 'block-1-328874.mp3', 'clear': 'clear-bell-notification-sound-351709.mp3',
            'level_up': 'game-level-complete-143022.mp3', 'game_over': 'game-over-arcade-6435.mp3'
        }
        try:
            pygame.mixer.music.load(resource_path(os.path.join('assets', 'sounds', 'game-music-loop-6-144641.mp3')))
            for name, filename in sound_files.items():
                sounds[name] = pygame.mixer.Sound(resource_path(os.path.join('assets', 'sounds', filename)))
            return sounds
        except pygame.error as e:
            print(f"Warning: Could not load sounds. {e}. Game will be silent.")
            class DummySound:
                def play(self): pass
                def set_volume(self, v): pass
            return {name: DummySound() for name in sound_files.keys()}

    def _load_icon(self):
        try: return pygame.image.load(resource_path('favicon.ico'))
        except pygame.error: print("Warning: 'favicon.ico' not found."); return None

    def set_sound_volumes(self):
        self.volumes = {'music': 0.3, 'drop': 0.5, 'clear': 0.7, 'level_up': 0.8, 'game_over': 1.0}
        if pygame.mixer.get_init():
            pygame.mixer.music.set_volume(self.volumes['music'])
            for name, sound in self.sounds.items(): sound.set_volume(self.volumes[name])

# =================================================================================
# ======================== ⭐️ 游戏核心逻辑类 ⭐️ =========================
# =================================================================================
class Piece:
    def __init__(self, shape_name):
        self.shape_name = shape_name
        data = TETROMINO_DATA[shape_name]
        self.matrix = data['shape']
        self.color = data['color']
        self.x = GRID_WIDTH // 2 - len(self.matrix[0]) // 2
        self.y = 0

    def rotate(self): self.matrix = list(zip(*self.matrix[::-1]))

class PieceGenerator:
    def __init__(self): self.bag = []; self.refill_bag()
    def refill_bag(self): self.bag = list(TETROMINO_DATA.keys()); random.shuffle(self.bag)
    def next(self):
        if not self.bag: self.refill_bag()
        return Piece(self.bag.pop())

class Grid:
    def __init__(self): self.grid = [[BLACK for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]

    def is_valid_position(self, piece, ox=0, oy=0):
        for y, row in enumerate(piece.matrix):
            for x, cell in enumerate(row):
                if cell:
                    new_x, new_y = piece.x + x + ox, piece.y + y + oy
                    if not (0 <= new_x < GRID_WIDTH and 0 <= new_y < GRID_HEIGHT and self.grid[new_y][new_x] == BLACK):
                        return False
        return True

    def lock_piece(self, piece):
        for y, row in enumerate(piece.matrix):
            for x, cell in enumerate(row):
                if cell: self.grid[piece.y + y][piece.x + x] = piece.color

    def clear_lines(self):
        lines_to_clear = [i for i, row in enumerate(self.grid) if all(cell != BLACK for cell in row)]
        if lines_to_clear:
            for i in lines_to_clear:
                del self.grid[i]; self.grid.insert(0, [BLACK for _ in range(GRID_WIDTH)])
        return len(lines_to_clear)
    
    def reset(self): self.grid = [[BLACK for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]

class ScoreManager:
    def __init__(self): self.reset()
        
    def reset(self):
        self.score = 0; self.level = 1; self.lines_cleared = 0
        self.fall_speed = CONFIG['initial_fall_speed']

    def update(self, cleared_lines):
        leveled_up = False
        self.score += SCORE_VALUES.get(cleared_lines, 0) * self.level
        self.lines_cleared += cleared_lines
        
        new_level = 1 + self.lines_cleared // LINES_PER_LEVEL
        if new_level > self.level:
            self.level = new_level
            self.fall_speed = max(100, CONFIG['initial_fall_speed'] - (self.level - 1) * CONFIG['speed_increase_per_level'])
            leveled_up = True
        return leveled_up

# =================================================================================
# ======================== ⭐️ UI 与渲染类 ⭐️ ===========================
# =================================================================================
class Button:
    def __init__(self, text, pos, size, font, callback):
        self.rect = pygame.Rect(pos, size)
        self.text = text; self.font = font; self.callback = callback
        self.is_hovered = False

    def draw(self, screen):
        color = BUTTON_HOVER_COLOR if self.is_hovered else BUTTON_COLOR
        pygame.draw.rect(screen, color, self.rect, border_radius=int(8 * SCALE))
        surf = self.font.render(self.text, True, WHITE)
        screen.blit(surf, surf.get_rect(center=self.rect.center))

    def handle_event(self, event):
        if event.type == pygame.MOUSEMOTION:
            self.is_hovered = self.rect.collidepoint(event.pos)
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and self.is_hovered:
            if self.callback: self.callback()

class UIManager:
    def __init__(self, game, assets):
        self.game = game; self.assets = assets
        self.buttons = {}; self._setup_buttons()

    def _setup_buttons(self):
        btn_w, btn_h = int(180 * SCALE), int(45 * SCALE)
        sidebar_x = GAME_AREA_X + GAME_AREA_WIDTH + GAP_BETWEEN_AREAS
        btn_x = sidebar_x + (SIDEBAR_WIDTH - btn_w) // 2
        btn_gap = int(55 * SCALE)
        
        # --- [移除] 暂存功能后，按钮数量减少，重新布局 ---
        btn_y_start = SCREEN_HEIGHT - PADDING - (btn_h + int(10*SCALE)) * 4
        self.buttons['in_game'] = [
            Button("Pause", (btn_x, btn_y_start), (btn_w, btn_h), self.assets.small_font, self.game.toggle_pause),
            Button("Restart", (btn_x, btn_y_start + btn_gap), (btn_w, btn_h), self.assets.small_font, self.game.reset),
            Button("Mute", (btn_x, btn_y_start + btn_gap * 2), (btn_w, btn_h), self.assets.small_font, self.game.toggle_mute),
            Button("Ghost:Off", (btn_x, btn_y_start + btn_gap * 3), (btn_w, btn_h), self.assets.small_font, self.game.toggle_ghost)
        ]

        menu_btn_x = SCREEN_WIDTH / 2 - btn_w / 2
        self.buttons['menu'] = [
            Button("Start Game", (menu_btn_x, SCREEN_HEIGHT / 2), (btn_w, btn_h), self.assets.small_font, self.game.start_game),
            Button("Quit", (menu_btn_x, SCREEN_HEIGHT / 2 + btn_gap), (btn_w, btn_h), self.assets.small_font, sys.exit)
        ]
        self.buttons['paused'] = [
            Button("Resume", (menu_btn_x, SCREEN_HEIGHT / 2 - btn_gap), (btn_w, btn_h), self.assets.small_font, self.game.toggle_pause),
            Button("Restart", (menu_btn_x, SCREEN_HEIGHT / 2), (btn_w, btn_h), self.assets.small_font, self.game.reset),
            Button("Quit", (menu_btn_x, SCREEN_HEIGHT / 2 + btn_gap), (btn_w, btn_h), self.assets.small_font, sys.exit)
        ]
        
        game_over_btn_y = SCREEN_HEIGHT/2 + int(CONFIG['game_over_buttons_y_offset'] * SCALE)
        self.buttons['game_over'] = [
            Button("Restart", (menu_btn_x, game_over_btn_y), (btn_w, btn_h), self.assets.small_font, self.game.reset),
            Button("Main Menu", (menu_btn_x, game_over_btn_y + btn_gap), (btn_w, btn_h), self.assets.small_font, self.game.go_to_menu)
        ]

    def get_buttons(self, state): return self.buttons.get(state, [])

class Renderer:
    def __init__(self, screen, assets):
        self.screen = screen; self.assets = assets
        self.crt_scanline_surface = self._create_crt_scanline_surface()

    def _create_crt_scanline_surface(self):
        w, h = self.screen.get_size()
        scanline_surface = pygame.Surface((w, h), pygame.SRCALPHA)
        for y in range(0, h, int(4 * SCALE)):
            pygame.draw.line(scanline_surface, (0, 0, 0, 40), (0, y), (w, y), int(2 * SCALE))
        return scanline_surface

    def draw(self, game):
        self.screen.fill(BLACK)
        if game.state == "menu": self.draw_main_menu(game)
        else:
            self.draw_game_area(game.grid)
            self.draw_piece(game.current_piece)
            if game.show_ghost: self.draw_ghost_piece(game)
            self.draw_sidebar(game)
            if game.state == "paused": self.draw_overlay("PAUSED", game.ui_manager.get_buttons('paused'))
            elif game.state == "game_over": self.draw_game_over_overlay(game)
        self.screen.blit(self.crt_scanline_surface, (0, 0))
        pygame.display.flip()

    def draw_piece(self, piece, ghost=False):
        if not piece: return
        for y, row in enumerate(piece.matrix):
            for x, cell in enumerate(row):
                if cell:
                    rect = (GAME_AREA_X + (piece.x + x) * BLOCK_SIZE, GAME_AREA_Y + (piece.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                    if ghost:
                        surf = pygame.Surface((BLOCK_SIZE, BLOCK_SIZE), pygame.SRCALPHA)
                        surf.fill((*piece.color, GHOST_COLOR_ALPHA))
                        self.screen.blit(surf, rect[:2])
                    else:
                        pygame.draw.rect(self.screen, piece.color, rect)
                        pygame.draw.rect(self.screen, WHITE, rect, 1)

    def draw_ghost_piece(self, game):
        if not game.current_piece: return
        ghost = Piece(game.current_piece.shape_name)
        ghost.matrix = game.current_piece.matrix
        ghost.x = game.current_piece.x; ghost.y = game.current_piece.y
        ghost.color = game.current_piece.color
        while game.grid.is_valid_position(ghost, oy=1): ghost.y += 1
        self.draw_piece(ghost, ghost=True)

    def draw_game_area(self, grid_obj):
        for y, row in enumerate(grid_obj.grid):
            for x, color in enumerate(row):
                if color != BLACK:
                    rect = (GAME_AREA_X + x * BLOCK_SIZE, GAME_AREA_Y + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                    pygame.draw.rect(self.screen, color, rect); pygame.draw.rect(self.screen, WHITE, rect, 1)
        for y in range(GRID_HEIGHT):
            for x in range(GRID_WIDTH):
                rect = (GAME_AREA_X + x * BLOCK_SIZE, GAME_AREA_Y + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                pygame.draw.rect(self.screen, GRID_COLOR, rect, 1)

    def draw_sidebar(self, game):
        sidebar_x = GAME_AREA_X + GAME_AREA_WIDTH + GAP_BETWEEN_AREAS
        sidebar_center_x = sidebar_x + SIDEBAR_WIDTH // 2
        def draw_info(label, value, y_pos):
            label_surf = self.assets.small_font.render(label, True, WHITE)
            self.screen.blit(label_surf, label_surf.get_rect(centerx=sidebar_center_x, top=y_pos))
            value_surf = self.assets.small_font.render(str(value), True, WHITE)
            self.screen.blit(value_surf, value_surf.get_rect(centerx=sidebar_center_x, top=y_pos + int(25 * SCALE)))
        draw_info("SCORE", game.score_manager.score, GAME_AREA_Y + int(40*SCALE))
        draw_info("LEVEL", game.score_manager.level, GAME_AREA_Y + int(110*SCALE))
        draw_info("LINES", game.score_manager.lines_cleared, GAME_AREA_Y + int(180*SCALE))

        next_title_y = GAME_AREA_Y + int(260*SCALE)
        next_label_surf = self.assets.small_font.render("NEXT", True, WHITE)
        self.screen.blit(next_label_surf, next_label_surf.get_rect(centerx=sidebar_center_x, top=next_title_y))
        if game.next_piece: self.draw_sidebar_piece(game.next_piece, sidebar_center_x, next_title_y + int(25*SCALE))
        
        # --- [移除] 移除暂存方块的绘制逻辑 ---
        
        for btn in game.ui_manager.get_buttons('in_game'): btn.draw(self.screen)

    def draw_sidebar_piece(self, piece, center_x, top_y):
        matrix = piece.matrix
        piece_w_pixels = len(matrix[0]) * BLOCK_SIZE
        start_x = center_x - piece_w_pixels // 2
        for y, row in enumerate(matrix):
            for x, cell in enumerate(row):
                if cell:
                    rect = (start_x + x * BLOCK_SIZE, top_y + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                    pygame.draw.rect(self.screen, piece.color, rect); pygame.draw.rect(self.screen, WHITE, rect, 1)

    def draw_overlay(self, text, buttons):
        overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA); overlay.fill(OVERLAY_COLOR)
        self.screen.blit(overlay, (0, 0))
        main_surf = self.assets.large_font.render(text, True, WHITE)
        self.screen.blit(main_surf, main_surf.get_rect(center=(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 3)))
        for btn in buttons: btn.draw(self.screen)
            
    def draw_game_over_overlay(self, game):
        overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA); overlay.fill(OVERLAY_COLOR)
        self.screen.blit(overlay, (0, 0))
        
        title_surf = self.assets.large_font.render("GAME OVER", True, WHITE)
        self.screen.blit(title_surf, title_surf.get_rect(center=(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 4)))
        
        y_start = SCREEN_HEIGHT / 4 + int(100*SCALE); line_height = int(40*SCALE)
        def draw_stat(label, value, y):
            text = f"{label}: {value}"
            surf = self.assets.small_font.render(text, True, WHITE)
            self.screen.blit(surf, surf.get_rect(centerx=SCREEN_WIDTH / 2, top=y))
        draw_stat("Final Score", game.score_manager.score, y_start)
        draw_stat("Level Reached", game.score_manager.level, y_start + line_height)
        draw_stat("Lines Cleared", game.score_manager.lines_cleared, y_start + line_height * 2)

        for btn in game.ui_manager.get_buttons('game_over'): btn.draw(self.screen)

    def draw_main_menu(self, game):
        surf = self.assets.large_font.render(CONFIG['title_text'], True, WHITE)
        self.screen.blit(surf, surf.get_rect(center=(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 3)))
        for btn in game.ui_manager.get_buttons('menu'): btn.draw(self.screen)
            
# =================================================================================
# ======================== ⭐️ 游戏主控类 ⭐️ ===========================
# =================================================================================
class TetrisGame:
    def __init__(self):
        pygame.init(); pygame.mixer.init()
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        pygame.display.set_caption(f"{CONFIG['title_text']} - 复古方块 - 像素风俄罗斯方块挑战")
        self.assets = Assets()
        if self.assets.icon: pygame.display.set_icon(self.assets.icon)
            
        self.clock = pygame.time.Clock(); self.state = "menu"
        self.grid = Grid(); self.score_manager = ScoreManager()
        self.piece_generator = PieceGenerator(); self.renderer = Renderer(self.screen, self.assets)
        self.ui_manager = UIManager(self, self.assets)
        
        self.fall_time = 0; self.lock_timer = None
        self.current_piece = None; self.next_piece = None
        # --- [移除] 移除暂存功能相关的状态变量 ---
        self.is_muted = False; self.show_ghost = False
        self.move_sideways_time = 0; self.key_down_time = {'left': 0, 'right': 0}

    def reset(self):
        self.grid.reset(); self.score_manager.reset(); self.piece_generator.refill_bag()
        self.current_piece = self.piece_generator.next(); self.next_piece = self.piece_generator.next()
        # --- [移除] 移除暂存功能相关的重置逻辑 ---
        self.fall_time = pygame.time.get_ticks(); self.lock_timer = None
        self.state = "playing"
        if not self.is_muted and pygame.mixer.get_init(): pygame.mixer.music.play(-1)

    def start_game(self): self.reset()
    def go_to_menu(self): self.state = "menu"; pygame.mixer.music.stop() if pygame.mixer.get_init() else None

    def toggle_pause(self):
        if self.state == "playing": self.state = "paused"; pygame.mixer.music.pause() if pygame.mixer.get_init() else None
        elif self.state == "paused": self.state = "playing"; pygame.mixer.music.unpause() if not self.is_muted and pygame.mixer.get_init() else None

    def toggle_mute(self):
        self.is_muted = not self.is_muted
        self.ui_manager.buttons['in_game'][2].text = "Unmute" if self.is_muted else "Mute"
        if pygame.mixer.get_init():
            music_vol = self.assets.volumes['music'] if not self.is_muted else 0
            pygame.mixer.music.set_volume(music_vol)
            for name, sound in self.assets.sounds.items(): sound.set_volume(self.assets.volumes[name] if not self.is_muted else 0)
    
    def toggle_ghost(self):
        self.show_ghost = not self.show_ghost
        self.ui_manager.buttons['in_game'][3].text = "Ghost: On" if self.show_ghost else "Ghost: Off"

    def move(self, dx, dy):
        if not self.current_piece: return False
        if self.grid.is_valid_position(self.current_piece, dx, dy):
            self.current_piece.x += dx; self.current_piece.y += dy
            self.reset_lock_delay(); return True
        return False

    def rotate(self):
        if not self.current_piece: return
        original_matrix = self.current_piece.matrix
        self.current_piece.rotate()
        if not self.grid.is_valid_position(self.current_piece):
            if self.grid.is_valid_position(self.current_piece, ox=1): self.current_piece.x += 1
            elif self.grid.is_valid_position(self.current_piece, ox=-1): self.current_piece.x -= 1
            else: self.current_piece.matrix = original_matrix; return
        self.reset_lock_delay()

    def hard_drop(self):
        if not self.current_piece: return
        while self.grid.is_valid_position(self.current_piece, oy=1): self.current_piece.y += 1
        self.lock_piece()

    # --- [移除] 彻底移除 hold 方法 ---

    def lock_piece(self):
        self.grid.lock_piece(self.current_piece)
        if self.assets.sounds.get('drop'): self.assets.sounds['drop'].play()
        
        cleared_lines = self.grid.clear_lines()
        if cleared_lines > 0:
            if self.assets.sounds.get('clear'): self.assets.sounds['clear'].play()
            if self.score_manager.update(cleared_lines):
                if self.assets.sounds.get('level_up'): self.assets.sounds['level_up'].play()

        self.current_piece = self.next_piece
        self.next_piece = self.piece_generator.next()
        self.lock_timer = None
        if not self.grid.is_valid_position(self.current_piece):
            self.state = "game_over"
            if pygame.mixer.get_init():
                pygame.mixer.music.stop()
                if self.assets.sounds.get('game_over'): self.assets.sounds['game_over'].play()
    
    def reset_lock_delay(self):
        if self.current_piece and not self.grid.is_valid_position(self.current_piece, oy=1):
            self.lock_timer = pygame.time.get_ticks()

    def run(self):
        while True:
            now = pygame.time.get_ticks()
            self.handle_events(now)
            if self.state == "playing": self.update(now)
            self.renderer.draw(self)
            self.clock.tick(60)

    def handle_events(self, now):
        if self.state == "playing":
            keys = pygame.key.get_pressed()
            if keys[pygame.K_DOWN] or keys[pygame.K_s]: self.move(0, 1)
            if keys[pygame.K_LEFT] or keys[pygame.K_a]:
                if now - self.key_down_time.get('left', 0) > CONFIG['move_sideways_delay'] and now - self.move_sideways_time > CONFIG['move_sideways_interval']:
                    self.move(-1, 0); self.move_sideways_time = now
            if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
                if now - self.key_down_time.get('right', 0) > CONFIG['move_sideways_delay'] and now - self.move_sideways_time > CONFIG['move_sideways_interval']:
                    self.move(1, 0); self.move_sideways_time = now
        for event in pygame.event.get():
            if event.type == pygame.QUIT: pygame.quit(); sys.exit()
            if self.state == 'menu': self.handle_menu_events(event)
            elif self.state == 'playing': self.handle_playing_events(event, now)
            elif self.state == 'paused': self.handle_paused_events(event)
            elif self.state == 'game_over': self.handle_game_over_events(event)

    def handle_menu_events(self, event):
        for btn in self.ui_manager.get_buttons('menu'): btn.handle_event(event)

    def handle_playing_events(self, event, now):
        for btn in self.ui_manager.get_buttons('in_game'): btn.handle_event(event)
        if event.type == pygame.KEYDOWN:
            if event.key in [pygame.K_LEFT, pygame.K_a]: self.move(-1, 0); self.key_down_time['left'] = now
            elif event.key in [pygame.K_RIGHT, pygame.K_d]: self.move(1, 0); self.key_down_time['right'] = now
            elif event.key in [pygame.K_SPACE, pygame.K_x, pygame.K_w]: self.rotate()
            elif event.key == pygame.K_UP: self.hard_drop()
            # --- [移除] 移除C键暂存的事件绑定 ---
            elif event.key == pygame.K_ESCAPE: self.toggle_pause()
        if event.type == pygame.KEYUP:
            if event.key in [pygame.K_LEFT, pygame.K_a]: self.key_down_time['left'] = 0
            if event.key in [pygame.K_RIGHT, pygame.K_d]: self.key_down_time['right'] = 0

    def handle_paused_events(self, event):
        for btn in self.ui_manager.get_buttons('paused'): btn.handle_event(event)
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: self.toggle_pause()
            
    def handle_game_over_events(self, event):
        for btn in self.ui_manager.get_buttons('game_over'): btn.handle_event(event)
        if event.type == pygame.KEYDOWN and event.key in [pygame.K_r, pygame.K_RETURN]: self.reset()

    def update(self, now):
        if self.current_piece is None: return
        if now - self.fall_time > self.score_manager.fall_speed:
            self.fall_time = now
            if not self.move(0, 1):
                if self.lock_timer is None: self.lock_timer = now
        if self.lock_timer and now - self.lock_timer > CONFIG['lock_delay']:
            if not self.grid.is_valid_position(self.current_piece, oy=1): self.lock_piece()

if __name__ == '__main__':
    game = TetrisGame()
    game.run()
