# 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()