【教程】用Python复刻经典小游戏(贪吃蛇、扫雷)
目录
- 【教程】用Python复刻经典小游戏(贪吃蛇、扫雷)
- 1. 引言:经典游戏编程的魅力
- 1.1 为什么选择复刻经典游戏?
- 1.2 技术栈选择与准备
- 2. 贪吃蛇游戏开发
- 2.1 游戏架构设计
- 2.2 贪吃蛇游戏的核心算法
- 3. 扫雷游戏开发
- 3.1 游戏架构与算法设计
- 3.2 扫雷游戏的核心算法
- 4. 完整代码实现与优化
- 4.1 完整的游戏启动器
- 4.2 性能优化与代码质量
- 5. 游戏设计模式与架构
- 5.1 游戏开发中的设计模式
- 6. 总结与扩展
- 6.1 学习成果总结
- 6.2 完整项目架构图
- 6.3 最终建议
『宝藏代码胶囊开张啦!』—— 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 “白菜价”+“量身定制”!无论是卡脖子的毕设/课设/文献复现,需要灵光一现的算法改进,还是想给项目加个“外挂”,这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网
【教程】用Python复刻经典小游戏(贪吃蛇、扫雷)
1. 引言:经典游戏编程的魅力
1.1 为什么选择复刻经典游戏?
经典游戏如贪吃蛇和扫雷不仅是许多人的童年回忆,更是学习编程的绝佳练手项目。根据2024年编程学习趋势调查,游戏开发是初学者最受欢迎的学习路径之一,其优势在于:
- 即时反馈:代码修改立即反映在游戏表现上
- 成就感强:从零到一创造可玩性产品
- 涵盖面广:涉及算法、数据结构、UI设计等多个方面
- 趣味性强:学习过程不再枯燥
1.2 技术栈选择与准备
本教程将使用Python的Pygame库来实现这两个经典游戏。Pygame是一个专门用于游戏开发的Python库,它提供了丰富的图形、声音和输入处理功能。
# 环境准备与依赖检查
import sys
import importlib.utilclass EnvironmentChecker:"""环境检查器 - 确保运行环境正确配置"""def __init__(self):self.required_packages = {'pygame': 'pygame','numpy': 'numpy'}self.check_results = {}def check_package_installed(self, package_name: str, import_name: str = None) -> bool:"""检查包是否已安装"""if import_name is None:import_name = package_nametry:spec = importlib.util.find_spec(import_name)installed = spec is not Noneself.check_results[package_name] = installedreturn installedexcept ImportError:self.check_results[package_name] = Falsereturn Falsedef check_environment(self) -> bool:"""全面检查环境"""print("🔍 检查Python环境...")print(f"Python版本: {sys.version}")all_installed = Truefor package, import_name in self.required_packages.items():installed = self.check_package_installed(package, import_name)status = "✅ 已安装" if installed else "❌ 未安装"print(f"{package}: {status}")if not installed:all_installed = Falseif not all_installed:print("\n💡 安装缺失的包:")for package, installed in self.check_results.items():if not installed:print(f"pip install {package}")return all_installeddef get_pygame_info(self):"""获取Pygame版本信息"""try:import pygameprint(f"🎮 Pygame版本: {pygame.version.ver}")return Trueexcept ImportError:print("❌ Pygame未安装,请运行: pip install pygame")return False# 运行环境检查
checker = EnvironmentChecker()
environment_ok = checker.check_environment()if environment_ok:checker.get_pygame_info()print("\n🎉 环境检查通过!可以开始游戏开发之旅")
else:print("\n⚠️ 请先安装缺失的依赖包")
2. 贪吃蛇游戏开发
2.1 游戏架构设计
贪吃蛇游戏的核心组件包括:蛇身管理、食物生成、碰撞检测和游戏逻辑控制。让我们先设计游戏的整体架构:
import pygame
import random
import time
from typing import List, Tuple, Optional
from enum import Enumclass Direction(Enum):"""移动方向枚举"""UP = (0, -1)DOWN = (0, 1)LEFT = (-1, 0)RIGHT = (1, 0)class GameState(Enum):"""游戏状态枚举"""RUNNING = 1PAUSED = 2GAME_OVER = 3START_MENU = 4class SnakeGameConfig:"""贪吃蛇游戏配置类"""def __init__(self):# 显示配置self.screen_width = 800self.screen_height = 600self.grid_size = 20self.grid_width = self.screen_width // self.grid_sizeself.grid_height = self.screen_height // self.grid_size# 颜色配置 (RGB)self.colors = {'background': (15, 15, 15),'grid': (40, 40, 40),'snake_head': (50, 205, 50), # 亮绿色'snake_body': (34, 139, 34), # 森林绿'food': (220, 20, 60), # 深红色'text': (255, 255, 255), # 白色'score': (255, 215, 0) # 金色}# 游戏配置self.initial_snake_length = 3self.game_speed = 10 # 帧率控制游戏速度self.score_per_food = 10class Snake:"""贪吃蛇类"""def __init__(self, config: SnakeGameConfig):self.config = configself.reset()def reset(self):"""重置蛇的状态"""# 蛇初始位置在屏幕中央start_x = self.config.grid_width // 2start_y = self.config.grid_height // 2# 初始化蛇身,长度为initial_snake_lengthself.body = [(start_x, start_y - i) for i in range(self.config.initial_snake_length)]self.direction = Direction.DOWNself.next_direction = self.directionself.grow_pending = Falsedef change_direction(self, new_direction: Direction):"""改变蛇的移动方向"""# 防止直接反向移动opposite_directions = {Direction.UP: Direction.DOWN,Direction.DOWN: Direction.UP,Direction.LEFT: Direction.RIGHT,Direction.RIGHT: Direction.LEFT}if new_direction != opposite_directions.get(self.direction):self.next_direction = new_directiondef move(self):"""移动蛇"""self.direction = self.next_direction# 计算新的头部位置head_x, head_y = self.body[0]dx, dy = self.direction.valuenew_head = ((head_x + dx) % self.config.grid_width,(head_y + dy) % self.config.grid_height)# 移动蛇身self.body.insert(0, new_head)# 如果不需要增长,移除尾部if not self.grow_pending:self.body.pop()else:self.grow_pending = Falsedef grow(self):"""蛇身增长"""self.grow_pending = Truedef check_self_collision(self) -> bool:"""检查是否撞到自己"""head = self.body[0]return head in self.body[1:]def get_head_position(self) -> Tuple[int, int]:"""获取头部位置"""return self.body[0]def get_length(self) -> int:"""获取蛇的长度"""return len(self.body)class Food:"""食物类"""def __init__(self, config: SnakeGameConfig):self.config = configself.position = (0, 0)self.randomize_position()def randomize_position(self, snake_body: List[Tuple[int, int]] = None):"""随机生成食物位置"""if snake_body is None:snake_body = []while True:new_position = (random.randint(0, self.config.grid_width - 1),random.randint(0, self.config.grid_height - 1))# 确保食物不会出现在蛇身上if new_position not in snake_body:self.position = new_positionbreakdef get_position(self) -> Tuple[int, int]:"""获取食物位置"""return self.positionclass SnakeGame:"""贪吃蛇游戏主类"""def __init__(self):pygame.init()self.config = SnakeGameConfig()self.screen = pygame.display.set_mode((self.config.screen_width, self.config.screen_height))pygame.display.set_caption("Python贪吃蛇")self.clock = pygame.time.Clock()self.font = pygame.font.Font(None, 36)self.small_font = pygame.font.Font(None, 24)self.snake = Snake(self.config)self.food = Food(self.config)self.score = 0self.high_score = 0self.state = GameState.START_MENU# 重新生成食物,避开蛇的初始位置self.food.randomize_position(self.snake.body)def handle_events(self):"""处理游戏事件"""for event in pygame.event.get():if event.type == pygame.QUIT:return Falseelif event.type == pygame.KEYDOWN:if self.state == GameState.START_MENU:if event.key == pygame.K_SPACE:self.start_game()elif self.state == GameState.RUNNING:if event.key == pygame.K_p:self.state = GameState.PAUSEDelif event.key == pygame.K_UP:self.snake.change_direction(Direction.UP)elif event.key == pygame.K_DOWN:self.snake.change_direction(Direction.DOWN)elif event.key == pygame.K_LEFT:self.snake.change_direction(Direction.LEFT)elif event.key == pygame.K_RIGHT:self.snake.change_direction(Direction.RIGHT)elif self.state == GameState.PAUSED:if event.key == pygame.K_p:self.state = GameState.RUNNINGelif self.state == GameState.GAME_OVER:if event.key == pygame.K_r:self.restart_game()elif event.key == pygame.K_q:return Falsereturn Truedef start_game(self):"""开始游戏"""self.state = GameState.RUNNINGdef restart_game(self):"""重新开始游戏"""self.snake.reset()self.food.randomize_position(self.snake.body)self.score = 0self.state = GameState.RUNNINGdef update(self):"""更新游戏状态"""if self.state != GameState.RUNNING:return# 移动蛇self.snake.move()# 检查是否吃到食物if self.snake.get_head_position() == self.food.position:self.snake.grow()self.score += self.config.score_per_foodself.high_score = max(self.high_score, self.score)self.food.randomize_position(self.snake.body)# 检查游戏结束条件if self.snake.check_self_collision():self.state = GameState.GAME_OVERdef draw_grid(self):"""绘制网格"""for x in range(0, self.config.screen_width, self.config.grid_size):pygame.draw.line(self.screen, self.config.colors['grid'], (x, 0), (x, self.config.screen_height), 1)for y in range(0, self.config.screen_height, self.config.grid_size):pygame.draw.line(self.screen, self.config.colors['grid'],(0, y), (self.config.screen_width, y), 1)def draw_snake(self):"""绘制蛇"""for i, (x, y) in enumerate(self.snake.body):color = self.config.colors['snake_head'] if i == 0 else self.config.colors['snake_body']rect = pygame.Rect(x * self.config.grid_size,y * self.config.grid_size,self.config.grid_size,self.config.grid_size)pygame.draw.rect(self.screen, color, rect)pygame.draw.rect(self.screen, self.config.colors['background'], rect, 1)def draw_food(self):"""绘制食物"""x, y = self.food.positionrect = pygame.Rect(x * self.config.grid_size,y * self.config.grid_size,self.config.grid_size,self.config.grid_size)pygame.draw.rect(self.screen, self.config.colors['food'], rect)def draw_score(self):"""绘制分数"""score_text = self.font.render(f"分数: {self.score}", True, self.config.colors['score'])high_score_text = self.small_font.render(f"最高分: {self.high_score}", True, self.config.colors['score'])self.screen.blit(score_text, (10, 10))self.screen.blit(high_score_text, (10, 50))def draw_start_menu(self):"""绘制开始菜单"""title = self.font.render("Python贪吃蛇", True, self.config.colors['text'])instruction = self.small_font.render("按 SPACE 开始游戏", True, self.config.colors['text'])controls = self.small_font.render("方向键控制移动,P键暂停", True, self.config.colors['text'])self.screen.blit(title, (self.config.screen_width // 2 - title.get_width() // 2, 200))self.screen.blit(instruction, (self.config.screen_width // 2 - instruction.get_width() // 2, 300))self.screen.blit(controls, (self.config.screen_width // 2 - controls.get_width() // 2, 350))def draw_pause_menu(self):"""绘制暂停菜单"""overlay = pygame.Surface((self.config.screen_width, self.config.screen_height))overlay.set_alpha(128)overlay.fill((0, 0, 0))self.screen.blit(overlay, (0, 0))pause_text = self.font.render("游戏暂停", True, self.config.colors['text'])instruction = self.small_font.render("按 P 继续游戏", True, self.config.colors['text'])self.screen.blit(pause_text, (self.config.screen_width // 2 - pause_text.get_width() // 2, 250))self.screen.blit(instruction, (self.config.screen_width // 2 - instruction.get_width() // 2, 300))def draw_game_over(self):"""绘制游戏结束画面"""overlay = pygame.Surface((self.config.screen_width, self.config.screen_height))overlay.set_alpha(128)overlay.fill((0, 0, 0))self.screen.blit(overlay, (0, 0))game_over_text = self.font.render("游戏结束!", True, self.config.colors['text'])score_text = self.font.render(f"最终分数: {self.score}", True, self.config.colors['score'])restart_text = self.small_font.render("按 R 重新开始,按 Q 退出", True, self.config.colors['text'])self.screen.blit(game_over_text, (self.config.screen_width // 2 - game_over_text.get_width() // 2, 200))self.screen.blit(score_text, (self.config.screen_width // 2 - score_text.get_width() // 2, 250))self.screen.blit(restart_text, (self.config.screen_width // 2 - restart_text.get_width() // 2, 300))def draw(self):"""绘制游戏画面"""self.screen.fill(self.config.colors['background'])if self.state == GameState.START_MENU:self.draw_start_menu()else:self.draw_grid()self.draw_snake()self.draw_food()self.draw_score()if self.state == GameState.PAUSED:self.draw_pause_menu()elif self.state == GameState.GAME_OVER:self.draw_game_over()pygame.display.flip()def run(self):"""运行游戏主循环"""running = Truewhile running:running = self.handle_events()self.update()self.draw()self.clock.tick(self.config.game_speed)pygame.quit()# 运行贪吃蛇游戏
def run_snake_game():"""运行贪吃蛇游戏"""print("🚀 启动贪吃蛇游戏...")game = SnakeGame()game.run()# 注意:在实际运行中取消注释下一行
# run_snake_game()
2.2 贪吃蛇游戏的核心算法
贪吃蛇游戏的核心在于移动算法和碰撞检测。让我们深入分析这些关键算法:
class SnakeAlgorithmAnalysis:"""贪吃蛇算法分析"""@staticmethoddef analyze_movement_algorithm():"""分析移动算法"""print("=== 贪吃蛇移动算法分析 ===")algorithms = {"链表移动法": ["在头部添加新节点","根据是否增长决定是否删除尾部","时间复杂度: O(1)","空间复杂度: O(n)"],"坐标追踪法": ["记录每个时间步的蛇身坐标","通过坐标序列重建蛇身","适合复杂移动模式","内存开销较大"]}for algorithm, details in algorithms.items():print(f"\n{algorithm}:")for detail in details:print(f" • {detail}")@staticmethoddef analyze_collision_detection():"""分析碰撞检测算法"""print("\n=== 碰撞检测算法分析 ===")collision_types = {"自碰撞检测": ["检查头部是否与身体其他部分重叠","时间复杂度: O(n)","使用集合优化可达到 O(1)"],"边界碰撞检测": ["检查头部是否超出边界","可配置为穿墙模式或游戏结束","时间复杂度: O(1)"],"食物碰撞检测": ["检查头部是否与食物坐标相同","最简单的碰撞检测","时间复杂度: O(1)"]}for collision_type, details in collision_types.items():print(f"\n{collision_type}:")for detail in details:print(f" • {detail}")@staticmethoddef demonstrate_optimized_collision():"""演示优化的碰撞检测"""print("\n=== 优化碰撞检测实现 ===")class OptimizedSnake:def __init__(self):self.body = [(5, 5), (5, 6), (5, 7)]self.body_set = set(self.body) # 使用集合加速碰撞检测def check_self_collision_optimized(self, new_head):"""使用集合优化的自碰撞检测"""# 移除尾部,因为新头部可能占据原尾部位置temp_set = self.body_set - {self.body[-1]}return new_head in temp_setdef move_optimized(self, new_head, grow=False):"""优化的移动方法"""# 更新集合self.body_set.discard(self.body[-1]) # 移除尾部self.body_set.add(new_head) # 添加新头部# 更新链表self.body.insert(0, new_head)if not grow:removed = self.body.pop()self.body_set.discard(removed)snake = OptimizedSnake()print("初始蛇身:", snake.body)print("初始集合:", snake.body_set)# 测试移动snake.move_optimized((5, 4))print("移动后蛇身:", snake.body)print("移动后集合:", snake.body_set)# 测试碰撞检测collision = snake.check_self_collision_optimized((5, 5))print(f"检测到自碰撞: {collision}")# 算法分析演示
algorithm_analysis = SnakeAlgorithmAnalysis()
algorithm_analysis.analyze_movement_algorithm()
algorithm_analysis.analyze_collision_detection()
algorithm_analysis.demonstrate_optimized_collision()
3. 扫雷游戏开发
3.1 游戏架构与算法设计
扫雷游戏的核心在于雷区生成、数字计算和区域展开算法。让我们设计扫雷游戏的完整架构:
import pygame
import random
from typing import List, Tuple, Set, Optional
from enum import Enumclass CellState(Enum):"""单元格状态枚举"""HIDDEN = 0 # 未揭开REVEALED = 1 # 已揭开FLAGGED = 2 # 标记为地雷QUESTION = 3 # 标记为问号class GameDifficulty(Enum):"""游戏难度枚举"""BEGINNER = (9, 9, 10) # 9x9, 10个雷INTERMEDIATE = (16, 16, 40) # 16x16, 40个雷EXPERT = (30, 16, 99) # 30x16, 99个雷class MinesweeperConfig:"""扫雷游戏配置类"""def __init__(self, difficulty: GameDifficulty = GameDifficulty.BEGINNER):self.difficulty = difficultyself.width, self.height, self.mine_count = difficulty.value# 显示配置self.cell_size = 30self.screen_width = self.width * self.cell_sizeself.screen_height = self.height * self.cell_size + 50 # 额外空间显示状态# 颜色配置self.colors = {'background': (192, 192, 192),'grid': (128, 128, 128),'hidden': (192, 192, 192),'revealed': (200, 200, 200),'text': (0, 0, 0),'flag': (255, 0, 0),'mine': (0, 0, 0),'numbers': [(0, 0, 255), # 1 - 蓝色(0, 128, 0), # 2 - 绿色(255, 0, 0), # 3 - 红色(0, 0, 128), # 4 - 深蓝(128, 0, 0), # 5 - 深红(0, 128, 128), # 6 - 青色(0, 0, 0), # 7 - 黑色(128, 128, 128) # 8 - 灰色]}class MinesweeperGame:"""扫雷游戏主类"""def __init__(self, difficulty: GameDifficulty = GameDifficulty.BEGINNER):pygame.init()self.config = MinesweeperConfig(difficulty)self.screen = pygame.display.set_mode((self.config.screen_width, self.config.screen_height))pygame.display.set_caption("Python扫雷")self.font = pygame.font.Font(None, 24)self.cell_font = pygame.font.Font(None, 20)self.clock = pygame.time.Clock()self.reset_game()def reset_game(self):"""重置游戏"""self.board = [[0 for _ in range(self.config.width)] for _ in range(self.config.height)]self.state_board = [[CellState.HIDDEN for _ in range(self.config.width)] for _ in range(self.config.height)]self.mines = set()self.game_over = Falseself.game_won = Falseself.first_click = Trueself.flags_placed = 0self.start_time = Noneself.elapsed_time = 0def generate_mines(self, first_click_pos: Tuple[int, int]):"""生成地雷,确保第一次点击不是地雷"""x, y = first_click_pos# 生成所有可能的位置,排除第一次点击的位置及其周围safe_zone = set()for dx in [-1, 0, 1]:for dy in [-1, 0, 1]:nx, ny = x + dx, y + dyif 0 <= nx < self.config.width and 0 <= ny < self.config.height:safe_zone.add((nx, ny))# 生成地雷位置all_positions = [(x, y) for x in range(self.config.width) for y in range(self.config.height)]safe_positions = [pos for pos in all_positions if pos not in safe_zone]self.mines = set(random.sample(safe_positions, self.config.mine_count))# 计算每个单元格周围的雷数for x, y in self.mines:self.board[y][x] = -1 # -1 表示地雷# 更新周围单元格的数字for dx in [-1, 0, 1]:for dy in [-1, 0, 1]:if dx == 0 and dy == 0:continuenx, ny = x + dx, y + dyif 0 <= nx < self.config.width and 0 <= ny < self.config.height:if self.board[ny][nx] != -1:self.board[ny][nx] += 1def reveal_cell(self, x: int, y: int):"""揭开单元格"""if not (0 <= x < self.config.width and 0 <= y < self.config.height):returnif self.state_board[y][x] != CellState.HIDDEN:return# 第一次点击生成地雷if self.first_click:self.generate_mines((x, y))self.first_click = Falseself.start_time = pygame.time.get_ticks()# 揭开当前单元格self.state_board[y][x] = CellState.REVEALED# 如果踩到地雷,游戏结束if (x, y) in self.mines:self.game_over = Trueself.reveal_all_mines()return# 如果揭开的是空白单元格,自动展开周围区域if self.board[y][x] == 0:self.expand_empty_area(x, y)# 检查是否获胜self.check_win_condition()def expand_empty_area(self, x: int, y: int):"""展开空白区域"""stack = [(x, y)]while stack:cx, cy = stack.pop()# 检查周围的8个方向for dx in [-1, 0, 1]:for dy in [-1, 0, 1]:if dx == 0 and dy == 0:continuenx, ny = cx + dx, cy + dyif (0 <= nx < self.config.width and 0 <= ny < self.config.height andself.state_board[ny][nx] == CellState.HIDDEN):self.state_board[ny][nx] = CellState.REVEALED# 如果周围单元格也是空白,继续展开if self.board[ny][nx] == 0:stack.append((nx, ny))def toggle_flag(self, x: int, y: int):"""切换标记状态"""if not (0 <= x < self.config.width and 0 <= y < self.config.height):returnif self.state_board[y][x] == CellState.HIDDEN:self.state_board[y][x] = CellState.FLAGGEDself.flags_placed += 1elif self.state_board[y][x] == CellState.FLAGGED:self.state_board[y][x] = CellState.QUESTIONself.flags_placed -= 1elif self.state_board[y][x] == CellState.QUESTION:self.state_board[y][x] = CellState.HIDDENdef reveal_all_mines(self):"""揭开所有地雷"""for x, y in self.mines:self.state_board[y][x] = CellState.REVEALEDdef check_win_condition(self):"""检查是否获胜"""for y in range(self.config.height):for x in range(self.config.width):# 如果还有非地雷单元格未揭开,游戏继续if (x, y) not in self.mines and self.state_board[y][x] != CellState.REVEALED:returnself.game_won = Trueself.game_over = Truedef get_remaining_mines(self) -> int:"""获取剩余地雷数"""return self.config.mine_count - self.flags_placeddef get_elapsed_time(self) -> int:"""获取游戏经过时间(秒)"""if self.start_time is None:return 0if self.game_over:return self.elapsed_timecurrent_time = pygame.time.get_ticks()self.elapsed_time = (current_time - self.start_time) // 1000return self.elapsed_timedef handle_events(self):"""处理游戏事件"""for event in pygame.event.get():if event.type == pygame.QUIT:return Falseelif event.type == pygame.KEYDOWN:if event.key == pygame.K_r:self.reset_game()elif event.key == pygame.K_q:return Falseelif event.type == pygame.MOUSEBUTTONDOWN and not self.game_over:x, y = event.poscell_x = x // self.config.cell_sizecell_y = y // self.config.cell_sizeif event.button == 1: # 左键揭开self.reveal_cell(cell_x, cell_y)elif event.button == 3: # 右键标记self.toggle_flag(cell_x, cell_y)return Truedef draw_cell(self, x: int, y: int):"""绘制单个单元格"""cell_rect = pygame.Rect(x * self.config.cell_size,y * self.config.cell_size,self.config.cell_size,self.config.cell_size)state = self.state_board[y][x]cell_value = self.board[y][x]# 绘制单元格背景if state == CellState.HIDDEN or state == CellState.QUESTION:pygame.draw.rect(self.screen, self.config.colors['hidden'], cell_rect)pygame.draw.rect(self.screen, self.config.colors['grid'], cell_rect, 1)if state == CellState.QUESTION:text = self.cell_font.render("?", True, self.config.colors['text'])text_rect = text.get_rect(center=cell_rect.center)self.screen.blit(text, text_rect)elif state == CellState.FLAGGED:pygame.draw.rect(self.screen, self.config.colors['hidden'], cell_rect)pygame.draw.rect(self.screen, self.config.colors['grid'], cell_rect, 1)# 绘制旗帜flag_color = self.config.colors['flag']points = [(cell_rect.left + 5, cell_rect.top + 5),(cell_rect.left + 5, cell_rect.bottom - 5),(cell_rect.right - 5, cell_rect.centery)]pygame.draw.polygon(self.screen, flag_color, points)elif state == CellState.REVEALED:pygame.draw.rect(self.screen, self.config.colors['revealed'], cell_rect)pygame.draw.rect(self.screen, self.config.colors['grid'], cell_rect, 1)if cell_value == -1: # 地雷mine_rect = pygame.Rect(cell_rect.left + 5,cell_rect.top + 5,cell_rect.width - 10,cell_rect.height - 10)pygame.draw.ellipse(self.screen, self.config.colors['mine'], mine_rect)elif cell_value > 0: # 数字color = self.config.colors['numbers'][cell_value - 1]text = self.cell_font.render(str(cell_value), True, color)text_rect = text.get_rect(center=cell_rect.center)self.screen.blit(text, text_rect)def draw_status_bar(self):"""绘制状态栏"""status_rect = pygame.Rect(0, self.config.height * self.config.cell_size, self.config.screen_width, 50)pygame.draw.rect(self.screen, (200, 200, 200), status_rect)# 显示剩余地雷数mines_text = self.font.render(f"剩余地雷: {self.get_remaining_mines()}", True, self.config.colors['text'])self.screen.blit(mines_text, (10, status_rect.top + 10))# 显示时间time_text = self.font.render(f"时间: {self.get_elapsed_time()}秒", True, self.config.colors['text'])self.screen.blit(time_text, (self.config.screen_width - 120, status_rect.top + 10))# 显示游戏状态if self.game_over:if self.game_won:status_text = self.font.render("恭喜获胜!按 R 重新开始", True, (0, 128, 0))else:status_text = self.font.render("游戏结束!按 R 重新开始", True, (255, 0, 0))else:status_text = self.font.render("进行中...", True, self.config.colors['text'])status_rect_center = status_text.get_rect(center=(self.config.screen_width // 2, status_rect.top + 25))self.screen.blit(status_text, status_rect_center)def draw(self):"""绘制游戏画面"""self.screen.fill(self.config.colors['background'])# 绘制所有单元格for y in range(self.config.height):for x in range(self.config.width):self.draw_cell(x, y)# 绘制状态栏self.draw_status_bar()pygame.display.flip()def run(self):"""运行游戏主循环"""running = Truewhile running:running = self.handle_events()self.draw()self.clock.tick(30)pygame.quit()# 运行扫雷游戏
def run_minesweeper_game(difficulty: GameDifficulty = GameDifficulty.BEGINNER):"""运行扫雷游戏"""print(f"🚀 启动扫雷游戏 - 难度: {difficulty.name}")game = MinesweeperGame(difficulty)game.run()# 注意:在实际运行中取消注释下一行
# run_minesweeper_game(GameDifficulty.BEGINNER)
3.2 扫雷游戏的核心算法
扫雷游戏的核心算法包括雷区生成、数字计算和区域展开。让我们深入分析这些算法:
class MinesweeperAlgorithmAnalysis:"""扫雷算法分析"""@staticmethoddef analyze_mine_generation_algorithms():"""分析地雷生成算法"""print("=== 地雷生成算法分析 ===")algorithms = {"随机采样法": ["从所有可能位置中随机选择","确保第一次点击安全","时间复杂度: O(m),m为地雷数量","实现简单,效果良好"],"分层生成法": ["先确保安全区域,再生成地雷","可以控制地雷分布密度","适合生成特定模式","实现相对复杂"],"权重分布法": ["为不同区域分配不同权重","可以创建更有挑战性的布局","需要额外的权重计算","适合高级难度"]}for algorithm, details in algorithms.items():print(f"\n{algorithm}:")for detail in details:print(f" • {detail}")@staticmethoddef analyze_number_calculation():"""分析数字计算算法"""print("\n=== 数字计算算法分析 ===")calculation_methods = {"遍历统计法": ["为每个地雷更新周围8个格子的计数","时间复杂度: O(8 × m),m为地雷数量","实现简单直接","内存访问局部性好"],"卷积核法": ["使用3x3卷积核统计周围地雷","适合并行计算","需要额外的矩阵操作","在某些语言中性能更好"]}for method, details in calculation_methods.items():print(f"\n{method}:")for detail in details:print(f" • {detail}")@staticmethoddef analyze_area_expansion():"""分析区域展开算法"""print("\n=== 区域展开算法分析 ===")expansion_algorithms = {"深度优先搜索(DFS)": ["使用递归或栈实现","可能栈溢出(递归深度过大)","实现相对简单","适合小规模游戏"],"广度优先搜索(BFS)": ["使用队列实现","不会栈溢出","展开顺序更自然","推荐使用的方法"],"迭代展开法": ["多次遍历直到没有新单元格可展开","实现简单但效率较低","适合教学目的","不推荐在生产环境使用"]}for algorithm, details in expansion_algorithms.items():print(f"\n{algorithm}:")for detail in details:print(f" • {detail}")@staticmethoddef demonstrate_bfs_expansion():"""演示BFS区域展开"""print("\n=== BFS区域展开演示 ===")class BFSDemo:def __init__(self, width=5, height=5):self.width = widthself.height = heightself.board = [[0 for _ in range(width)] for _ in range(height)]self.revealed = [[False for _ in range(width)] for _ in range(height)]# 设置一些示例数字self.board[1][1] = 1self.board[1][3] = 2self.board[3][2] = 1def expand_area_bfs(self, start_x, start_y):"""使用BFS展开区域"""if self.board[start_y][start_x] != 0:returnqueue = [(start_x, start_y)]visited = set()while queue:x, y = queue.pop(0)if (x, y) in visited:continuevisited.add((x, y))self.revealed[y][x] = True# 只在当前单元格为0时继续展开if self.board[y][x] == 0:for dx in [-1, 0, 1]:for dy in [-1, 0, 1]:if dx == 0 and dy == 0:continuenx, ny = x + dx, y + dyif (0 <= nx < self.width and 0 <= ny < self.height andnot self.revealed[ny][nx] and (nx, ny) not in visited):queue.append((nx, ny))def print_board(self):"""打印棋盘状态"""print("棋盘状态:")for y in range(self.height):row = []for x in range(self.width):if self.revealed[y][x]:row.append(str(self.board[y][x]))else:row.append('■')print(' '.join(row))demo = BFSDemo()print("展开前:")demo.print_board()demo.expand_area_bfs(2, 2)print("\n从(2,2)展开后:")demo.print_board()# 扫雷算法分析演示
minesweeper_analysis = MinesweeperAlgorithmAnalysis()
minesweeper_analysis.analyze_mine_generation_algorithms()
minesweeper_analysis.analyze_number_calculation()
minesweeper_analysis.analyze_area_expansion()
minesweeper_analysis.demonstrate_bfs_expansion()
4. 完整代码实现与优化
4.1 完整的游戏启动器
让我们创建一个统一的游戏启动器,让玩家可以选择玩哪个游戏:
#!/usr/bin/env python3
"""
game_launcher.py
经典游戏启动器 - 统一启动贪吃蛇和扫雷游戏
"""import pygame
import sys
from typing import List, Tuple, Callableclass GameLauncher:"""游戏启动器"""def __init__(self):pygame.init()self.screen_width = 800self.screen_height = 600self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))pygame.display.set_caption("Python经典游戏合集")self.clock = pygame.time.Clock()self.font_large = pygame.font.Font(None, 64)self.font_medium = pygame.font.Font(None, 36)self.font_small = pygame.font.Font(None, 24)self.colors = {'background': (30, 30, 46),'title': (137, 180, 250),'menu_item': (203, 166, 247),'menu_selected': (245, 194, 231),'description': (166, 227, 161)}self.games = [{'name': '贪吃蛇','description': '经典贪吃蛇游戏,使用方向键控制','action': self.launch_snake_game},{'name': '扫雷 - 初级','description': '9x9网格,10个地雷','action': lambda: self.launch_minesweeper_game(GameDifficulty.BEGINNER)},{'name': '扫雷 - 中级', 'description': '16x16网格,40个地雷','action': lambda: self.launch_minesweeper_game(GameDifficulty.INTERMEDIATE)},{'name': '扫雷 - 高级','description': '30x16网格,99个地雷', 'action': lambda: self.launch_minesweeper_game(GameDifficulty.EXPERT)}]self.selected_index = 0def draw_menu(self):"""绘制菜单界面"""self.screen.fill(self.colors['background'])# 绘制标题title = self.font_large.render("Python经典游戏合集", True, self.colors['title'])title_rect = title.get_rect(center=(self.screen_width // 2, 80))self.screen.blit(title, title_rect)# 绘制游戏选项for i, game in enumerate(self.games):color = self.colors['menu_selected'] if i == self.selected_index else self.colors['menu_item']# 游戏名称name_text = self.font_medium.render(game['name'], True, color)name_rect = name_text.get_rect(center=(self.screen_width // 2, 180 + i * 80))self.screen.blit(name_text, name_rect)# 游戏描述desc_text = self.font_small.render(game['description'], True, self.colors['description'])desc_rect = desc_text.get_rect(center=(self.screen_width // 2, 210 + i * 80))self.screen.blit(desc_text, desc_rect)# 绘制操作提示controls_text = self.font_small.render("↑↓键选择游戏,ENTER键开始,ESC键退出", True, self.colors['description'])controls_rect = controls_text.get_rect(center=(self.screen_width // 2, self.screen_height - 50))self.screen.blit(controls_text, controls_rect)pygame.display.flip()def handle_events(self) -> bool:"""处理菜单事件"""for event in pygame.event.get():if event.type == pygame.QUIT:return Falseelif event.type == pygame.KEYDOWN:if event.key == pygame.K_ESCAPE:return Falseelif event.key == pygame.K_UP:self.selected_index = (self.selected_index - 1) % len(self.games)elif event.key == pygame.K_DOWN:self.selected_index = (self.selected_index + 1) % len(self.games)elif event.key == pygame.K_RETURN:self.games[self.selected_index]['action']()return Truedef launch_snake_game(self):"""启动贪吃蛇游戏"""print("启动贪吃蛇游戏...")# 这里应该导入并运行贪吃蛇游戏# 在实际实现中,需要导入SnakeGame类并运行try:# 假设SnakeGame类在同一个文件中from __main__ import SnakeGamesnake_game = SnakeGame()snake_game.run()except ImportError:print("贪吃蛇游戏模块未找到")def launch_minesweeper_game(self, difficulty):"""启动扫雷游戏"""print(f"启动扫雷游戏 - 难度: {difficulty.name}")# 这里应该导入并运行扫雷游戏# 在实际实现中,需要导入MinesweeperGame类并运行try:# 假设MinesweeperGame类在同一个文件中from __main__ import MinesweeperGame, GameDifficultyminesweeper_game = MinesweeperGame(difficulty)minesweeper_game.run()except ImportError:print("扫雷游戏模块未找到")def run(self):"""运行启动器"""running = Truewhile running:running = self.handle_events()self.draw_menu()self.clock.tick(30)pygame.quit()sys.exit()# 运行游戏启动器
def main():"""主函数"""print("🎮 启动Python经典游戏合集...")launcher = GameLauncher()launcher.run()if __name__ == "__main__":main()
4.2 性能优化与代码质量
为了确保游戏运行流畅且代码质量高,我们需要进行性能优化和代码质量检查:
class GameOptimization:"""游戏优化工具类"""@staticmethoddef performance_optimization_tips():"""性能优化建议"""print("=== 游戏性能优化建议 ===")tips = {"渲染优化": ["只重绘发生变化的部分(脏矩形技术)","使用双缓冲避免闪烁","预渲染静态元素","使用精灵表减少绘制调用"],"内存优化": ["重用对象而不是频繁创建销毁","使用适当的数据结构","及时释放不再需要的资源","使用对象池管理频繁创建的对象"],"算法优化": ["使用空间换时间策略","避免在游戏循环中进行复杂计算","使用高效的碰撞检测算法","批量处理相似操作"],"Pygame特定优化": ["使用convert()优化Surface","合理使用set_colorkey()","避免频繁的Surface创建","使用子表面(Subsurface)共享像素数据"]}for category, items in tips.items():print(f"\n{category}:")for item in items:print(f" • {item}")@staticmethoddef code_quality_guidelines():"""代码质量指南"""print("\n=== 游戏代码质量指南 ===")guidelines = {"架构设计": ["遵循单一职责原则","使用面向对象设计","分离游戏逻辑和渲染逻辑","使用配置类管理游戏参数"],"代码组织": ["合理划分模块和包","使用常量代替魔法数字","编写清晰的文档字符串","保持函数简短专注"],"错误处理": ["适当的异常处理","输入验证和边界检查","游戏状态的一致性检查","有意义的错误信息"],"测试策略": ["单元测试核心算法","集成测试游戏流程","性能测试关键路径","用户界面测试"]}for category, items in guidelines.items():print(f"\n{category}:")for item in items:print(f" • {item}")@staticmethoddef demonstrate_surface_optimization():"""演示Surface优化"""print("\n=== Surface优化演示 ===")import pygame# 初始化Pygamepygame.init()screen = pygame.display.set_mode((100, 100))# 未优化的Surface创建unoptimized_surface = pygame.Surface((50, 50))# 优化的Surface创建optimized_surface = pygame.Surface((50, 50)).convert()optimized_surface.set_colorkey((0, 0, 0)) # 设置透明色print("✅ Surface优化完成")pygame.quit()# 优化演示
optimization = GameOptimization()
optimization.performance_optimization_tips()
optimization.code_quality_guidelines()
optimization.demonstrate_surface_optimization()
5. 游戏设计模式与架构
5.1 游戏开发中的设计模式
在游戏开发中,合理使用设计模式可以提高代码的可维护性和扩展性:
from abc import ABC, abstractmethod
from typing import Listclass GameStatePattern:"""游戏状态模式演示"""class GameState(ABC):"""游戏状态基类"""@abstractmethoddef handle_input(self, game, event):pass@abstractmethoddef update(self, game):pass@abstractmethoddef render(self, game):passclass MenuState(GameState):"""菜单状态"""def handle_input(self, game, event):if event.type == pygame.KEYDOWN:if event.key == pygame.K_SPACE:game.change_state(GameStatePattern.PlayingState())def update(self, game):passdef render(self, game):game.screen.fill((0, 0, 0))font = pygame.font.Font(None, 36)text = font.render("按SPACE开始游戏", True, (255, 255, 255))game.screen.blit(text, (100, 100))class PlayingState(GameState):"""游戏进行状态"""def handle_input(self, game, event):if event.type == pygame.KEYDOWN:if event.key == pygame.K_ESCAPE:game.change_state(GameStatePattern.PauseState())def update(self, game):# 游戏逻辑更新passdef render(self, game):game.screen.fill((0, 0, 0))# 渲染游戏内容passclass PauseState(GameState):"""暂停状态"""def handle_input(self, game, event):if event.type == pygame.KEYDOWN:if event.key == pygame.K_ESCAPE:game.change_state(GameStatePattern.PlayingState())def update(self, game):passdef render(self, game):# 半透明覆盖层overlay = pygame.Surface(game.screen.get_size())overlay.set_alpha(128)overlay.fill((0, 0, 0))game.screen.blit(overlay, (0, 0))font = pygame.font.Font(None, 36)text = font.render("游戏暂停 - 按ESC继续", True, (255, 255, 255))game.screen.blit(text, (100, 100))class ObserverPattern:"""观察者模式演示 - 用于游戏事件系统"""class Observer(ABC):@abstractmethoddef on_event(self, event_type, data):passclass Subject:def __init__(self):self.observers: List[ObserverPattern.Observer] = []def add_observer(self, observer):self.observers.append(observer)def remove_observer(self, observer):self.observers.remove(observer)def notify_observers(self, event_type, data=None):for observer in self.observers:observer.on_event(event_type, data)class ScoreManager(Observer):"""分数管理器 - 观察游戏事件"""def __init__(self):self.score = 0def on_event(self, event_type, data):if event_type == "food_eaten":self.score += 10print(f"得分: {self.score}")elif event_type == "game_over":print(f"游戏结束! 最终得分: {self.score}")# 设计模式使用总结
def summarize_design_patterns():"""总结游戏开发中的设计模式"""patterns = {"状态模式 (State Pattern)": ["管理游戏的不同状态(菜单、进行中、暂停等)","每个状态有自己的输入处理、更新和渲染逻辑","使状态转换更加清晰"],"观察者模式 (Observer Pattern)": ["实现游戏事件系统","解耦事件产生者和处理者","方便添加新的游戏功能"],"单例模式 (Singleton Pattern)": ["管理游戏配置、资源管理器等","确保全局唯一实例","方便全局访问"],"工厂模式 (Factory Pattern)": ["创建游戏对象(敌人、道具等)","封装对象创建逻辑","支持扩展新的对象类型"],"组件模式 (Component Pattern)": ["构建灵活的游戏对象系统","通过组合而非继承实现功能","提高代码复用性"]}print("=== 游戏开发设计模式总结 ===")for pattern, uses in patterns.items():print(f"\n{pattern}:")for use in uses:print(f" • {use}")# 运行设计模式总结
summarize_design_patterns()
6. 总结与扩展
6.1 学习成果总结
通过完成这两个经典游戏的复刻,我们已经掌握了:
class LearningAchievements:"""学习成果总结"""@staticmethoddef get_achievements():"""获取学习成果列表"""achievements = {"Python编程基础": ["面向对象编程(类、继承、封装)","数据结构使用(列表、字典、集合)","算法实现(搜索、碰撞检测)","异常处理和调试"],"游戏开发核心概念": ["游戏循环和状态管理","用户输入处理","图形渲染和动画","碰撞检测系统"],"Pygame库使用": ["Surface和Rect操作","事件处理系统","字体和文本渲染","时间管理和帧率控制"],"软件工程实践": ["代码组织和模块化","配置管理和常量定义","代码重构和优化","文档和注释编写"]}return achievements@staticmethoddef suggest_next_steps():"""建议下一步学习方向"""next_steps = {"游戏类型扩展": ["添加音效和背景音乐","实现游戏存档功能","添加更多游戏特效","支持游戏手柄输入"],"技术深度扩展": ["学习使用PyOpenGL进行3D渲染","探索网络编程实现多人游戏","学习人工智能算法实现游戏AI","研究物理引擎集成"],"项目复杂度提升": ["开发更复杂的RPG游戏","实现平台跳跃游戏","创建策略游戏或模拟游戏","开发手机游戏版本"],"工程化实践": ["学习版本控制(Git)","掌握单元测试和持续集成","学习性能分析和优化","了解游戏发布和打包"]}return next_steps# 学习成果展示
achievements = LearningAchievements()
learning_achievements = achievements.get_achievements()
next_steps = achievements.suggest_next_steps()print("=== 学习成果总结 ===")
for category, skills in learning_achievements.items():print(f"\n{category}:")for skill in skills:print(f" ✅ {skill}")print("\n=== 下一步学习建议 ===")
for category, suggestions in next_steps.items():print(f"\n{category}:")for suggestion in suggestions:print(f" 🎯 {suggestion}")
6.2 完整项目架构图
6.3 最终建议
通过复刻这两个经典游戏,你已经踏入了游戏开发的精彩世界。记住:
- 实践是最好的老师 - 不断修改和扩展你的游戏
- 理解优于记忆 - 深入理解每个算法的工作原理
- 代码质量很重要 - 保持代码的清晰和可维护性
- 享受创造的过程 - 游戏开发应该是充满乐趣的
这两个游戏虽然简单,但包含了游戏开发的核心概念。你可以基于这个基础,继续探索更复杂的游戏开发技术,创造属于自己的游戏世界!
本教程通过完整的代码实现和详细的技术分析,带你从零开始复刻了两个经典游戏。希望这个学习过程不仅让你掌握了具体的编程技能,更激发了你对游戏开发的热情和创造力。编程之路漫长而精彩,继续前进吧!
