# Отчёт по лабораторной работе «Поиск выхода из лабиринта»

## 1. Цель работы

Разработать гибкую расширяемую программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. В ходе работы применены паттерны проектирования GoF: **Builder**, **Strategy**, **Observer**, **Command**.

Реализованные алгоритмы поиска пути:

- **BFS** (поиск в ширину) — гарантирует кратчайший путь по числу шагов.
- **DFS** (поиск в глубину) — не гарантирует кратчайший путь, но быстрее при удачном порядке соседей.
- **A\*** (с манхэттенской эвристикой) — направленный поиск, учитывает веса клеток.
- **Dijkstra** — оптимален для взвешенных графов, без эвристики.

Тестовые лабиринты:

- Маленький 10×10 — простой путь.
- Средний 50×50 — с тупиками (алгоритм Прима).
- Большой 100×100 — запутанная структура.
- Пустой 30×30 — без стен, демонстрирует максимальную нагрузку.
- Без выхода 20×20 — старт и выход разделены глухой стеной.
- Взвешенный 40×40 — клетки с разным весом: асфальт (1), песок (2), болото (3).

Каждый эксперимент повторялся 7 раз, результаты усреднены.

## 2. Описание паттернов

| Паттерн | Классы | Назначение |
|---|---|---|
| Builder | `MazeBuilder`, `TextFileMazeBuilder` | Скрывает парсинг файла; новый формат = новый класс |
| Strategy | `PathFindingStrategy`, BFS/DFS/A\*/Dijkstra | Смена алгоритма одной строкой без изменения остального кода |
| Observer | `Observer`, `ConsoleView` | Визуализация отделена от логики поиска |
| Command | `Command`, `MoveCommand`, `CommandHistory` | Пошаговое управление игроком с поддержкой undo |

## 3. Результаты экспериментов

Усреднённые значения (7 повторений) представлены в таблице:

| Лабиринт | Алгоритм | Время, мс | Посещено клеток | Длина пути |
|---|---|---|---|---|
| 10×10 | BFS | 0.081 | 28 | 21 |
| 10×10 | DFS | 0.053 | 22 | 21 |
| 10×10 | A\* | 0.088 | 24 | 21 |
| 10×10 | Dijkstra | 0.672 | 28 | 21 |
| 50×50 | BFS | 1.150 | 493 | 257 |
| 50×50 | DFS | 0.614 | 263 | 257 |
| 50×50 | A\* | 1.220 | 357 | 257 |
| 50×50 | Dijkstra | 1.685 | 493 | 257 |
| 100×100 | BFS | 11.378 | 4783 | 1953 |
| 100×100 | DFS | 5.141 | 2161 | 1953 |
| 100×100 | A\* | 18.019 | 4741 | 1953 |
| 100×100 | Dijkstra | 17.489 | 4783 | 1953 |
| 30×30 пустой | BFS | 1.832 | 784 | 55 |
| 30×30 пустой | DFS | 1.151 | 433 | 379 |
| 30×30 пустой | A\* | 3.748 | 784 | 55 |
| 30×30 пустой | Dijkstra | 3.945 | 784 | 55 |
| 20×20 без выхода | BFS | 0.370 | 162 | — |
| 20×20 без выхода | DFS | 0.373 | 162 | — |
| 20×20 без выхода | A\* | 0.708 | 162 | — |
| 20×20 без выхода | Dijkstra | 0.677 | 162 | — |
| 40×40 взвешенный | BFS | 1.104 | 533 | 321 |
| 40×40 взвешенный | DFS | 0.774 | 361 | 321 |
| 40×40 взвешенный | A\* | 1.516 | 452 | 321 |
| 40×40 взвешенный | Dijkstra | 1.725 | 533 | 321 |

Графическое представление результатов приведено на рисунке ниже.

![Сравнение производительности](performance_comparison.png)

## 4. Анализ результатов

### 4.1. BFS

Гарантирует кратчайший путь по числу шагов. Исследует все клетки на расстоянии d перед переходом к d+1, поэтому число посещённых клеток максимально среди всех алгоритмов — на лабиринте 100×100 это 4783 клетки. На пустом лабиринте 30×30 BFS находит оптимальный путь длиной 55 клеток, тогда как DFS даёт 379. Время растёт линейно с размером: от 0.08 мс (10×10) до 11.4 мс (100×100).

### 4.2. DFS

Самый быстрый алгоритм по времени: на лабиринте 100×100 — 5.1 мс против 11.4 мс у BFS. Посещает вдвое меньше клеток (2161 против 4783), так как уходит глубоко в одном направлении. Однако на пустом лабиринте DFS даёт путь в 7 раз длиннее оптимального (379 против 55) — алгоритм уходит в угол и обходит весь лабиринт по периметру. Оптимальная длина пути при этом совпадает с BFS только в лабиринтах-лабиринтах (50×50 и 100×100), где единственный путь — сам.

### 4.3. A\*

На невзвешенных лабиринтах находит тот же кратчайший путь, что BFS, но исследует на 20–30% меньше клеток благодаря манхэттенской эвристике (на 50×50: 357 против 493). Однако на большом лабиринте 100×100 оказывается медленнее BFS (18 мс против 11 мс) из-за накладных расходов на `heapq`. На взвешенном лабиринте A\* корректно учитывает стоимость клеток, в отличие от BFS.

### 4.4. Dijkstra

На невзвешенных лабиринтах полностью совпадает с BFS по посещённым клеткам и длине пути, но медленнее из-за `heapq` вместо `deque`. На взвешенном лабиринте 40×40 корректно минимизирует суммарный вес пути. Практически вытесняется A\* везде, где цель заранее известна.

### 4.5. Лабиринт «без выхода»

Все алгоритмы обходят все 162 доступные клетки левой секции и возвращают пустой путь. Реализация корректно обрабатывает этот случай через событие `no_path` для Observer. A\* и Dijkstra работают медленнее (0.7 мс против 0.37 мс у BFS/DFS) из-за накладных расходов `heapq` при полном обходе без нахождения цели.

## 5. Выводы и рекомендации

На основе полученных результатов можно сформулировать следующие рекомендации:

- **Кратчайший путь в невзвешенном лабиринте → BFS.** Гарантированный результат, простая реализация на `deque`, линейное масштабирование.

- **Максимальная скорость, длина пути не критична → DFS.** В 2 раза быстрее BFS на больших лабиринтах, посещает вдвое меньше клеток. Не использовать на открытых пространствах — путь может быть многократно длиннее оптимального.

- **Взвешенный граф, цель известна → A\*.** Направленный поиск + учёт весов. На небольших и средних лабиринтах быстрее и экономнее BFS. На очень больших (100×100+) накладные расходы `heapq` могут перевесить выигрыш от эвристики.

- **Взвешенный граф, нужны все кратчайшие расстояния → Dijkstra.** Оптимален без целевой точки. С целевой точкой предпочтительнее A\*.

**Итог:** для навигации в лабиринте с одним выходом оптимален BFS (гарантия) или DFS (скорость). A\* предпочтителен при взвешенных клетках. Паттерн Strategy позволяет переключать алгоритмы без изменения остального кода — `solver.set_strategy(AStarStrategy())`.


