Created
March 31, 2025 13:48
-
-
Save nonefffds/6c1518edafb5d8114434b39225b1ce37 to your computer and use it in GitHub Desktop.
NGU 3rd: MAZE 迷宫生成器
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from matplotlib.colors import ListedColormap | |
import matplotlib.patches as patches | |
import os | |
from matplotlib import font_manager | |
from matplotlib.path import Path | |
# 尝试设置中文字体 | |
def setup_chinese_font(): | |
# 尝试查找系统中的中文字体 | |
chinese_fonts = [ | |
'Microsoft YaHei', 'SimHei', 'SimSun', 'STSong', | |
'FangSong', 'KaiTi', 'WenQuanYi Micro Hei', 'Noto Sans CJK SC', | |
'Noto Sans CJK TC', 'Noto Sans CJK JP', 'Noto Sans CJK KR', | |
'Arial Unicode MS' | |
] | |
font_found = False | |
for font in chinese_fonts: | |
try: | |
font_path = font_manager.findfont(font_manager.FontProperties(family=font)) | |
if os.path.exists(font_path): | |
plt.rcParams['font.family'] = font | |
print(f"使用中文字体: {font}") | |
font_found = True | |
break | |
except: | |
continue | |
if not font_found: | |
# 如果没有找到中文字体,使用英文标签 | |
print("警告: 未找到合适的中文字体,将使用英文标签") | |
return False | |
return True | |
# 获取唯一的文件名(如果文件已存在则添加数字后缀) | |
def get_unique_filename(filepath): | |
if not os.path.exists(filepath): | |
return filepath | |
directory, filename = os.path.split(filepath) | |
name, extension = os.path.splitext(filename) | |
counter = 1 | |
while os.path.exists(filepath): | |
new_filename = f"{name}({counter}){extension}" | |
filepath = os.path.join(directory, new_filename) | |
counter += 1 | |
return filepath | |
class MazeGenerator: | |
def __init__(self, width, height, num_exits=3, save_dir=None, block_size=3): | |
# 确保尺寸为奇数 | |
self.width = width if width % 2 == 1 else width + 1 | |
self.height = height if height % 2 == 1 else height + 1 | |
self.num_exits = num_exits | |
self.block_size = block_size # 区块大小(有多少个原始迷宫格子组成一个区块) | |
# 0表示墙,1表示通道 | |
self.maze = np.zeros((self.height, self.width), dtype=int) | |
self.entrance = (1, 0) # 左上角入口 | |
self.exits = [] # 右侧出口 | |
self.true_exit = None # 真正的出口 | |
self.solution_path = [] # 解决路径 | |
# 保存目录 | |
self.save_dir = save_dir if save_dir else os.getcwd() | |
if not os.path.exists(self.save_dir): | |
os.makedirs(self.save_dir, exist_ok=True) | |
# 检查中文字体 | |
self.use_chinese = setup_chinese_font() | |
def generate_maze(self): | |
# 使用随机Prim算法生成迷宫 | |
# 初始化迷宫,所有单元格都是墙 | |
for i in range(self.height): | |
for j in range(self.width): | |
self.maze[i, j] = 0 | |
# 设置入口 | |
self.maze[1, 0] = 1 | |
walls = [(1, 1)] # 待考虑的墙列表 | |
while walls: | |
# 随机选择一堵墙 | |
wall_idx = random.randint(0, len(walls) - 1) | |
wall = walls.pop(wall_idx) | |
x, y = wall | |
# 确保在范围内 | |
if x < 1 or y < 1 or x >= self.height - 1 or y >= self.width - 1: | |
continue | |
# 检查这堵墙两侧的单元格 | |
# 如果墙是水平的 | |
if x % 2 == 0: | |
if self.maze[x-1, y] == 1 and self.maze[x+1, y] == 0: | |
self.maze[x, y] = 1 | |
self.maze[x+1, y] = 1 | |
# 添加新形成的墙 | |
if y > 1: | |
walls.append((x+1, y-1)) | |
if y < self.width - 2: | |
walls.append((x+1, y+1)) | |
if x < self.height - 2: | |
walls.append((x+2, y)) | |
elif self.maze[x-1, y] == 0 and self.maze[x+1, y] == 1: | |
self.maze[x, y] = 1 | |
self.maze[x-1, y] = 1 | |
# A添加新形成的墙 | |
if y > 1: | |
walls.append((x-1, y-1)) | |
if y < self.width - 2: | |
walls.append((x-1, y+1)) | |
if x > 2: | |
walls.append((x-2, y)) | |
# 如果墙是垂直的 | |
else: | |
if self.maze[x, y-1] == 1 and self.maze[x, y+1] == 0: | |
self.maze[x, y] = 1 | |
self.maze[x, y+1] = 1 | |
# 添加新形成的墙 | |
if x > 1: | |
walls.append((x-1, y+1)) | |
if x < self.height - 2: | |
walls.append((x+1, y+1)) | |
if y < self.width - 2: | |
walls.append((x, y+2)) | |
elif self.maze[x, y-1] == 0 and self.maze[x, y+1] == 1: | |
self.maze[x, y] = 1 | |
self.maze[x, y-1] = 1 | |
# 添加新形成的墙 | |
if x > 1: | |
walls.append((x-1, y-1)) | |
if x < self.height - 2: | |
walls.append((x+1, y-1)) | |
if y > 2: | |
walls.append((x, y-2)) | |
# 设置出口 | |
possible_exits = [] | |
for i in range(1, self.height-1, 2): | |
if self.maze[i, self.width-2] == 1: | |
possible_exits.append((i, self.width-1)) | |
# 如果可能的出口少于要求的出口数,使用所有可能的出口 | |
num_actual_exits = min(len(possible_exits), self.num_exits) | |
# 如果没有可能的出口,创建一个 | |
if not possible_exits: | |
exit_row = random.randrange(1, self.height-1, 2) | |
self.maze[exit_row, self.width-2] = 1 | |
possible_exits.append((exit_row, self.width-1)) | |
num_actual_exits = 1 | |
# 随机选择出口 | |
self.exits = random.sample(possible_exits, num_actual_exits) | |
# 在迷宫右侧开出口 | |
for exit_pos in self.exits: | |
self.maze[exit_pos] = 1 | |
# 选择一个真正的出口 | |
self.true_exit = random.choice(self.exits) | |
# 使用BFS寻找从入口到真正出口的路径 | |
self.find_solution() | |
def find_solution(self): | |
# 使用BFS找到从入口到真正出口的路径 | |
queue = [(self.entrance, [self.entrance])] | |
visited = set([self.entrance]) | |
while queue: | |
(vertex, path) = queue.pop(0) | |
if vertex == self.true_exit: | |
self.solution_path = path | |
return | |
# 检查四个方向 | |
for dx, dy in [(0, 1), (1, 0), (0, -1), (-1, 0)]: | |
nx, ny = vertex[0] + dx, vertex[1] + dy | |
if (0 <= nx < self.height and 0 <= ny < self.width and | |
self.maze[nx, ny] == 1 and (nx, ny) not in visited): | |
queue.append(((nx, ny), path + [(nx, ny)])) | |
visited.add((nx, ny)) | |
def draw_maze(self, show_solution=False): | |
# 复制迷宫以创建用于绘图的版本 | |
maze_plot = np.copy(self.maze) | |
if show_solution: | |
# 标记入口 | |
maze_plot[self.entrance] = 2 | |
# 标记假出口 | |
for exit_pos in self.exits: | |
if exit_pos != self.true_exit: | |
maze_plot[exit_pos] = 3 | |
# 标记真正的出口 | |
maze_plot[self.true_exit] = 4 | |
# 标记解决方案路径 | |
for x, y in self.solution_path: | |
if (x, y) != self.entrance and (x, y) != self.true_exit: | |
maze_plot[x, y] = 5 | |
# 创建颜色映射 | |
colors = ['black', 'white', 'green', 'red', 'blue', 'yellow'] | |
cmap = ListedColormap(colors) | |
else: | |
# 标记入口 | |
maze_plot[self.entrance] = 2 | |
# 标记所有出口为相同颜色 | |
for exit_pos in self.exits: | |
maze_plot[exit_pos] = 3 | |
# 创建颜色映射 - 无需黄色和蓝色 | |
colors = ['black', 'white', 'green', 'red'] | |
cmap = ListedColormap(colors) | |
plt.figure(figsize=(10, 10)) | |
plt.imshow(maze_plot, cmap=cmap) | |
# 设置图例 | |
if self.use_chinese: | |
legend_labels = { | |
'black': '墙', | |
'white': '通道', | |
'green': '入口', | |
'red': '出口' if not show_solution else '假出口', | |
'blue': '真出口', | |
'yellow': '解决路径' | |
} | |
else: | |
legend_labels = { | |
'black': 'Wall', | |
'white': 'Path', | |
'green': 'Entrance', | |
'red': 'Exit' if not show_solution else 'Fake Exit', | |
'blue': 'True Exit', | |
'yellow': 'Solution Path' | |
} | |
# 添加图例 - 放在图的下方而不是上方 | |
legend_elements = [ | |
patches.Patch(color='black', label=legend_labels['black']), | |
patches.Patch(color='white', label=legend_labels['white']), | |
patches.Patch(color='green', label=legend_labels['green']) | |
] | |
if show_solution: | |
legend_elements.extend([ | |
patches.Patch(color='red', label=legend_labels['red']), | |
patches.Patch(color='blue', label=legend_labels['blue']), | |
patches.Patch(color='yellow', label=legend_labels['yellow']) | |
]) | |
else: | |
legend_elements.append(patches.Patch(color='red', label=legend_labels['red'])) | |
# 将图例放在迷宫下方 | |
plt.legend(handles=legend_elements, loc='upper center', bbox_to_anchor=(0.5, -0.05), | |
ncol=3, fancybox=True, shadow=True) | |
# 设置标题 | |
title = f"Maze {self.width}x{self.height}" + (" (with solution)" if show_solution else "") | |
if self.use_chinese: | |
title = f"迷宫 {self.width}x{self.height}" + (" (带解决方案)" if show_solution else "") | |
plt.title(title) | |
plt.axis('off') | |
# 生成初始文件名 | |
base_filename = os.path.join(self.save_dir, f"maze_{self.width}x{self.height}_{'with_solution' if show_solution else 'no_solution'}.png") | |
# 获取唯一文件名 | |
filename = get_unique_filename(base_filename) | |
# 保存图片,调整图像边距以确保图例不会被裁剪 | |
plt.savefig(filename, dpi=300, bbox_inches='tight', pad_inches=0.5) | |
plt.close() | |
print(f"已保存图片:{filename}") | |
def draw_block_maze(self): | |
"""绘制区块迷宫(第三种图像) - 使用 solution_path 数据""" | |
# 计算区块迷宫的尺寸 | |
block_height = (self.height - 1) // self.block_size + 1 | |
block_width = (self.width - 1) // self.block_size + 1 | |
# 创建一个区块迷宫的矩阵 | |
block_maze = np.zeros((block_height, block_width), dtype=int) | |
# 将 solution_path 转换为集合,方便快速查找 | |
solution_path_set = set(self.solution_path) | |
# 计算哪些区块包含解决方案路径 | |
for i in range(block_height): | |
for j in range(block_width): | |
# 计算对应原始迷宫的区域范围 | |
start_i = i * self.block_size | |
end_i = min(start_i + self.block_size, self.height) | |
start_j = j * self.block_size | |
end_j = min(start_j + self.block_size, self.width) | |
block_has_solution_path = False | |
for mi in range(start_i, end_i): | |
for mj in range(start_j, end_j): | |
if 0 <= mi < self.height and 0 <= mj < self.width: | |
if (mi, mj) in solution_path_set: # 检查当前单元格是否在 solution_path 中 | |
block_has_solution_path = True | |
break # 找到 solution_path 中的一个单元格,即可确定区块包含路径 | |
if block_has_solution_path: | |
break | |
if block_has_solution_path: | |
block_maze[i, j] = 1 # 标记为包含路径 | |
else: | |
block_maze[i, j] = 0 # 标记为不包含路径 | |
# 打印 Block Maze Array 用于调试 - 检查 block_maze 的值 | |
print("Block Maze Array (using solution_path):\n", block_maze) | |
# 创建图像 (图像绘制部分与之前的函数相同,无需修改) | |
block_aspect_ratio = block_width / block_height | |
fig_width = 10 | |
fig_height = fig_width / block_aspect_ratio | |
fig, ax = plt.subplots(figsize=(fig_width, fig_height)) | |
# 颜色设置 | |
yellow_color = '#FFFF99' | |
white_color = '#FFFFFF' | |
grid_color = '#000000' | |
# 绘制格子 | |
for i in range(block_height): | |
for j in range(block_width): | |
fill_color = yellow_color if block_maze[i, j] == 1 else white_color | |
rect = patches.Rectangle( | |
(j, i), 1, 1, | |
linewidth=1, | |
edgecolor=grid_color, | |
facecolor=fill_color | |
) | |
ax.add_patch(rect) | |
# 坐标轴设置 | |
ax.set_xlim(0, block_width) | |
ax.set_ylim(0, block_height) | |
ax.invert_yaxis() | |
ax.set_aspect('equal', adjustable='box') # 保持格子长宽比 | |
# 标题和图例 (与之前的函数相同,无需修改) | |
title_text = f"区块迷宫 {block_width}x{block_height} (区块大小: {self.block_size})" | |
legend_elements = [ | |
patches.Patch(color=yellow_color, label='包含路径的区块'), | |
patches.Patch(color=white_color, label='无路径区块') | |
] | |
if not self.use_chinese: | |
legend_elements = [ | |
patches.Patch(color=yellow_color, label='Block with Path'), | |
patches.Patch(color=white_color, label='Block without Path') | |
] | |
title_text = f"Block Maze {block_width}x{block_height} (Block size: {self.block_size})" | |
plt.title(title_text) | |
plt.legend(handles=legend_elements, loc='upper center', bbox_to_anchor=(0.5, -0.05), | |
ncol=2, fancybox=True, shadow=True) | |
plt.xticks([]) | |
plt.yticks([]) | |
# 网格线 | |
for x in range(block_width + 1): | |
ax.axvline(x, color=grid_color, linestyle='-', linewidth=1) | |
for y in range(block_height + 1): | |
ax.axhline(y, color=grid_color, linestyle='-', linewidth=1) | |
# 文件名和保存 (与之前的函数相同,无需修改) | |
base_filename = os.path.join(self.save_dir, f"block_maze_solution_{self.width}x{self.height}_bs{self.block_size}.png") # 修改文件名以区分 | |
filename = get_unique_filename(base_filename) | |
plt.savefig(filename, dpi=300, bbox_inches='tight', pad_inches=0.5) | |
plt.close() | |
print(f"已保存区块迷宫图片 (基于解谜路径): {filename}") | |
# 使用示例 | |
def generate_maze_with_parameters(width, height, num_exits=3, block_size=3, save_dir=None): | |
maze_gen = MazeGenerator(width, height, num_exits, save_dir, block_size) | |
maze_gen.generate_maze() | |
maze_gen.draw_maze(show_solution=False) # 只显示入口和出口 | |
maze_gen.draw_maze(show_solution=True) # 显示解决方案 | |
maze_gen.draw_block_maze() # 生成区块迷宫 | |
# 生成迷宫 - 您可以指定保存目录 | |
save_dir = os.path.join("H:/fdsw2/Desktop/", "Maze_Output") | |
# 生成一个61x61的迷宫,有3个出口 | |
generate_maze_with_parameters(120, 60, 5, block_size=15, save_dir=save_dir) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment