import random import csv import json import os from datetime import datetime class MazeGenerator: def __init__(self, size, filename, name="Default Maze"): # 迷宫基础元素 self.ROUTE = '0' self.WALL = '1' # 特殊元素 self.BOSS = 'b' self.START = 's' self.END = 'e' self.TRAP = 't' self.MECHANISM = 'l' self.GOLD = 'g' self.size = size self.maze = [] self.filename = filename self.name = name # 迷宫名称 self.special_elements = [] # 存储特殊元素的位置和值 self.history_mazes = [] def initialize_maze(self): """初始化迷宫,全部填充为墙""" self.maze = [[self.WALL for _ in range(self.size)] for _ in range(self.size)] def create_maze(self, x1, y1, x2, y2): """递归分割法生成迷宫,墙壁始终在偶数坐标""" def getWallIndex(start, length): assert length >= 3 wall_index = random.randint(start + 1, start + length - 2) if wall_index % 2 == 1: wall_index -= 1 return wall_index def isValid(x, y): return 0 <= x < self.size and 0 <= y < self.size def isMovable(x, y): return self.maze[y][x] != self.WALL def generateHoles(x, y, width, height, wall_x, wall_y): holes = [] hole_entrys = [ (random.randint(x, wall_x - 1), wall_y), (random.randint(wall_x + 1, x + width - 1), wall_y), (wall_x, random.randint(y, wall_y - 1)), (wall_x, random.randint(wall_y + 1, y + height - 1)) ] margin_entrys = [ (x, wall_y), (x + width - 1, wall_y), (wall_x, y), (wall_x, y + height - 1) ] adjacent_entrys = [ (x - 1, wall_y), (x + width, wall_y), (wall_x, y - 1), (wall_x, y + height) ] for i in range(4): adj_x, adj_y = adjacent_entrys[i] if isValid(adj_x, adj_y) and isMovable(adj_x, adj_y): mx, my = margin_entrys[i] self.maze[my][mx] = self.ROUTE else: holes.append(hole_entrys[i]) ignore_hole = random.randint(0, len(holes) - 1) for i in range(len(holes)): if i != ignore_hole: hx, hy = holes[i] self.maze[hy][hx] = self.ROUTE def recursiveDivision(x, y, width, height): if width < 3 or height < 3: return wall_x = getWallIndex(x, width) wall_y = getWallIndex(y, height) for i in range(x, x + width): self.maze[wall_y][i] = self.WALL for i in range(y, y + height): self.maze[i][wall_x] = self.WALL generateHoles(x, y, width, height, wall_x, wall_y) recursiveDivision(x, y, wall_x - x, wall_y - y) recursiveDivision(x, wall_y + 1, wall_x - x, y + height - wall_y - 1) recursiveDivision(wall_x + 1, y, x + width - wall_x - 1, wall_y - y) recursiveDivision(wall_x + 1, wall_y + 1, x + width - wall_x - 1, y + height - wall_y - 1) self.history_mazes.append([[__ for __ in _] for _ in self.maze]) # 先全部通路 self.maze = [[self.ROUTE for _ in range(self.size)] for _ in range(self.size)] # 四周加墙 for x in range(self.size): self.maze[0][x] = self.WALL self.maze[self.size - 1][x] = self.WALL for y in range(self.size): self.maze[y][0] = self.WALL self.maze[y][self.size - 1] = self.WALL # 递归分割 try: recursiveDivision(1, 1, self.size - 2, self.size - 2) except: self.create_maze(x1, y1, x2, y2) # 如果递归失败,重新尝试 def set_random_exits(self): """随机设置迷宫入口和出口""" available = self.get_available_cells() if len(available) < 2: raise ValueError("迷宫空间不足,无法设置随机出入口") start, end = random.sample(available, 2) self.maze[start[0]][start[1]] = self.START self.maze[end[0]][end[1]] = self.END self.special_elements.extend([(start[0], start[1], self.START), (end[0], end[1], self.END)]) def get_history_mazes(self): for idx,i in enumerate(self.history_mazes): if i == self.history_mazes[idx-1]: del self.history_mazes[idx] return self.history_mazes def get_available_cells(self): """获取所有可通行单元格""" cells = [] for i in range(1, self.size - 1): for j in range(1, self.size - 1): if self.maze[i][j] == self.ROUTE: cells.append((i, j)) return cells def place_special_elements(self, boss_count=0, traps_range=(3, 8), mechanisms_range=(2, 6), skill_traps=5,gold_range=(3,8)): """放置特殊元素(支持技能触发陷阱)- 不包括Boss和机关,这些将在路径生成后放置""" available = self.get_available_cells() random.shuffle(available) # 计算所需单元格数量(不包括Boss和机关) required = 2 + random.randint(*traps_range) + skill_traps + random.randint(*gold_range) if len(available) < required: raise ValueError(f"空间不足,需要{required}个单元格,实际可用{len(available)}") # 放置出入口 start, end = available.pop(), available.pop() self.special_elements.extend([(start[0], start[1], self.START), (end[0], end[1], self.END)]) self.maze[start[0]][start[1]] = self.START self.maze[end[0]][end[1]] = self.END # 注释掉Boss放置,Boss将在路径生成后放置 # # 放置BOSS # for _ in range(boss_count): # pos = available.pop() # val = random.randint(50, 100) # self.special_elements.append((pos[0], pos[1], f"{self.BOSS}{val}")) # self.maze[pos[0]][pos[1]] = f"{self.BOSS}{val}" # 放置普通陷阱 traps = random.randint(*traps_range) for _ in range(traps): pos = available.pop() val = random.randint(5, 20) self.special_elements.append((pos[0], pos[1], f"{self.TRAP}{val}")) self.maze[pos[0]][pos[1]] = f"{self.TRAP}{val}" # 技能触发陷阱 for _ in range(skill_traps): pos = available.pop() val = random.randint(5, 20) self.special_elements.append((pos[0], pos[1], f"{self.TRAP}{val}")) self.maze[pos[0]][pos[1]] = f"{self.TRAP}{val}" # 注释掉机关放置,机关将在路径生成后放置 # # 放置机关 # mechanisms = random.randint(*mechanisms_range) # for _ in range(mechanisms): # pos = available.pop() # val = random.randint(10, 30) # self.special_elements.append((pos[0], pos[1], f"{self.MECHANISM}{val}")) # self.maze[pos[0]][pos[1]] = f"{self.MECHANISM}{val}" # 放置金币 gold_count = random.randint(*gold_range) for _ in range(gold_count): pos = available.pop() val = random.randint(10, 30) self.special_elements.append((pos[0], pos[1], f"{self.GOLD}{val}")) self.maze[pos[0]][pos[1]] = f"{self.GOLD}{val}" def generate(self, seed=None, traps_range=(3, 8), mechanisms_range=(1, 1), skill_traps=5): """生成迷宫主方法 - Boss和机关将在路径生成后单独放置""" # 清除之前的历史记录 self.history_mazes = [] self.special_elements = [] random.seed(seed or random.randint(0, 1000)) self.initialize_maze() self.create_maze(1, 1, self.size - 2, self.size - 2) self.patch_maze_edges() # 自动修正边界 self.place_special_elements(0, traps_range, mechanisms_range, skill_traps) # boss_count=0, mechanisms也不在这里放置 print(f"成功生成迷宫: {self.name} (Boss和机关将在路径生成后放置)") def export_to_csv(self, filename): """导出迷宫到CSV文件""" try: with open(filename, 'w', newline='') as f: writer = csv.writer(f) for row in self.maze: writer.writerow(row) print(f"迷宫已导出至: {os.path.abspath(filename)}") except Exception as e: print(f"导出失败: {str(e)}") def read_from_csv(self): """从CSV读取迷宫数据""" try: with open(self.filename, 'r', newline='') as f: reader = csv.reader(f) self.maze = [list(map(self._parse_element, row)) for row in reader] self.size = len(self.maze) print(f"成功从{os.path.abspath(self.filename)}读取迷宫") return True except Exception as e: print(f"读取失败: {str(e)}") return False def _parse_element(self, elem): """解析CSV中的元素类型""" if elem == 's': return self.START if elem == 'e': return self.END if elem.startswith('b'): return self.BOSS if elem.startswith('t'): return self.TRAP if elem.startswith('l'): return self.MECHANISM if elem.startswith('g'): return self.GOLD return int(elem) def print_maze(self): """打印迷宫到控制台(带元素标识)""" symbols = { self.WALL: '■', self.ROUTE: '·', self.START: 'S', self.END: 'E', self.BOSS: 'B', self.TRAP: 'T', self.GOLD: 'G','|':'|','-':'—', } for row in self.maze: display = [] for cell in row: symbol = symbols.get(((str)(cell))[0], ((str)(cell))[0]) display.append(str(symbol)) print(' '.join(display)) def read_csv(self): l = [] with open(f'{self.filename}', 'r', newline='') as f: reader = csv.reader(f) for row in reader: l.append(row) return l def patch_maze_edges(self): """只在不破坏联通性的前提下,修正右侧和下侧边界的多余通路(加墙)""" n = self.size if n %2==1: return candidates = [] # 倒数第二列(右侧) x = n - 2 for y in range(1, n-1): if self.maze[y][x] == self.ROUTE: right = self.maze[y][x+1] if x+1 < n else self.WALL down = self.maze[y+1][x] if y+1 < n else self.WALL if right == self.ROUTE or down == self.ROUTE: candidates.append((y, x)) # 倒数第 # 二行(下侧) y = n - 2 for x in range(1, n-1): if self.maze[y][x] == self.ROUTE: right = self.maze[y][x+1] if x+1 < n else self.WALL down = self.maze[y+1][x] if y+1 < n else self.WALL if right == self.ROUTE or down == self.ROUTE: candidates.append((y, x)) # 逐个尝试加墙,确保联通 for y, x in candidates: old = self.maze[y][x] self.maze[y][x] = self.WALL if not self.is_maze_connected(): self.maze[y][x] = old # 恢复 self.history_mazes.append([[__ for __ in _] for _ in self.maze]) def is_maze_connected(self): """检查迷宫是否连通(深度优先搜索)""" visited = [[False] * self.size for _ in range(self.size)] def dfs(x, y): if x < 0 or x >= self.size or y < 0 or y >= self.size: return if visited[y][x] or self.maze[y][x] == self.WALL: return visited[y][x] = True dfs(x + 1, y) dfs(x - 1, y) dfs(x, y + 1) dfs(x, y - 1) # 从左上角开始搜索 dfs(1, 1) # 检查是否所有通路都被访问 for y in range(1, self.size - 1): for x in range(1, self.size - 1): if self.maze[y][x] == self.ROUTE and not visited[y][x]: return False return True def export_to_json(self, filename): """导出迷宫到JSON文件""" try: import json from datetime import datetime # 转换迷宫格式 json_maze = self._convert_to_json_format() # 创建JSON数据结构 json_data = { "maze": json_maze, "metadata": { "name": self.name, "size": self.size, "export_time": datetime.now().isoformat() if 'datetime' in globals() else None, "special_elements_count": len(self.special_elements) } } with open(filename, 'w', encoding='utf-8') as f: json.dump(json_data, f, indent=2, ensure_ascii=False) print(f"迷宫已导出至: {os.path.abspath(filename)}") except Exception as e: print(f"JSON导出失败: {str(e)}") def _convert_to_json_format(self): """ 将内部迷宫格式转换为JSON格式 '1' -> '#', '0' -> ' ', 's' -> 'S', 'e' -> 'E' 'g数字' -> 'G', 't数字' -> 'T', 'l数字' -> 'L', 'b数字' -> 'B' """ json_maze = [] for row in self.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 place_boss_on_path(self, path, boss_count=1): """ 在给定路径上随机放置Boss Args: path: 路径列表,格式为[(y1, x1), (y2, x2), ...] boss_count: 要放置的Boss数量,默认为1 Returns: list: 放置Boss的位置列表 """ if not path or len(path) < 3: print("路径太短,无法放置Boss") return [] # 排除起点和终点,只在中间路径上放置Boss middle_path = path[1:-1] # 去掉第一个(起点)和最后一个(终点) if len(middle_path) < boss_count: print(f"路径中间位置不足,只能放置{len(middle_path)}个Boss") boss_count = len(middle_path) # 随机选择Boss放置位置 boss_positions = random.sample(middle_path, boss_count) placed_bosses = [] for pos in boss_positions: y, x = pos # 只在通路位置放置Boss,避免覆盖特殊元素 if self.maze[y][x] == self.ROUTE or str(self.maze[y][x]).startswith('p'): val = random.randint(50, 100) boss_element = f"{self.BOSS}{val}" self.maze[y][x] = boss_element self.special_elements.append((y, x, boss_element)) placed_bosses.append((y, x, val)) print(f"在路径位置 ({y}, {x}) 放置Boss,血量: {val}") return placed_bosses def place_mechanisms_on_path(self, path, mechanisms_count=1, mechanisms_range=(1, 1)): """ 在给定路径上随机放置机关 Args: path: 路径列表,格式为[(y1, x1), (y2, x2), ...] mechanisms_count: 要放置的机关数量,默认为1 mechanisms_range: 机关数量范围,当mechanisms_count为None时使用 Returns: list: 放置机关的位置列表 """ if not path or len(path) < 3: print("路径太短,无法放置机关") return [] # 如果没有指定机关数量,使用默认值1 if mechanisms_count is None: mechanisms_count = 1 # 排除起点和终点,只在中间路径上放置机关 middle_path = path[1:-1] # 去掉第一个(起点)和最后一个(终点) # 过滤掉已经被Boss和其他特殊元素占据的位置 available_positions = [] for pos in middle_path: y, x = pos cell_str = str(self.maze[y][x]) # 只在通路位置或路径标记位置放置机关,避免覆盖Boss和其他特殊元素 if (self.maze[y][x] == self.ROUTE or cell_str.startswith('p')) and not cell_str.startswith('b'): available_positions.append(pos) if len(available_positions) < mechanisms_count: print(f"路径中可用位置不足,只能放置{len(available_positions)}个机关") mechanisms_count = len(available_positions) if mechanisms_count <= 0: print("无可用位置放置机关") return [] # 随机选择机关放置位置,确保不重复 mechanism_positions = random.sample(available_positions, mechanisms_count) placed_mechanisms = [] for pos in mechanism_positions: y, x = pos # 再次检查位置是否可用(避免重复放置) if self.maze[y][x] == self.ROUTE or str(self.maze[y][x]).startswith('p'): val = random.randint(10, 30) mechanism_element = f"{self.MECHANISM}{val}" self.maze[y][x] = mechanism_element self.special_elements.append((y, x, mechanism_element)) placed_mechanisms.append((y, x, val)) print(f"在路径位置 ({y}, {x}) 放置机关,奖励: {val}") return placed_mechanisms def main(): # 示例1: 生成带技能陷阱的迷宫 generator = MazeGenerator( size=20, filename="dungeon_maze.csv" ) generator.generate( seed=666 ) generator.print_maze() generator.export_to_csv("d.csv") for i in generator.history_mazes: print(i[3:]) reader = MazeGenerator(size=1, filename="d.csv") if reader.read_from_csv(): print("\n读取的迷宫:") reader.print_maze() for i in generator.get_history_mazes(): for j in i: print(j) print() if __name__ == "__main__": main()