From 313fe75c36fbcc752ccb66eb84e6bbd6c1b0d6eb Mon Sep 17 00:00:00 2001 From: SobolevNS Date: Fri, 22 May 2026 13:45:29 +0300 Subject: [PATCH] generate mazes --- .../docs/data/task2_maze/generate_mazes.py | 146 ++++++++++++++++++ .../task2_maze/generate_weighted_choice.py | 19 +++ 2 files changed, 165 insertions(+) create mode 100644 SobolevNS/docs/data/task2_maze/generate_mazes.py create mode 100644 SobolevNS/docs/data/task2_maze/generate_weighted_choice.py diff --git a/SobolevNS/docs/data/task2_maze/generate_mazes.py b/SobolevNS/docs/data/task2_maze/generate_mazes.py new file mode 100644 index 0000000..0860cb3 --- /dev/null +++ b/SobolevNS/docs/data/task2_maze/generate_mazes.py @@ -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() diff --git a/SobolevNS/docs/data/task2_maze/generate_weighted_choice.py b/SobolevNS/docs/data/task2_maze/generate_weighted_choice.py new file mode 100644 index 0000000..a85aa42 --- /dev/null +++ b/SobolevNS/docs/data/task2_maze/generate_weighted_choice.py @@ -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())