""" Этап 2: Паттерн Builder — загрузка лабиринта из файла. Формат файла: # — стена ' ' (пробел) — проход S — старт E — выход """ from abc import ABC, abstractmethod from maze_model import Cell, Maze class MazeBuilder(ABC): """Интерфейс строителя лабиринта (паттерн Builder).""" @abstractmethod def build_from_file(self, filename: str) -> Maze: """Читает файл и возвращает готовый объект Maze.""" ... class TextFileMazeBuilder(MazeBuilder): """ Конкретный строитель: читает текстовый файл и строит Maze. Зачем Builder? Процесс построения многошаговый: чтение, парсинг, валидация, поиск старта/выхода, создание объектов Cell. Builder скрывает эту сложность от клиента. Чтобы добавить JSON-формат достаточно написать JsonMazeBuilder, не трогая остальной код. """ def build_from_file(self, filename: str) -> Maze: with open(filename, "r", encoding="utf-8") as f: lines = f.read().splitlines() if not lines: raise ValueError("Файл лабиринта пуст.") height = len(lines) width = max(len(line) for line in lines) # Дополняем строки до одинаковой длины (стенами) lines = [line.ljust(width, "#") for line in lines] cells: list[list[Cell]] = [] start: Cell | None = None exit_cell: Cell | None = None for y, line in enumerate(lines): row = [] for x, ch in enumerate(line): is_wall = ch == "#" is_start = ch == "S" is_exit = ch == "E" cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) if is_start: start = cell if is_exit: exit_cell = cell row.append(cell) cells.append(row) if start is None: raise ValueError("Лабиринт не содержит стартовой клетки (S).") if exit_cell is None: raise ValueError("Лабиринт не содержит выхода (E).") return Maze(width, height, cells, start, exit_cell)