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

121 lines
4.4 KiB
Python
Raw Normal View History

# 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