""" maze_solver/solver.py - оркестратор MazeSolver + паттерн Observer. MazeSolver знает лабиринт и текущую стратегию (Strategy). Перед поиском он уведомляет наблюдателей (Observer) о старте, после поиска - о результате. """ import time from abc import ABC, abstractmethod # ---------- Observer ---------- class Observer(ABC): """Интерфейс наблюдателя.""" @abstractmethod def update(self, event): """event - dict с ключом 'type' и сопровождающими данными.""" class ConsoleView(Observer): """Простой текстовый наблюдатель.""" def __init__(self, verbose=True): self.verbose = verbose def update(self, event): if not self.verbose: return t = event["type"] if t == "maze_loaded": m = event["maze"] print(f"[ConsoleView] лабиринт {m.width}x{m.height} загружен") elif t == "search_start": print(f"[ConsoleView] старт поиска: {event['strategy']}") elif t == "search_end": stats = event["stats"] print(f"[ConsoleView] поиск окончен: путь={stats['path_length']}, " f"посещено={stats['visited']}, время={stats['elapsed_ms']:.3f} мс") elif t == "move": print(f"[ConsoleView] игрок -> ({event['x']},{event['y']})") elif t == "path_found": print("[ConsoleView] путь найден") elif t == "no_path": print("[ConsoleView] пути нет") # ---------- MazeSolver ---------- class SearchStats(dict): """Простой dict-подобный контейнер статистики поиска.""" pass class MazeSolver: def __init__(self, maze, strategy=None): self.maze = maze self.strategy = strategy self._observers = [] def set_strategy(self, strategy): self.strategy = strategy def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def _notify(self, event): for obs in self._observers: obs.update(event) def solve(self): if self.strategy is None: raise RuntimeError("Стратегия не задана") if self.maze.start is None or self.maze.exit_ is None: raise RuntimeError("В лабиринте нет старта или выхода") self._notify({"type": "search_start", "strategy": self.strategy.name}) t0 = time.perf_counter() result = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit_) elapsed = (time.perf_counter() - t0) * 1000.0 path = result["path"] stats = SearchStats( strategy=self.strategy.name, elapsed_ms=elapsed, visited=result["visited"], path_length=len(path), path=path, ) self._notify({"type": "search_end", "stats": stats}) if path: self._notify({"type": "path_found"}) else: self._notify({"type": "no_path"}) return stats