累计签到:6 天 连续签到:2 天
|
近来在教舅舅家上初二的儿子Python编程,每次教完基本语法,都会用一个小游戏来资助他提高爱好。今天为各人带来Python开发的俄罗斯方块游戏,使用pygame 2.5.2 (SDL 2.28.3, Python 3.12.6)开发,一共1338行代码。带霓虹炫彩和音效,自己玩了挺多遍,体验还不错,欢迎各人下载试玩。下载链接如下(带exe格式的游戏约27MB,源代码和音乐素材):
链接: https://pan.Baidu.com/s/11RkBSKlwdWIpcA34JvwFJw?pwd=52pj
提取码: 52pj
同时把源代码分享如下,请各人多多指点:
[Python] 纯文本查看 复制代码import pygameimport randomimport mathimport osimport sysfrom typing import List, Tuple, Optional, Dict, Anyfrom pygame import gfxdrawimport json# 初始化 Pygamepygame.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 = 1100SCREEN_HEIGHT = 900GRID_SIZE = 35GRID_WIDTH = 10GRID_HEIGHT = 20SIDEBAR_WIDTH = 300HEADER_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 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.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() |
精密测量技术论坛免责声明
重要声明:以上内容仅代表该作者观点,不代表本站精密测量技术论坛立场。
如有涉及侵权请尽快告知,我们将会在第一时间处理。作者原创内容未经允许不得转载!
站长联系邮箱:1339305021@qq.com
站长联系微信:dddnnbbb
|