86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
|
|
"""
|
|||
|
|
Этап 4: Класс-оркестратор MazeSolver.
|
|||
|
|
|
|||
|
|
Принимает лабиринт и стратегию, запускает поиск,
|
|||
|
|
собирает статистику и уведомляет наблюдателей.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import time
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
|
|||
|
|
from maze_model import Maze, Cell
|
|||
|
|
from strategies import PathFindingStrategy
|
|||
|
|
from observer import Observer
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class SearchStats:
|
|||
|
|
"""Результаты одного запуска поиска."""
|
|||
|
|
time_ms: float # время выполнения в миллисекундах
|
|||
|
|
visited_cells: int # количество посещённых клеток
|
|||
|
|
path_length: int # длина найденного пути (0 если не найден)
|
|||
|
|
path: list[Cell] # сам путь
|
|||
|
|
|
|||
|
|
|
|||
|
|
class MazeSolver:
|
|||
|
|
"""
|
|||
|
|
Оркестратор: связывает лабиринт, стратегию и наблюдателей.
|
|||
|
|
|
|||
|
|
Паттерны внутри:
|
|||
|
|
- Strategy: алгоритм задаётся снаружи через set_strategy()
|
|||
|
|
- Observer: подписчики получают события о ходе поиска
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, maze: Maze, strategy: PathFindingStrategy | None = None):
|
|||
|
|
self.maze = maze
|
|||
|
|
self.strategy = strategy
|
|||
|
|
self._observers: list[Observer] = []
|
|||
|
|
|
|||
|
|
# ── Strategy ──────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
|||
|
|
"""Динамически меняет алгоритм поиска."""
|
|||
|
|
self.strategy = strategy
|
|||
|
|
|
|||
|
|
# ── Observer ──────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def add_observer(self, observer: Observer) -> None:
|
|||
|
|
self._observers.append(observer)
|
|||
|
|
|
|||
|
|
def remove_observer(self, observer: Observer) -> None:
|
|||
|
|
self._observers.remove(observer)
|
|||
|
|
|
|||
|
|
def _notify(self, event: dict) -> None:
|
|||
|
|
for obs in self._observers:
|
|||
|
|
obs.update(event)
|
|||
|
|
|
|||
|
|
# ── Solve ─────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def solve(self) -> SearchStats:
|
|||
|
|
"""Запускает поиск пути и возвращает статистику."""
|
|||
|
|
if self.strategy is None:
|
|||
|
|
raise RuntimeError("Стратегия не задана. Используйте set_strategy().")
|
|||
|
|
|
|||
|
|
self._notify({"type": "maze_loaded", "maze": self.maze})
|
|||
|
|
|
|||
|
|
t_start = time.perf_counter()
|
|||
|
|
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
|||
|
|
t_end = time.perf_counter()
|
|||
|
|
|
|||
|
|
time_ms = (t_end - t_start) * 1000
|
|||
|
|
visited = getattr(self.strategy, "visited_count", 0)
|
|||
|
|
|
|||
|
|
stats = SearchStats(
|
|||
|
|
time_ms=time_ms,
|
|||
|
|
visited_cells=visited,
|
|||
|
|
path_length=len(path),
|
|||
|
|
path=path,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if path:
|
|||
|
|
self._notify({"type": "path_found", "maze": self.maze, "path": path})
|
|||
|
|
else:
|
|||
|
|
self._notify({"type": "no_path"})
|
|||
|
|
|
|||
|
|
return stats
|