2026-rff_mp/SobolevNS/docs/data/task2_maze/generate_mazes.py
2026-05-22 13:45:29 +03:00

147 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
generate_mazes.py - генерирует тестовые лабиринты в mazes/.
Состав:
small_10x10.txt - маленький с простым путём
medium_50x50.txt - средний с тупиками (DFS-генератор)
large_100x100.txt - большой запутанный (DFS-генератор)
empty_30x30.txt - без стен внутри (только периметр)
nopath_15x15.txt - без пути от S до E (выход замурован)
weighted_30x30.txt - со взвешенными клетками (асфальт/песок/болото)
"""
import os
import random
random.seed(2024)
MAZES_DIR = "mazes"
os.makedirs(MAZES_DIR, exist_ok=True)
def write(name, lines):
path = os.path.join(MAZES_DIR, name)
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
print("written:", path, f"({len(lines)} строк, ширина {len(lines[0])})")
def make_small():
"""Маленький лабиринт 10x10, ручной."""
raw = [
"##########",
"#S #",
"# ###### #",
"# # #",
"###### # #",
"# # # #",
"# ## # # #",
"# # # #",
"# ##### E",
"##########",
]
write("small_10x10.txt", raw)
def _carve_perfect_maze(w, h, rng):
"""Генератор «идеального» лабиринта DFS (recursive backtracker),
итеративный - чтобы не упасть в RecursionError на больших размерах."""
grid = [['#'] * w for _ in range(h)]
grid[1][1] = ' '
stack = [(1, 1)]
while stack:
x, y = stack[-1]
dirs = [(0, -2), (0, 2), (-2, 0), (2, 0)]
rng.shuffle(dirs)
carved = False
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if 0 < nx < w - 1 and 0 < ny < h - 1 and grid[ny][nx] == '#':
grid[y + dy // 2][x + dx // 2] = ' '
grid[ny][nx] = ' '
stack.append((nx, ny))
carved = True
break
if not carved:
stack.pop()
return grid
def make_with_generator(name, w, h):
"""Создаёт перфектный лабиринт и расставляет S/E в противоположных углах."""
rng = random.Random(hash(name) & 0xFFFF)
grid = _carve_perfect_maze(w, h, rng)
grid[1][1] = 'S'
grid[h - 2][w - 2] = 'E'
lines = ["".join(row) for row in grid]
write(name, lines)
def make_empty(name, w, h):
"""Пустая комната - только периметр."""
lines = []
for y in range(h):
if y == 0 or y == h - 1:
lines.append('#' * w)
else:
lines.append('#' + ' ' * (w - 2) + '#')
# старт в левом верхнем углу, выход в правом нижнем
row = list(lines[1]); row[1] = 'S'; lines[1] = "".join(row)
row = list(lines[h - 2]); row[w - 2] = 'E'; lines[h - 2] = "".join(row)
write(name, lines)
def make_nopath(name, w=15, h=15):
"""Лабиринт, в котором выход замурован - пути нет."""
lines = ['#' * w]
for y in range(1, h - 1):
lines.append('#' + ' ' * (w - 2) + '#')
lines.append('#' * w)
# S слева сверху
row = list(lines[1]); row[1] = 'S'; lines[1] = "".join(row)
# E в правой нижней клетке, но обнесён стенами с двух сторон
# делаем «коробочку» 3x3 вокруг E с одним зазором, который мы тут же закроем
ex, ey = w - 2, h - 2
# сначала откроем коробочку из стен 1 клетка по периметру вокруг E
# построим коробочку: на (ex-1, ey-1)..(ex+1, ey+1) поставим '#' кроме E
for yy in range(ey - 1, ey + 2):
for xx in range(ex - 1, ex + 2):
if 0 <= xx < w and 0 <= yy < h and not (xx == ex and yy == ey):
row = list(lines[yy]); row[xx] = '#'; lines[yy] = "".join(row)
row = list(lines[ey]); row[ex] = 'E'; lines[ey] = "".join(row)
write(name, lines)
def make_weighted(name, w=30, h=30):
"""Перфектный лабиринт + случайные взвешенные клетки на проходимых местах."""
rng = random.Random(7)
grid = _carve_perfect_maze(w | 1, h | 1, rng)
# Перекрасим часть проходов в '.', ',' и '~'
for y, row in enumerate(grid):
for x, ch in enumerate(row):
if ch == ' ':
r = rng.random()
if r < 0.65:
grid[y][x] = ' ' # асфальт (1)
elif r < 0.90:
grid[y][x] = ',' # песок (2)
else:
grid[y][x] = '~' # болото (3)
grid[1][1] = 'S'
grid[len(grid) - 2][len(grid[0]) - 2] = 'E'
lines = ["".join(row) for row in grid]
write(name, lines)
def main():
make_small()
make_with_generator("medium_51x51.txt", 51, 51)
make_with_generator("large_101x101.txt", 101, 101)
make_empty("empty_30x30.txt", 30, 30)
make_nopath("nopath_15x15.txt", 15, 15)
make_weighted("weighted_31x31.txt", 31, 31)
if __name__ == "__main__":
main()