from __future__ import annotations import random from collections import deque from pathlib import Path ROOT = Path(__file__).resolve().parents[1] MAZE_DIR = ROOT / "data" / "mazes" def main() -> None: generate_all() def generate_all() -> None: MAZE_DIR.mkdir(parents=True, exist_ok=True) _write("small.txt", _small_maze()) _write("medium.txt", _perfect_maze(50, 50, seed=2026)) _write("large.txt", _perfect_maze(100, 100, seed=2027)) _write("empty.txt", _empty_maze(50, 50)) _write("no_exit.txt", _no_path_maze(30, 30)) def _write(filename: str, rows: list[str]) -> None: (MAZE_DIR / filename).write_text("\n".join(rows) + "\n", encoding="utf-8") def _small_maze() -> list[str]: return [ "##########", "#S #E#", "# #### # #", "# # # #", "# # #### #", "# # #", "# ###### #", "# #", "######## #", "##########", ] def _empty_maze(width: int, height: int) -> list[str]: grid = _bordered_grid(width, height, fill=" ") grid[1][1] = "S" grid[height - 2][width - 2] = "E" return _to_rows(grid) def _no_path_maze(width: int, height: int) -> list[str]: grid = [["#" for _ in range(width)] for _ in range(height)] grid[1][1] = "S" grid[height - 2][width - 2] = "E" return _to_rows(grid) def _perfect_maze(width: int, height: int, seed: int) -> list[str]: if width < 5 or height < 5: raise ValueError("Maze must be at least 5x5") randomizer = random.Random(seed) grid = [["#" for _ in range(width)] for _ in range(height)] start = (1, 1) stack = [start] grid[start[1]][start[0]] = " " while stack: x, y = stack[-1] candidates = [] for dx, dy in ((0, -2), (2, 0), (0, 2), (-2, 0)): nx, ny = x + dx, y + dy if 1 <= nx < width - 1 and 1 <= ny < height - 1 and grid[ny][nx] == "#": candidates.append((nx, ny, dx, dy)) if not candidates: stack.pop() continue nx, ny, dx, dy = randomizer.choice(candidates) grid[y + dy // 2][x + dx // 2] = " " grid[ny][nx] = " " stack.append((nx, ny)) exit_x, exit_y = _farthest_open_cell(grid, start) grid[start[1]][start[0]] = "S" grid[exit_y][exit_x] = "E" return _to_rows(grid) def _farthest_open_cell(grid: list[list[str]], start: tuple[int, int]) -> tuple[int, int]: queue = deque([start]) distances = {start: 0} farthest = start while queue: x, y = queue.popleft() if distances[(x, y)] > distances[farthest]: farthest = (x, y) for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)): nx, ny = x + dx, y + dy if (nx, ny) not in distances and grid[ny][nx] != "#": distances[(nx, ny)] = distances[(x, y)] + 1 queue.append((nx, ny)) return farthest def _bordered_grid(width: int, height: int, fill: str) -> list[list[str]]: grid = [[fill for _ in range(width)] for _ in range(height)] for x in range(width): grid[0][x] = "#" grid[height - 1][x] = "#" for y in range(height): grid[y][0] = "#" grid[y][width - 1] = "#" return grid def _to_rows(grid: list[list[str]]) -> list[str]: return ["".join(row) for row in grid] if __name__ == "__main__": main()