from __future__ import annotations import heapq import os import random WALL = "M" def _maze_path() -> str: return os.path.join(os.path.dirname(__file__), "maze.txt") def _read_int( prompt: str, *, min_value: int | None = None, max_value: int | None = None ) -> int: while True: try: s = input(prompt).strip() except EOFError: raise try: v = int(s) except ValueError: print("Ошибка: нужно целое число.") continue if min_value is not None and v < min_value: print(f"Ошибка: число должно быть не меньше {min_value}.") continue if max_value is not None and v > max_value: print(f"Ошибка: число должно быть не больше {max_value}.") continue return v def _normalize_grid(rows: list[str], n: int) -> list[str]: if len(rows) != n: raise ValueError(f"Ожидалось {n} строк, получено {len(rows)}.") grid = [(r[:n] + (" " * max(0, n - len(r)))) for r in rows] return grid def read_maze_from_file(n: int) -> list[str]: path = _maze_path() if not os.path.exists(path): raise FileNotFoundError("Файл maze.txt не найден.") with open(path, "r", encoding="utf-8") as f: rows = [ln.rstrip("\n") for ln in f.readlines()] rows = rows[:n] return _normalize_grid(rows, n) def write_maze_to_file(grid: list[str]) -> None: path = _maze_path() with open(path, "w", encoding="utf-8") as f: for r in grid: f.write(r + "\n") def print_maze_with_coords(grid: list[str]) -> None: n = len(grid) # Заголовок столбцов print(" ", end="") for j in range(n): print(f"{j + 1:2}", end=" ") print() # Лабиринт с номерами строк for i, row in enumerate(grid): print(f"{i + 1:2} ", end="") for cell in row: print(f" {cell if cell != ' ' else '.'}", end=" ") print() def _is_exit(grid: list[str], i: int, j: int) -> bool: n = len(grid) return (i == 0 or j == 0 or i == n - 1 or j == n - 1) and grid[i][j] != WALL def a_star_shortest_path(grid: list[str], start: tuple[int, int]) -> str: n = len(grid) sx, sy = start if not (0 <= sx < n and 0 <= sy < n): raise ValueError("Стартовые координаты вне лабиринта.") if grid[sx][sy] == WALL: raise ValueError("Стартовая клетка является стеной.") exits = [(i, j) for i in range(n) for j in range(n) if _is_exit(grid, i, j)] if not exits: raise ValueError("В лабиринте нет выходов.") if _is_exit(grid, sx, sy): return "" def h(i: int, j: int) -> int: return min(abs(i - ei) + abs(j - ej) for ei, ej in exits) inf = 10**9 gscore = [[inf] * n for _ in range(n)] gscore[sx][sy] = 0 prev: list[list[tuple[int, int] | None]] = [[None] * n for _ in range(n)] prev_move: list[list[str | None]] = [[None] * n for _ in range(n)] heap: list[tuple[int, int, int, int]] = [(h(sx, sy), 0, sx, sy)] closed = [[False] * n for _ in range(n)] dirs = ((0, -1, "L"), (0, 1, "R"), (-1, 0, "U"), (1, 0, "D")) while heap: _, g, i, j = heapq.heappop(heap) if closed[i][j]: continue closed[i][j] = True if _is_exit(grid, i, j): path: list[str] = [] ci, cj = i, j while (ci, cj) != (sx, sy): mv = prev_move[ci][cj] p = prev[ci][cj] if mv is None or p is None: raise RuntimeError("Ошибка восстановления пути.") path.append(mv) ci, cj = p path.reverse() return "".join(path) for di, dj, mv in dirs: ni, nj = i + di, j + dj if not (0 <= ni < n and 0 <= nj < n): continue if grid[ni][nj] == WALL: continue ng = g + 1 if ng < gscore[ni][nj]: gscore[ni][nj] = ng prev[ni][nj] = (i, j) prev_move[ni][nj] = mv heapq.heappush(heap, (ng + h(ni, nj), ng, ni, nj)) raise ValueError("Пути к выходу нет.") def _random_grid(n: int, wall_percent: int) -> list[str]: p = wall_percent / 100.0 rows: list[str] = [] for i in range(n): row_chars: list[str] = [] for j in range(n): if random.random() < p: row_chars.append(WALL) else: row_chars.append(" ") rows.append("".join(row_chars)) return rows def _random_passage_cell(grid: list[str]) -> tuple[int, int]: n = len(grid) cells = [(i, j) for i in range(n) for j in range(n) if grid[i][j] != WALL] if not cells: raise ValueError("В лабиринте нет проходов.") return random.choice(cells) def generate_maze_file_interactive() -> None: n = _read_int("Введите N (1..15): ", min_value=1, max_value=15) wall_percent = _read_int( "Введите процент стен M (0..90): ", min_value=0, max_value=90 ) tries = 0 while True: tries += 1 grid = _random_grid(n, wall_percent) for j in range(n): if random.random() < 0.25: grid[0] = grid[0][:j] + " " + grid[0][j + 1 :] start = _random_passage_cell(grid) try: _ = a_star_shortest_path(grid, start) write_maze_to_file(grid) print( f"Файл maze.txt создан. N={n}. Старт: X={start[0] + 1} Y={start[1] + 1}." ) print_maze_with_coords(grid) return except Exception: if tries >= 500: raise ValueError("Не удалось сгенерировать лабиринт.") def print_maze_file_interactive() -> None: n = _read_int("Введите N (1..15): ", min_value=1, max_value=15) grid = read_maze_from_file(n) print("maze.txt:") print_maze_with_coords(grid) def solve_from_file_interactive() -> None: n = _read_int("Введите N (1..15): ", min_value=1, max_value=15) grid = read_maze_from_file(n) print_maze_with_coords(grid) x = _read_int("Введите строку (1..N): ", min_value=1, max_value=n) y = _read_int("Введите колонку (1..N): ", min_value=1, max_value=n) start = (x - 1, y - 1) path = a_star_shortest_path(grid, start) print("Путь:", path) def main() -> None: while True: print() print("Меню:") print("1) Создать лабиринт") print("2) Показать лабиринт") print("3) Найти путь") print("0) Выход") try: choice = input("Выберите пункт: ").strip() except EOFError: return try: if choice == "0": return if choice == "1": generate_maze_file_interactive() elif choice == "2": print_maze_file_interactive() elif choice == "3": solve_from_file_interactive() else: print("Неизвестный пункт меню.") except Exception as e: print(f"Ошибка: {e}") if __name__ == "__main__": main()