# Этап 4. Класс-оркестратор MazeSolver

В четвёртом этапе реализован класс `MazeSolver`, который объединяет лабиринт и стратегию поиска, выполняет поиск и собирает статистику.

## Роль в архитектуре

`MazeSolver` — точка входа для клиентского кода. Он не знает деталей ни одного алгоритма и не работает напрямую с клетками лабиринта — только делегирует задачу стратегии и замеряет время:

```python
solver = MazeSolver(maze, BFSStrategy())
stats = solver.solve()
print(stats)
# Время: 0.041 мс | Посещено клеток: 13 | Длина пути: 13
```

## Класс `SearchStats`

Оформлен через `@dataclass` — это избавляет от ручного `__init__` и автоматически даёт `__repr__`. Хранит четыре поля: время выполнения, количество посещённых клеток, длину пути и сам путь как список клеток.

`__str__` переопределён для удобного вывода в консоль и отчётах.

### Ограничение

В текущей реализации `visited_count` и `path_length` всегда равны друг другу — оба вычисляются как `len(path)`. Это потому что стратегии возвращают только финальный путь, а не все посещённые клетки. Чтобы получить точное количество посещений, потребовалось бы дорабатывать каждую стратегию — добавлять счётчик внутри `find_path`. На данном этапе это сознательное упрощение.

## Класс `MazeSolver`

### `set_strategy`

Позволяет менять алгоритм без пересоздания солвера. Это и есть основная демонстрация паттерна Strategy в действии — один объект, разные алгоритмы:

```python
solver = MazeSolver(maze, BFSStrategy())
stats_bfs = solver.solve()

solver.set_strategy(AStarStrategy())
stats_astar = solver.solve()
```

### `solve`

Замеряет время через `time.perf_counter()` — самый точный таймер в Python для коротких интервалов, не зависящий от системных часов. Результат переводится в миллисекунды умножением на 1000.

`start` и `exit` можно не передавать — стратегия найдёт их сама через `_validate`. Явная передача нужна только если хочется запустить поиск не от стандартного старта, а от произвольной клетки.
