Skip to content

Instantly share code, notes, and snippets.

@nonefffds
Created March 31, 2025 13:48
Show Gist options
  • Save nonefffds/6c1518edafb5d8114434b39225b1ce37 to your computer and use it in GitHub Desktop.
Save nonefffds/6c1518edafb5d8114434b39225b1ce37 to your computer and use it in GitHub Desktop.
NGU 3rd: MAZE 迷宫生成器
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