147 lines
5.3 KiB
Python
147 lines
5.3 KiB
Python
"""
|
||
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()
|