# maze_solver/builders.py from abc import ABC, abstractmethod from models import Cell, Maze class MazeBuilder(ABC): """Интерфейс строителя лабиринта.""" @abstractmethod def buildFromFile(self, filename: str) -> Maze: """Строит объект Maze из файла.""" pass class TextFileMazeBuilder(MazeBuilder): """Строитель для текстового формата: ■ стена, ' ' проход, S старт, E выход.""" # Поддерживаемые символы стен WALL_SYMBOLS = {'#', '■', '█', '▓', '▒', '░'} def __init__(self, require_exit: bool = True): """ Args: require_exit: Если False, позволяет создавать лабиринты без выхода """ self.require_exit = require_exit def buildFromFile(self, filename: str) -> Maze: with open(filename, 'r', encoding='utf-8') as f: lines = f.readlines() # Убираем символы новой строки и пустые строки в конце файла cleaned_lines = [line.rstrip('\n') for line in lines if line.strip() != ''] if not cleaned_lines: raise ValueError("Файл лабиринта пуст") height = len(cleaned_lines) width = len(cleaned_lines[0]) grid = [] start_cell = None exit_cell = None for y, line in enumerate(cleaned_lines): row = [] if len(line) != width: raise ValueError( f"Строка {y} имеет длину {len(line)}, ожидалось {width}. " f"Лабиринт должен быть прямоугольным." ) for x, char in enumerate(line): is_wall = char in self.WALL_SYMBOLS cell = Cell(x, y, is_wall) if char == 'S': cell.isStart = True start_cell = cell elif char == 'E': cell.isExit = True exit_cell = cell row.append(cell) grid.append(row) if not start_cell: raise ValueError("В лабиринте не найдена стартовая позиция (S)") if self.require_exit and not exit_cell: raise ValueError("В лабиринте не найдена выходная позиция (E)") maze = Maze(width, height, grid) maze.start = start_cell maze.exit = exit_cell return maze