76 lines
2.5 KiB
Python
76 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
|
|
from .models import Cell, Maze
|
|
|
|
|
|
class MazeBuilder(ABC):
|
|
@abstractmethod
|
|
def build_from_file(self, filename: str | Path) -> Maze:
|
|
raise NotImplementedError
|
|
|
|
def buildFromFile(self, filename: str | Path) -> Maze:
|
|
return self.build_from_file(filename)
|
|
|
|
|
|
class TextFileMazeBuilder(MazeBuilder):
|
|
WALL = "#"
|
|
START = "S"
|
|
EXIT = "E"
|
|
PASSAGES = {" ", "."}
|
|
WEIGHTS = {"1": 1, "2": 2, "3": 3, "~": 3}
|
|
|
|
def build_from_file(self, filename: str | Path) -> Maze:
|
|
path = Path(filename)
|
|
rows = path.read_text(encoding="utf-8").splitlines()
|
|
|
|
if not rows:
|
|
raise ValueError(f"Maze file is empty: {path}")
|
|
|
|
width = len(rows[0])
|
|
if width == 0:
|
|
raise ValueError("Maze width must be greater than zero")
|
|
if any(len(row) != width for row in rows):
|
|
raise ValueError("All maze rows must have the same width")
|
|
|
|
cells: list[list[Cell]] = []
|
|
start: Cell | None = None
|
|
exit: Cell | None = None
|
|
|
|
for y, row in enumerate(rows):
|
|
cell_row: list[Cell] = []
|
|
for x, char in enumerate(row):
|
|
cell = self._create_cell(x, y, char)
|
|
if cell.is_start:
|
|
if start is not None:
|
|
raise ValueError("Maze must contain exactly one start cell")
|
|
start = cell
|
|
if cell.is_exit:
|
|
if exit is not None:
|
|
raise ValueError("Maze must contain exactly one exit cell")
|
|
exit = cell
|
|
cell_row.append(cell)
|
|
cells.append(cell_row)
|
|
|
|
if start is None:
|
|
raise ValueError("Maze must contain a start cell marked with 'S'")
|
|
if exit is None:
|
|
raise ValueError("Maze must contain an exit cell marked with 'E'")
|
|
|
|
return Maze(cells, start, exit)
|
|
|
|
def _create_cell(self, x: int, y: int, char: str) -> Cell:
|
|
if char == self.WALL:
|
|
return Cell(x=x, y=y, is_wall=True, symbol=char)
|
|
if char == self.START:
|
|
return Cell(x=x, y=y, is_start=True, symbol=char)
|
|
if char == self.EXIT:
|
|
return Cell(x=x, y=y, is_exit=True, symbol=char)
|
|
if char in self.PASSAGES:
|
|
return Cell(x=x, y=y, symbol=" ")
|
|
if char in self.WEIGHTS:
|
|
return Cell(x=x, y=y, weight=self.WEIGHTS[char], symbol=char)
|
|
raise ValueError(f"Unsupported maze symbol {char!r} at ({x}, {y})")
|