import pygame import time import random import json import traceback from maze_generator import MazeGenerator from SourceCollector import SourceCollector from tanxin import GreedyCollector from simple_save_manager import simple_save_manager from config import UI_WIDTH import time import random import json INPUT_DATA = "./saves/input/input_case_2.json" class Maze: def __init__(self, wall_size, maze_size, file_name): self.wall_size = wall_size self.maze_size = maze_size self.size = int(maze_size / wall_size) self.file_name = file_name # 动态显示尺寸(会根据实际地图大小调整) self.actual_display_size = maze_size self.actual_wall_size = wall_size self.grid = [] self.generater = MazeGenerator(self.size, self.file_name) self.source_collector = None self.path_step = 0 # 当前显示到路径的第几步 self.full_path = [] # 完整路径 self.is_path_complete = False # 路径是否完全显示 # 历史迭代展示相关 self.history_mazes = [] # 迷宫生成历史 self.history_step = 0 # 当前历史步骤 self.show_history = False # 是否正在展示历史 self.history_auto_play = False # 历史自动播放 self.history_timer = 0 # 历史播放计时器 # Boss战斗相关 self.boss_data = [] # Boss血量序列 self.player_skills = [] # 玩家技能序列 # 已遇到的Boss和机关位置记录(避免重复触发) self.encountered_bosses = set() # 已遇到的Boss位置 (y, x) self.encountered_mechanisms = set() # 已遇到的机关位置 (y, x) def update_display_size(self): """根据当前迷宫大小更新显示尺寸""" if len(self.grid) > 0: self.size = len(self.grid) # 计算合适的墙壁大小,确保迷宫不会太大或太小 max_display_size = min(800, UI_WIDTH - 400) # 留出400像素给控制面板 min_wall_size = 20 max_wall_size = 60 # 根据迷宫大小计算墙壁尺寸 ideal_wall_size = max_display_size // self.size self.actual_wall_size = max(min_wall_size, min(max_wall_size, ideal_wall_size)) self.actual_display_size = self.size * self.actual_wall_size print(f"迷宫大小: {self.size}x{self.size}, 墙壁尺寸: {self.actual_wall_size}, 显示尺寸: {self.actual_display_size}") def get_actual_display_size(self): """获取当前实际显示尺寸""" return self.actual_display_size def generate(self): # 重置所有状态 self.reset_all_states() # 生成新迷宫 seed = int(time.time() * 1000) % (2**32) self.generater.generate(seed=seed) # 获取生成历史 self.history_mazes = self.generater.get_history_mazes() # 生成路径 self.source_collector = SourceCollector(maze=self.generater.maze) self.source_collector.run() self.full_path = self.source_collector.get_path() # 在路径生成完成后,在路径上放置Boss if self.full_path and len(self.full_path) > 2: placed_bosses = self.generater.place_boss_on_path(self.full_path, boss_count=1) if placed_bosses: print(f"已在路径上放置 {len(placed_bosses)} 个Boss") try: with open(INPUT_DATA, 'r', encoding='utf-8') as f: file = json.load(f) # 设置Boss数据和玩家技能(如果存在的话) if 'B' in file and 'PlayerSkills' in file: self.boss_data = list(file['B']) self.player_skills = [list(skill) for skill in file['PlayerSkills']] print(f"Boss数据: {self.boss_data}") print(f"玩家技能: {self.player_skills}") else: print(f"警告:输入文件 {INPUT_DATA} 中没有Boss数据('B')或玩家技能('PlayerSkills'),Boss战斗功能将不可用") self.boss_data = [] self.player_skills = [] except Exception as e: print(f"读取Boss数据文件失败: {e}") self.boss_data = [] self.player_skills = [] # 在路径上放置机关(只放置1个) placed_mechanisms = self.generater.place_mechanisms_on_path(self.full_path, mechanisms_count=1) if placed_mechanisms: print(f"已在路径上放置 {len(placed_mechanisms)} 个机关") # 设置显示状态 self.grid = self.generater.maze # 使用原始迷宫数据 print(self.grid) self.update_display_size() # 更新显示尺寸 print(f"路径长度: {len(self.full_path)}") print(f"生成历史步数: {len(self.history_mazes)}") print("新迷宫生成完成") def next_path_step(self): """显示路径的下一步""" if self.path_step < len(self.full_path): # 获取当前要显示的位置 current_y, current_x = self.full_path[self.path_step] current_position = (current_y, current_x) # 检查当前位置是否有boss或机关 boss_encountered = False mechanism_encountered = False boss_info = None mechanism_info = None if self.generater.maze: current_cell = str(self.generater.maze[current_y][current_x]) # 检查Boss(只有在之前没有遇到过这个位置的Boss时才触发) if current_cell.lower().startswith('b') and current_position not in self.encountered_bosses: boss_encountered = True self.encountered_bosses.add(current_position) # 记录已遇到的Boss位置 # 如果有boss数据和玩家技能数据,创建boss信息 if self.boss_data and self.player_skills: boss_info = { 'boss_data': self.boss_data, 'player_skills': self.player_skills } print(f"遇到Boss!位置: ({current_y}, {current_x}), Boss数据: {self.boss_data}, 玩家技能: {self.player_skills}") else: print(f"遇到Boss但缺少战斗数据!Boss数据: {self.boss_data}, 玩家技能: {self.player_skills}") boss_info = None # 检查机关(只有在之前没有遇到过这个位置的机关时才触发) elif current_cell.lower().startswith('l') and current_position not in self.encountered_mechanisms: mechanism_encountered = True self.encountered_mechanisms.add(current_position) # 记录已遇到的机关位置 mechanism_info = { 'position': (current_y, current_x), 'cell_value': current_cell } print(f"遇到机关!位置: ({current_y}, {current_x}), 机关值: {current_cell}") self.path_step += 1 self.update_grid_with_path() # 返回是否有下一步、是否遇到boss、boss信息、是否遇到机关、机关信息 return True, boss_encountered, boss_info, mechanism_encountered, mechanism_info return False, False, None, False, None def reset_path(self): """重置路径显示""" self.path_step = 0 self.is_path_complete = False self.grid = self.generater.maze if self.generater.maze else [] # 重置已遇到的Boss和机关记录,允许重新体验 self.encountered_bosses = set() self.encountered_mechanisms = set() print("路径重置,Boss和机关遭遇记录已清除") def auto_advance_path(self): """自动推进路径显示""" if not self.is_path_complete: has_next, boss_encountered, boss_info = self.next_path_step() if not has_next: self.is_path_complete = True # 注意:这里不处理boss遭遇,因为这个方法可能不在主循环中调用 return boss_encountered, boss_info return False, None def update_grid_with_path(self): """根据当前步数更新网格显示""" if not self.full_path or not self.generater.maze: return # 从原始迷宫开始 self.grid = [row[:] for row in self.generater.maze] # 深拷贝 # 只显示到当前步数的路径 for idx in range(min(self.path_step, len(self.full_path))): y, x = self.full_path[idx] if self.grid[y][x].startswith('s') or self.grid[y][x].startswith('e'): continue if self.grid[y][x].startswith('g') or self.grid[y][x].startswith('t'): self.grid[y][x] = f"{self.grid[y][x]}p{idx}" continue self.grid[y][x] = f"p{idx}" def export_to_csv(self, filename): """导出迷宫到CSV文件(兼容旧版本)""" self.generater.export_to_csv(filename=filename) def save_game(self, save_name=None, format_type="json"): """保存包含路径信息的迷宫到文件""" if len(self.grid) == 0: print("没有生成迷宫,无法保存") return None return simple_save_manager.save_maze_with_path(self, save_name, format_type) def load_game(self, save_file): """从存档文件加载游戏状态(支持JSON和CSV格式)""" load_data = simple_save_manager.load_maze_from_file(save_file) if load_data is None: return False try: # 恢复迷宫数据 self.generater.maze = load_data['original_grid'] self.size = len(load_data['original_grid']) # 恢复路径数据 self.full_path = load_data['path_sequence'] self.path_step = len(self.full_path) # 显示完整路径 self.is_path_complete = True # 重新创建SourceCollector以便后续操作 self.source_collector = SourceCollector(maze=self.generater.maze) self.source_collector.run() # 运行路径计算和资源统计 # 使用包含路径信息的网格作为显示网格 self.grid = load_data['path_grid'] self.update_display_size() # 更新显示尺寸 # 加载Boss数据和玩家技能 self.boss_data = load_data.get('boss_data', []) self.player_skills = load_data.get('player_skills', []) # 读档时不展示历史迭代(清空历史数据) self.history_mazes = [] self.history_step = 0 self.show_history = False file_format = load_data.get('format', '未知') print(f"成功加载游戏状态 ({file_format}格式),路径长度: {len(self.full_path)}") if self.boss_data: print(f"Boss数据: {self.boss_data}") if self.player_skills: print(f"玩家技能: {self.player_skills}") print(f"当前显示完整路径") return True except Exception as e: print(f"加载游戏状态失败: {str(e)}") return False def get_save_list(self): """获取所有可用的存档列表""" return simple_save_manager.get_save_list() def draw(self, screen, wall_texture, coin_texture, trap_texture, boss_texture, lock_texture): if len(self.grid) == 0: return # 使用动态计算的墙壁尺寸 tile_size = self.actual_wall_size # 根据需要缩放贴图 if wall_texture.get_width() != tile_size: wall_texture = pygame.transform.scale(wall_texture, (tile_size, tile_size)) coin_texture = pygame.transform.scale(coin_texture, (tile_size, tile_size)) trap_texture = pygame.transform.scale(trap_texture, (tile_size, tile_size)) boss_texture = pygame.transform.scale(boss_texture, (tile_size, tile_size)) lock_texture = pygame.transform.scale(lock_texture, (tile_size, tile_size)) for y in range(self.size): for x in range(self.size): if self.grid[y][x] == '1': screen.blit(wall_texture, (x * tile_size, y * tile_size)) continue if self.grid[y][x].startswith('b'): screen.blit(boss_texture, (x * tile_size, y * tile_size)) continue if self.grid[y][x].startswith('l'): screen.blit(lock_texture, (x * tile_size, y * tile_size)) continue if self.grid[y][x].startswith('g'): screen.blit(coin_texture, (x * tile_size, y * tile_size)) font = pygame.font.SysFont(None, tile_size // 2) path = self.grid[y][x].rfind('p') if path == -1: continue path = self.grid[y][x][path+1:] center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 text = font.render(path, True, (255, 255, 255)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) if self.grid[y][x].startswith('t'): screen.blit(trap_texture, (x * tile_size, y * tile_size)) font = pygame.font.SysFont(None, tile_size // 2) path = self.grid[y][x].rfind('p') if path == -1: continue path = self.grid[y][x][path+1:] center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 text = font.render(path, True, (255, 255, 255)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) if self.grid[y][x].startswith('|') or self.grid[y][x].startswith('-'): font = pygame.font.SysFont(None, tile_size // 2) num = int(self.grid[y][x][1:]) center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 pygame.draw.circle(screen, (255, 215, 0), center, radius) if num: text = font.render(str(num), True, (0, 0, 0)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) continue if self.grid[y][x].startswith('s'): font = pygame.font.SysFont(None, tile_size // 2) text = "s" center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 pygame.draw.circle(screen, (255, 215, 0), center, radius) if text: text = font.render(text, True, (0, 0, 0)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) continue if self.grid[y][x].startswith('e'): font = pygame.font.SysFont(None, tile_size // 2) text = "e" center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 pygame.draw.circle(screen, (255, 215, 0), center, radius) if text: text = font.render(text, True, (0, 0, 0)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) if self.grid[y][x].startswith('p'): font = pygame.font.SysFont(None, tile_size // 2) text = self.grid[y][x][1:] center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) radius = tile_size // 3 pygame.draw.circle(screen, (255, 215, 0), center, radius) if text: text = font.render(text, True, (0, 0, 0)) text_rect = text.get_rect(center=center) screen.blit(text, text_rect) # 绘制迷宫边界线(动态位置) pygame.draw.line(screen, (0, 0, 0), (self.actual_display_size, 0), (self.actual_display_size, self.actual_display_size), 3) # 绘制贪心路径 if hasattr(self, 'greedy_path') and self.greedy_path and hasattr(self, 'greedy_step'): self._draw_greedy_path(screen, tile_size) def toggle_history_mode(self): """切换历史展示模式""" if len(self.history_mazes) > 0: self.show_history = not self.show_history if self.show_history: # 切换到历史模式,显示当前历史步骤 self.update_grid_with_history() else: # 切换到路径模式,显示当前路径步骤 self.update_grid_with_path() print(f"切换到{'历史' if self.show_history else '路径'}模式") def next_history_step(self): """显示历史的下一步""" if self.history_step < len(self.history_mazes) - 1: self.history_step += 1 self.update_grid_with_history() return True return False def prev_history_step(self): """显示历史的上一步""" if self.history_step > 0: self.history_step -= 1 self.update_grid_with_history() return True return False def update_grid_with_history(self): """根据当前历史步数更新网格显示""" if not self.history_mazes or self.history_step >= len(self.history_mazes): return # 显示指定历史步骤的迷宫状态 self.grid = [row[:] for row in self.history_mazes[self.history_step]] # 深拷贝 def reset_all_states(self): """重置所有状态(用于生成新迷宫时)""" # 历史相关状态 self.history_mazes = [] self.history_step = 0 self.show_history = False self.history_auto_play = False self.history_timer = 0 # 路径相关状态 self.path_step = 0 self.is_path_complete = False self.full_path = [] # 重置已遇到的Boss和机关记录 self.encountered_bosses = set() self.encountered_mechanisms = set() # 重置贪心算法相关状态 if hasattr(self, 'greedy_path'): delattr(self, 'greedy_path') if hasattr(self, 'greedy_result'): delattr(self, 'greedy_result') if hasattr(self, 'greedy_step'): delattr(self, 'greedy_step') if hasattr(self, 'is_greedy_path_complete'): delattr(self, 'is_greedy_path_complete') # 显示相关状态 self.grid = [] print("所有状态已重置") def run_greedy_search(self): """运行3x3视野贪心搜索算法""" if not self.grid: print("没有迷宫数据,无法运行贪心搜索") return False try: # 使用tanxin.py中的GreedyCollector类(包含3x3视野贪心策略、死胡同检测和回溯功能) # 创建贪心算法实例 collector = GreedyCollector(maze=self.grid) # 运行算法 result = collector.run_3x3_greedy_collection() # 将结果转换为路径格式 (y, x) # 注意:tanxin.py中的路径是(x, y)格式,而maze.py中使用(y, x)格式 self.greedy_path = [(y, x) for (x, y) in result['path']] # 转换收集资源格式 resources = [] for resource in result['collected_resources']: x, y = resource['position'] resources.append({ 'position': (x, y), # 保持(x, y)格式以兼容_draw_greedy_path方法 'type': resource['type'], 'value': resource['value'] }) # 更新结果 result_formatted = { 'path_yx_format': self.greedy_path, 'collected_resources': resources, 'total_value': result['total_value'], 'total_moves': result['total_moves'], 'resources_count': result['resources_count'] } self.greedy_result = result_formatted self.greedy_step = 0 self.is_greedy_path_complete = False print(f"贪心搜索完成!") print(f"收集资源数量: {result['resources_count']}") print(f"资源总价值: {result['total_value']}") print(f"移动步数: {result['total_moves']}") print(f"路径长度: {len(self.greedy_path)}") return True except Exception as e: print(f"贪心搜索失败: {e}") import traceback traceback.print_exc() return False def next_greedy_step(self): """显示贪心路径的下一步""" if not hasattr(self, 'greedy_path') or not self.greedy_path: return False if self.greedy_step < len(self.greedy_path): self.greedy_step += 1 if self.greedy_step >= len(self.greedy_path): self.is_greedy_path_complete = True return True return False def reset_greedy_path(self): """重置贪心路径显示""" self.greedy_step = 0 self.is_greedy_path_complete = False def get_greedy_progress(self): """获取贪心路径进度信息""" if not hasattr(self, 'greedy_path') or not self.greedy_path: return "贪心路径: 未生成" if hasattr(self, 'greedy_result'): result = self.greedy_result return (f"贪心路径: {self.greedy_step}/{len(self.greedy_path)} | " f"资源: {result['resources_count']} | " f"价值: {result['total_value']}") else: return f"贪心路径: {self.greedy_step}/{len(self.greedy_path)}" def _draw_greedy_path(self, screen, tile_size): """绘制贪心算法路径""" if not self.greedy_path: return # 绘制已走过的路径 for i in range(min(self.greedy_step, len(self.greedy_path))): y, x = self.greedy_path[i] # 绘制路径点 center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2) # 起点用绿色圆圈 if i == 0: pygame.draw.circle(screen, (0, 255, 0), center, tile_size // 4, 3) # 当前位置用红色圆圈 elif i == self.greedy_step - 1: pygame.draw.circle(screen, (255, 0, 0), center, tile_size // 4, 3) # 其他位置用蓝色小圆点 else: pygame.draw.circle(screen, (0, 0, 255), center, tile_size // 6) # 绘制路径连线 if i > 0: prev_y, prev_x = self.greedy_path[i - 1] prev_center = (prev_x * tile_size + tile_size // 2, prev_y * tile_size + tile_size // 2) pygame.draw.line(screen, (100, 100, 255), prev_center, center, 2) # 如果有贪心算法结果,绘制收集的资源位置 if hasattr(self, 'greedy_result') and self.greedy_result: for resource in self.greedy_result['collected_resources']: pos_x, pos_y = resource['position'] # 注意:贪心算法返回的是(x, y)格式 center = (pos_x * tile_size + tile_size // 2, pos_y * tile_size + tile_size // 2) # 用金色圆圈标记已收集的资源 if resource['value'] > 0: pygame.draw.circle(screen, (255, 215, 0), center, tile_size // 3, 2) else: pygame.draw.circle(screen, (255, 100, 100), center, tile_size // 3, 2) # 显示资源价值 font = pygame.font.SysFont(None, max(12, tile_size // 4)) value_text = font.render(str(resource['value']), True, (255, 255, 255)) text_rect = value_text.get_rect(center=center) screen.blit(value_text, text_rect)