import csv import json import os from datetime import datetime from config import DEFAULT_MAZE_FILE class SimpleSaveManager: """简化的存档管理器 - 支持JSON和CSV格式的迷宫""" def __init__(self): self.save_directory = "saves" self.ensure_save_directory() def ensure_save_directory(self): """确保存档目录存在""" if not os.path.exists(self.save_directory): os.makedirs(self.save_directory) def save_maze_with_path(self, maze_instance, save_name=None, format_type="json"): """ 保存包含路径信息的迷宫到文件 Args: maze_instance: Maze类的实例 save_name: 存档名称,如果为None则使用时间戳 format_type: 文件格式 "json" 或 "csv" Returns: str: 保存的文件路径 """ if save_name is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") save_name = f"maze_save_{timestamp}" # 根据格式类型确定文件扩展名 if format_type.lower() == "json": if not save_name.endswith('.json'): save_name += '.json' return self._save_maze_json(maze_instance, save_name) else: if not save_name.endswith('.csv'): save_name += '.csv' return self._save_maze_csv(maze_instance, save_name) def create_path_grid(self, maze_instance): """ 创建包含路径信息的网格 Args: maze_instance: Maze类的实例 Returns: list: 包含路径信息的二维网格 """ if not maze_instance.generater.maze or not maze_instance.full_path: return maze_instance.generater.maze if maze_instance.generater.maze else [] # 从原始迷宫开始 path_grid = [row[:] for row in maze_instance.generater.maze] # 深拷贝 # 将完整路径信息添加到网格中 for idx, (y, x) in enumerate(maze_instance.full_path): current_cell = path_grid[y][x] path_info = f"p{idx + 1}" # 路径编号从1开始 if current_cell.startswith('s'): # 起点:保留起点标记,但也添加路径信息 path_grid[y][x] = f"s{path_info}" elif current_cell.startswith('e'): # 终点:保留终点标记,但也添加路径信息 path_grid[y][x] = f"e{path_info}" elif current_cell.startswith('g'): # 金币:g10p31 格式 path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('t'): # 陷阱:t19p31 格式 path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('l'): # 机关:l24p31 格式 path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('b'): # boss:b92p31 格式 path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell == '0': # 空地:直接是p31 path_grid[y][x] = path_info else: # 其他情况也添加路径信息 path_grid[y][x] = f"{current_cell}{path_info}" return path_grid def load_maze_from_file(self, file_path): """ 从文件加载迷宫,自动检测文件格式 Args: file_path: 文件路径 Returns: dict: 包含迷宫数据和路径信息的字典 """ if file_path.endswith('.json'): return self.load_maze_from_json(file_path) elif file_path.endswith('.csv'): return self.load_maze_from_csv(file_path) else: print(f"不支持的文件格式: {file_path}") return None def load_maze_from_json(self, json_file): """ 从JSON文件加载迷宫 Args: json_file: JSON文件路径 Returns: dict: 包含迷宫数据和路径信息的字典 """ try: with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) # 检查数据结构 if 'maze' not in data: print("JSON文件格式错误:缺少maze字段") return None # 转换JSON格式到内部格式 internal_maze = self.convert_from_json_format(data['maze']) # 检查并修复迷宫边界 internal_maze, boundary_added = self.ensure_maze_boundaries(internal_maze) result = { 'original_grid': internal_maze, 'path_grid': internal_maze, # 对于JSON格式,初始时路径网格与原始网格相同 'path_sequence': [], 'path_positions': {}, 'metadata': data.get('metadata', {}), 'format': 'json', 'boss_data': data.get('B', []), # Boss血量序列 'player_skills': data.get('PlayerSkills', []) # 玩家技能序列 } # 如果有路径数据,也加载路径信息 if 'path_data' in data: raw_path = data['path_data'].get('full_path', []) # 确保路径数据是元组格式 (JSON会将元组转换为列表) path_sequence = [tuple(pos) if isinstance(pos, list) else pos for pos in raw_path] # 如果添加了边界,调整路径坐标 if boundary_added: path_sequence = self.adjust_path_coordinates(path_sequence, boundary_added) result['path_sequence'] = path_sequence result['path_positions'] = {i+1: pos for i, pos in enumerate(path_sequence)} # 如果有路径,创建包含路径的网格 if result['path_sequence']: result['path_grid'] = self.create_path_grid_from_data(internal_maze, result['path_sequence']) print(f"成功从 {os.path.abspath(json_file)} 加载迷宫") if result['path_sequence']: print(f"路径长度: {len(result['path_sequence'])}") return result except Exception as e: print(f"JSON加载失败: {str(e)}") return None def load_maze_from_csv(self, csv_file): """ 从CSV文件加载迷宫,解析路径信息 Args: csv_file: CSV文件路径 Returns: dict: 包含迷宫数据和路径信息的字典 """ try: with open(csv_file, 'r', newline='', encoding='utf-8') as f: reader = csv.reader(f) grid = [list(row) for row in reader] # 检查并修复迷宫边界 grid, boundary_added = self.ensure_maze_boundaries(grid) # 解析路径信息 path_data = self.extract_path_from_grid(grid) # 如果添加了边界但有路径数据,需要调整路径坐标 # 注意:对于CSV,路径信息是从网格中提取的,添加边界后坐标已经自动调整了 # 所以这里不需要额外调整 # 创建原始迷宫(移除路径信息) original_grid = self.create_original_grid(grid) result = { 'original_grid': original_grid, 'path_grid': grid, 'path_sequence': path_data['path_sequence'], 'path_positions': path_data['path_positions'], 'format': 'csv' } print(f"成功从 {os.path.abspath(csv_file)} 加载迷宫") print(f"路径长度: {len(path_data['path_sequence'])}") return result except Exception as e: print(f"CSV加载失败: {str(e)}") return None def create_path_grid_from_data(self, original_grid, path_sequence): """从原始网格和路径序列创建包含路径信息的网格""" path_grid = [row[:] for row in original_grid] # 深拷贝 for idx, (y, x) in enumerate(path_sequence): if y >= len(path_grid) or x >= len(path_grid[0]): continue current_cell = path_grid[y][x] path_info = f"p{idx + 1}" # 路径编号从1开始 if current_cell.startswith('s'): path_grid[y][x] = f"s{path_info}" elif current_cell.startswith('e'): path_grid[y][x] = f"e{path_info}" elif current_cell.startswith('g'): path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('t'): path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('l'): path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell.startswith('b'): path_grid[y][x] = f"{current_cell}{path_info}" elif current_cell == '0': path_grid[y][x] = path_info else: path_grid[y][x] = f"{current_cell}{path_info}" return path_grid def extract_path_from_grid(self, grid): """从网格中提取路径信息""" path_positions = {} # {step_number: (y, x)} for y in range(len(grid)): for x in range(len(grid[y])): cell = grid[y][x] # 查找所有路径信息 p数字 import re # 使用正则表达式找到所有p后跟数字的模式 path_matches = re.findall(r'p(\d+)', cell) for path_match in path_matches: try: step_number = int(path_match) path_positions[step_number] = (y, x) except ValueError: continue # 按步数排序创建路径序列 path_sequence = [] for step in sorted(path_positions.keys()): path_sequence.append(path_positions[step]) return { 'path_sequence': path_sequence, 'path_positions': path_positions } def create_original_grid(self, path_grid): """从包含路径的网格创建原始迷宫网格""" original_grid = [] for row in path_grid: original_row = [] for cell in row: # 移除路径信息,恢复原始状态 if 'p' in cell: p_index = cell.find('p') original_cell = cell[:p_index] if p_index > 0 else '0' else: original_cell = cell original_row.append(original_cell) original_grid.append(original_row) return original_grid def get_save_list(self): """获取所有存档文件列表(支持JSON和CSV格式)""" saves = [] if os.path.exists(self.save_directory): for filename in os.listdir(self.save_directory): if filename.endswith('.csv') or filename.endswith('.json'): file_path = os.path.join(self.save_directory, filename) try: stat = os.stat(file_path) # 确定文件格式 file_format = 'json' if filename.endswith('.json') else 'csv' # 移除扩展名作为名称 name = filename.replace('.json', '').replace('.csv', '') saves.append({ 'filename': filename, 'path': file_path, 'name': name, 'save_time': datetime.fromtimestamp(stat.st_mtime).isoformat(), 'size': stat.st_size, 'format': file_format }) except: continue return sorted(saves, key=lambda x: x['save_time'], reverse=True) def delete_save(self, save_file): """删除存档文件""" try: if os.path.exists(save_file): os.remove(save_file) print(f"存档已删除: {save_file}") return True except Exception as e: print(f"删除失败: {str(e)}") return False def _save_maze_json(self, maze_instance, save_name): """保存迷宫为JSON格式""" save_file = os.path.join(self.save_directory, save_name) try: # 转换迷宫格式 json_maze = self.convert_to_json_format(maze_instance) # 创建保存数据结构 save_data = { "maze": json_maze, "metadata": { "save_name": save_name.replace('.json', ''), "save_time": datetime.now().isoformat(), "maze_size": maze_instance.size, "path_length": len(maze_instance.full_path) if maze_instance.full_path else 0 } } # 如果有路径信息,也保存路径 if maze_instance.full_path: save_data["path_data"] = { "full_path": maze_instance.full_path, "current_step": maze_instance.path_step, "is_path_complete": maze_instance.is_path_complete } # 保存为JSON文件 with open(save_file, 'w', encoding='utf-8') as f: json.dump(save_data, f, indent=2, ensure_ascii=False) print(f"迷宫已保存至: {os.path.abspath(save_file)}") return save_file except Exception as e: print(f"JSON保存失败: {str(e)}") return None def _save_maze_csv(self, maze_instance, save_name): """保存迷宫为CSV格式(原有功能)""" save_file = os.path.join(self.save_directory, save_name) try: # 生成包含路径信息的网格 path_grid = self.create_path_grid(maze_instance) # 保存到CSV文件 with open(save_file, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) for row in path_grid: writer.writerow(row) print(f"迷宫已保存至: {os.path.abspath(save_file)}") return save_file except Exception as e: print(f"CSV保存失败: {str(e)}") return None def convert_to_json_format(self, maze_instance): """ 将内部迷宫格式转换为JSON格式 内部格式 -> JSON格式: '1' -> '#' (墙壁) '0' -> ' ' (通路) 's' -> 'S' (起点) 'e' -> 'E' (终点) 'g数字' -> 'G' (金币) 't数字' -> 'T' (陷阱) 'l数字' -> 'L' (机关) 'b数字' -> 'B' (BOSS) """ if not maze_instance.generater.maze: return [] json_maze = [] for row in maze_instance.generater.maze: json_row = [] for cell in row: cell_str = str(cell) if cell_str == '1': json_row.append('#') elif cell_str == '0': json_row.append(' ') elif cell_str == 's': json_row.append('S') elif cell_str == 'e': json_row.append('E') elif cell_str.startswith('g'): json_row.append('G') elif cell_str.startswith('t'): json_row.append('T') elif cell_str.startswith('l'): json_row.append('L') elif cell_str.startswith('b'): json_row.append('B') else: # 未知类型,默认为通路 json_row.append(' ') json_maze.append(json_row) return json_maze def convert_from_json_format(self, json_maze): """ 将JSON格式转换为内部迷宫格式 JSON格式 -> 内部格式: '#' -> '1' (墙壁) ' ' -> '0' (通路) 'S' -> 's' (起点) 'E' -> 'e' (终点) 'G' -> 'g10' (金币,默认值10) 'T' -> 't15' (陷阱,默认值15) 'L' -> 'l20' (机关,默认值20) 'B' -> 'b50' (BOSS,默认值50) """ internal_maze = [] for row in json_maze: internal_row = [] for cell in row: if cell == '#': internal_row.append('1') elif cell == ' ': internal_row.append('0') elif cell == 'S': internal_row.append('s') elif cell == 'E': internal_row.append('e') elif cell == 'G': internal_row.append('g30') # 默认金币值 elif cell == 'T': internal_row.append('t20') # 默认陷阱值 elif cell == 'L': internal_row.append('l20') # 默认机关值 elif cell == 'B': internal_row.append('b') # 默认BOSS值 else: # 未知类型,默认为通路 internal_row.append('0') internal_maze.append(internal_row) return internal_maze def ensure_maze_boundaries(self, maze_grid): """ 确保迷宫四周都是墙壁,如果不是则自动添加边界墙 Args: maze_grid: 迷宫网格数据 Returns: tuple: (修复后的迷宫网格, 是否添加了边界) """ if not maze_grid or len(maze_grid) == 0: return maze_grid, False # 检查是否需要添加边界 needs_boundary = False height = len(maze_grid) width = len(maze_grid[0]) if height > 0 else 0 if width == 0: return maze_grid, False # 检查四条边是否全是墙壁('#' 或 '1')或起终点('S', 'E', 's', 'e') # 上边界 for col in range(width): cell = maze_grid[0][col] if cell not in ['#', '1', 'S', 'E', 's', 'e']: needs_boundary = True break # 下边界 if not needs_boundary: for col in range(width): cell = maze_grid[height-1][col] if cell not in ['#', '1', 'S', 'E', 's', 'e']: needs_boundary = True break # 左边界 if not needs_boundary: for row in range(height): cell = maze_grid[row][0] if cell not in ['#', '1', 'S', 'E', 's', 'e']: needs_boundary = True break # 右边界 if not needs_boundary: for row in range(height): cell = maze_grid[row][width-1] if cell not in ['#', '1', 'S', 'E', 's', 'e']: needs_boundary = True break if not needs_boundary: print("迷宫边界检查通过,无需修复") return maze_grid, False print("检测到迷宫边界不完整,自动添加边界墙壁...") # 创建新的迷宫,四周加上墙壁 new_height = height + 2 new_width = width + 2 new_maze = [] # 添加上边界 new_maze.append(['#'] * new_width) # 添加中间行(左右加墙) for row in maze_grid: new_row = ['#'] + row + ['#'] new_maze.append(new_row) # 添加下边界 new_maze.append(['#'] * new_width) print(f"边界修复完成:{width}x{height} -> {new_width}x{new_height}") return new_maze, True def adjust_path_coordinates(self, path_sequence, boundary_added): """ 当添加边界后,调整路径坐标 Args: path_sequence: 原始路径序列 boundary_added: 是否添加了边界 Returns: list: 调整后的路径序列 """ if not boundary_added or not path_sequence: return path_sequence # 所有坐标向右下偏移1个单位 adjusted_path = [] for pos in path_sequence: if isinstance(pos, (list, tuple)) and len(pos) == 2: adjusted_path.append((pos[0] + 1, pos[1] + 1)) else: adjusted_path.append(pos) # 保持原样(可能是错误数据) print(f"路径坐标已调整,共 {len(adjusted_path)} 个点") return adjusted_path # 全局简化存档管理器实例 simple_save_manager = SimpleSaveManager()