# 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