# Этап 3. Стратегии поиска пути

В третьем этапе реализованы алгоритмы поиска пути с применением паттерна **Strategy**.

## Паттерн Strategy

Все три алгоритма реализуют один интерфейс `PathFindingStrategy`. Это позволяет переключать алгоритм в любой момент без изменения кода клиента — достаточно передать другой объект стратегии:

```python
solver = MazeSolver(maze, BFSStrategy())
solver.set_strategy(AStarStrategy())
```

Новый алгоритм добавляется реализацией интерфейса — остальной код трогать не нужно.

## Структура пакета

Стратегии разбиты по отдельным файлам, а `__init__.py` собирает всё в один импорт:

```
source/strategy/
├── __init__.py       ← единственный импорт для пользователя
├── algorithms.py     ← базовый класс PathFindingStrategy
├── bfs.py
├── dfs.py
└── astar.py
```

```python
from source.strategy import BFSStrategy, DFSStrategy, AStarStrategy
```

## Класс `PathFindingStrategy`

Абстрактный базовый класс — интерфейс паттерна. Объявляет абстрактный метод `find_path()` и содержит два вспомогательных метода, общих для всех стратегий.

По условию задания метод назывался `findPath` — приведён к **PEP 8** как `find_path`.

### `_validate`

Добавлен в процессе разработки — изначально в задании не было требования к обработке отсутствия старта или выхода. Проблема проявилась при тестировании лабиринтов типа `noexit`: алгоритм падал с `AttributeError` внутри, вместо понятного сообщения.

`_validate` подставляет `start` и `exit` из лабиринта если они не переданы явно, и выбрасывает `ValueError` с понятным сообщением если клетки не найдены:

```python
start, exit = self._validate(maze, start, exit)
```

Вынесен в базовый класс чтобы не дублировать в каждом алгоритме.

### `_reconstruct_path`

Восстанавливает путь по словарю предков `came_from`. Все три алгоритма строят этот словарь одинаково — `{клетка: откуда_пришли}` — поэтому восстановление вынесено в общий метод базового класса.

Алгоритм идёт от выхода к старту по цепочке предков, затем разворачивает список:

```
exit → D → C → B → start   (идём по came_from)
start → B → C → D → exit   (после reverse)
```

## Алгоритмы

### BFS — `BFSStrategy`

Поиск в ширину. Использует `deque` как очередь (FIFO) — каждый раз берём самую старую клетку из начала. Это гарантирует послойный обход и кратчайший путь по количеству шагов.

Сложность: O(V + E) по времени и памяти.

### DFS — `DFSStrategy`

Поиск в глубину. Использует `list` как стек (LIFO) — каждый раз берём самую свежую клетку с конца. Алгоритм ныряет вглубь по одному направлению до тупика, затем возвращается.

Не гарантирует кратчайший путь. На запутанных лабиринтах может обойти почти все клетки прежде чем найти выход, хотя по времени часто быстрее BFS из-за меньших накладных расходов на структуру данных.

Сложность: O(V + E) по времени и памяти.

### A* — `AStarStrategy`

Использует `heapq` как приоритетную очередь. На каждом шаге выбирает клетку с минимальным значением `f = g + h`, где `g` — стоимость пути от старта, `h` — манхэттенская эвристика до выхода.

Эвристика направляет поиск в сторону выхода, поэтому A* обходит меньше клеток чем BFS при том же гарантированно кратчайшем пути.

В кортеж приоритетной очереди добавлен счётчик `counter` как tie-breaker — без него `heapq` попытался бы сравнивать объекты `Cell` при одинаковом `f`, что вызвало бы `TypeError`:

```python
heapq.heappush(open_heap, (f, counter, neighbor))
```

Сложность: O(E · log V) в худшем случае.