2026-rff_mp/pogodinda/lab2/src/maze_builder.py

121 lines
4.4 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.

# maze_builder.py
from abc import ABC, abstractmethod
from maze import Maze, Cell
class MazeBuilder(ABC):
"""Интерфейс строителя лабиринта (Builder pattern)."""
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
"""Принимает путь к файлу, возвращает готовый Maze."""
pass
class TextFileMazeBuilder(MazeBuilder):
"""
Строитель лабиринта из текстового файла.
Формат файла:
# — стена
. или пробел — проход
S — старт
E — выход
"""
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
if not lines:
raise ValueError("Файл лабиринта пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
start_found = False
exit_found = False
for y, line in enumerate(lines):
for x, char in enumerate(line):
is_wall = (char == '#')
is_start = (char == 'S')
is_exit = (char == 'E')
if is_start:
start_found = True
if is_exit:
exit_found = True
cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
maze.set_cell(x, y, cell)
if not start_found or not exit_found:
raise ValueError("Лабиринт должен содержать старт (S) и выход (E)")
return maze
class RandomMazeBuilder(MazeBuilder):
"""
Строитель случайного лабиринта.
Алгоритм: рекурсивный бэктрекинг.
"""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
def build_from_file(self, filename: str = None) -> Maze:
"""
filename игнорируется — лабиринт генерируется случайно.
Название метода общее для всех Builder'ов.
"""
import random
maze = Maze(self.width, self.height)
# Шаг 1: Заполняем всё стенами
for y in range(self.height):
for x in range(self.width):
maze.set_cell(x, y, Cell(x, y, is_wall=True))
# Шаг 2: Рекурсивно прокладываем пути
visited = set()
def carve(x, y):
"""Прокладывает проход из точки (x, y)."""
visited.add((x, y))
maze.set_cell(x, y, Cell(x, y, is_wall=False))
# 4 направления, перемешанные случайно
directions = [(0, -2), (0, 2), (-2, 0), (2, 0)]
random.shuffle(directions)
for dx, dy in directions:
nx, ny = x + dx, y + dy
# Проверяем границы и что ещё не посещали
if (0 <= nx < self.width and 0 <= ny < self.height
and (nx, ny) not in visited):
# Убираем стену между текущей и новой клеткой
wall_x = x + dx // 2
wall_y = y + dy // 2
maze.set_cell(wall_x, wall_y, Cell(wall_x, wall_y, is_wall=False))
# Рекурсия в новую клетку
carve(nx, ny)
# Начинаем с (1, 1) — нечётные координаты для коридоров
carve(1, 1)
# Шаг 3: Устанавливаем старт и выход в углах лабиринта
maze.set_cell(1, 1, Cell(1, 1, is_wall=False, is_start=True))
maze.set_cell(
self.width - 2, self.height - 2,
Cell(self.width - 2, self.height - 2, is_wall=False, is_exit=True)
)
return maze