generate mazes
This commit is contained in:
parent
2f980aaa77
commit
313fe75c36
146
SobolevNS/docs/data/task2_maze/generate_mazes.py
Normal file
146
SobolevNS/docs/data/task2_maze/generate_mazes.py
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
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()
|
||||||
19
SobolevNS/docs/data/task2_maze/generate_weighted_choice.py
Normal file
19
SobolevNS/docs/data/task2_maze/generate_weighted_choice.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""generate_weighted_choice.py - создаёт лабиринт, где Dijkstra/A* реально
|
||||||
|
обходят 'болото' и находят более дешёвый путь, чем BFS."""
|
||||||
|
W, H = 21, 13
|
||||||
|
grid = [[' '] * W for _ in range(H)]
|
||||||
|
# периметр
|
||||||
|
for x in range(W):
|
||||||
|
grid[0][x] = '#'; grid[H-1][x] = '#'
|
||||||
|
for y in range(H):
|
||||||
|
grid[y][0] = '#'; grid[y][W-1] = '#'
|
||||||
|
# центральное болото 5х5 (вес 3)
|
||||||
|
for y in range(4, 9):
|
||||||
|
for x in range(8, 13):
|
||||||
|
grid[y][x] = '~'
|
||||||
|
# старт слева в центре, выход справа в центре
|
||||||
|
grid[H//2][1] = 'S'
|
||||||
|
grid[H//2][W-2] = 'E'
|
||||||
|
with open('mazes/weighted_choice.txt','w') as f:
|
||||||
|
f.write('\n'.join(''.join(row) for row in grid) + '\n')
|
||||||
|
print(open('mazes/weighted_choice.txt').read())
|
||||||
Loading…
Reference in New Issue
Block a user