Files
labs/labPython/lab20/task_1.py
2026-05-02 20:27:02 +05:00

240 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()