2026-rff_mp/SobolevNS/docs/data/task2_maze/maze_solver/builder.py
2026-05-22 13:42:42 +03:00

93 lines
3.5 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_solver/builder.py - паттерн Builder для создания лабиринтов.
Зачем Builder: процесс построения лабиринта сложный (чтение файла, парсинг,
валидация символов, простановка флагов, поиск старта и выхода). Builder
изолирует эти подробности от клиента; для нового формата (JSON, бинарный)
достаточно реализовать ещё один builder с тем же интерфейсом.
"""
from abc import ABC, abstractmethod
from .model import Cell, Maze
class MazeBuilder(ABC):
"""Абстрактный билдер лабиринта."""
@abstractmethod
def build_from_file(self, filename) -> Maze:
"""Возвращает готовый Maze."""
class TextFileMazeBuilder(MazeBuilder):
"""Билдер из текстового формата.
Символы:
'#' - стена
' ' - проход (вес 1)
'S' - старт (проходим)
'E' - выход (проходим)
'.' - асфальт (вес 1) - то же, что пробел
',' - песок (вес 2)
'~' - болото (вес 3)
Лишние пробельные символы в начале/конце файла игнорируются,
но внутри строки пробелы значимы (это проходы).
"""
WEIGHT_MAP = {'.': 1, ',': 2, '~': 3}
def build_from_file(self, filename) -> Maze:
with open(filename, encoding="utf-8") as f:
raw = f.read().splitlines()
# отбрасываем пустые строки в конце - частая мелочь
while raw and raw[-1] == "":
raw.pop()
if not raw:
raise ValueError(f"Файл лабиринта {filename!r} пуст.")
height = len(raw)
width = max(len(line) for line in raw)
# выравниваем строки по ширине пробелами (если строки разной длины)
lines = [line.ljust(width, '#') for line in raw]
maze = Maze(width, height)
start_count = 0
exit_count = 0
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = self._parse_char(x, y, ch)
maze.grid[y][x] = cell
if cell.is_start:
maze.start = cell
start_count += 1
if cell.is_exit:
maze.exit_ = cell
exit_count += 1
# валидация
if start_count != 1:
raise ValueError(
f"В лабиринте {filename!r} ожидался ровно 1 'S', нашли {start_count}.")
if exit_count != 1:
raise ValueError(
f"В лабиринте {filename!r} ожидался ровно 1 'E', нашли {exit_count}.")
return maze
def _parse_char(self, x, y, ch):
if ch == '#':
return Cell(x, y, is_wall=True)
if ch == 'S':
return Cell(x, y, is_start=True, weight=1)
if ch == 'E':
return Cell(x, y, is_exit=True, weight=1)
if ch in self.WEIGHT_MAP:
return Cell(x, y, weight=self.WEIGHT_MAP[ch])
if ch == ' ':
return Cell(x, y, weight=1)
raise ValueError(f"Неизвестный символ {ch!r} в позиции ({x},{y}).")