# Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования) ## Цель работы Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 3 паттерна проектирования из списка GoF и обосновать их выбор. --- ## Применённые паттерны проектирования ### 1. Builder (Строитель) — `maze_builder.py` **Задача:** загрузка лабиринта из файла — сложный процесс (парсинг, валидация, расстановка старта/выхода). **Решение:** интерфейс `MazeBuilder` с методом `build_from_file()` и реализация `TextFileMazeBuilder`. Клиентский код работает только с интерфейсом и не знает деталей парсинга. **Преимущество:** чтобы добавить поддержку JSON или бинарного формата — достаточно создать новый класс, не трогая ничего остального. ### 2. Strategy (Стратегия) — `maze_strategies.py` **Задача:** несколько алгоритмов поиска пути (BFS, DFS, A*) нужно переключать без изменения кода оркестратора. **Решение:** интерфейс `PathFindingStrategy` с методом `find_path()`. Каждый алгоритм — отдельный класс. `MazeSolver.set_strategy()` меняет алгоритм в одну строку. **Преимущество:** новый алгоритм (например, Dijkstra) добавляется реализацией интерфейса, без правок в `MazeSolver`. ### 3. Observer (Наблюдатель) — `maze_solver.py` **Задача:** отображать события (путь найден, игрок переместился) без жёсткой связи между логикой и интерфейсом. **Решение:** интерфейс `Observer` с методом `update(event, data)`. `ConsoleView` подписывается на `MazeSolver` и реагирует на события `path_found`, `maze_loaded`, `move`. **Преимущество:** можно добавить графический интерфейс или логгер, не меняя логику решателя. ### 4. Command (Команда) — `maze_solver.py` **Задача:** пошаговое перемещение игрока с возможностью отмены хода. **Решение:** интерфейс `Command` с `execute()` и `undo()`. `MoveCommand` хранит предыдущую клетку и умеет откатить ход. `Player` хранит текущую позицию. **Преимущество:** история команд позволяет реализовать `Ctrl+Z` для любого количества шагов. --- ## Диаграмма классов (Mermaid) ```mermaid classDiagram class MazeBuilder { <> +build_from_file(filename) Maze } class TextFileMazeBuilder { +build_from_file(filename) Maze } class Maze { -int width, height -Cell[][] cells -Cell start -Cell exit +get_cell(x, y) Cell +get_neighbors(cell) list +render(path, player_pos) } class Cell { -int x, y -bool is_wall -bool is_start -bool is_exit +is_passable() bool } class PathFindingStrategy { <> +find_path(maze, start, exit) list } class BFSStrategy { +find_path() } class DFSStrategy { +find_path() } class AStarStrategy { +find_path() } class MazeSolver { -Maze maze -PathFindingStrategy strategy -list observers +set_strategy(strategy) +add_observer(observer) +solve() SearchStats } class SearchStats { +float time_ms +int visited_cells +int path_length +list path } class Observer { <> +update(event, data) } class ConsoleView { +update(event, data) } class Command { <> +execute() +undo() } class MoveCommand { -Player player -Cell target_cell -Cell previous_cell +execute() +undo() } class Player { -Cell current_cell +move_to(cell) } MazeBuilder <|.. TextFileMazeBuilder TextFileMazeBuilder ..> Maze : creates Maze o-- Cell PathFindingStrategy <|.. BFSStrategy PathFindingStrategy <|.. DFSStrategy PathFindingStrategy <|.. AStarStrategy MazeSolver --> Maze MazeSolver --> PathFindingStrategy MazeSolver --> SearchStats MazeSolver --> Observer Observer <|.. ConsoleView Command <|.. MoveCommand MoveCommand --> Player Player --> Cell ``` --- ## Экспериментальная часть ### Параметры эксперимента | Параметр | Значение | |---|---| | Повторений на замер | 7 | | Алгоритмы | BFS, DFS, A* | ### Тестовые лабиринты | Название | Размер | Особенность | |---|---|---| | small_10x10 | 10×10 | Маленький, простой путь | | medium_50x50 | 50×50 | Средний, тупики (28% стен) | | large_100x100 | 100×100 | Большой (30% стен) | | open_50x50 | 50×50 | Без внутренних стен | | no_exit_20x20 | 20×20 | Выход недостижим | --- ## Результаты ### Таблица средних значений | Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути | |---|---|---|---|---| | small_10x10 | BFS | 0.094 | 54 | 15 | | small_10x10 | DFS | 0.059 | 33 | 33 | | small_10x10 | A* | 0.078 | 36 | 15 | | medium_50x50 | BFS | 2.446 | 1639 | 95 | | medium_50x50 | DFS | 1.480 | 1063 | 185 | | medium_50x50 | A* | 1.528 | 588 | 95 | | large_100x100 | BFS | 9.891 | 6564 | — | | large_100x100 | DFS | 9.057 | 6564 | — | | large_100x100 | A* | 17.578 | 6564 | — | | open_50x50 | BFS | 3.296 | 2304 | 95 | | open_50x50 | DFS | 1.830 | 1223 | 1129 | | open_50x50 | A* | 5.566 | 2304 | 95 | | no_exit_20x20 | BFS | 0.368 | 260 | — | | no_exit_20x20 | DFS | 0.343 | 260 | — | | no_exit_20x20 | A* | 0.607 | 260 | — | *«—» означает путь не найден (все доступные клетки исчерпаны)* ### Визуализация ![Время выполнения](data/chart_время-мс.png) ![Посещено клеток](data/chart_посещено-клеток.png) ![Длина пути](data/chart_длина-пути.png) --- ## Анализ результатов ### 1. BFS — оптимальный путь, высокое покрытие BFS всегда находит **кратчайший путь** (15 шагов на small, 95 на medium). Но для этого он обходит больше клеток, чем DFS: на medium_50x50 посетил 1639 против 1063 у DFS. Это нормально — BFS расширяется волнами во все стороны. ### 2. DFS — быстрый по времени, длинный путь DFS посещает меньше клеток в среднем, но путь получается значительно длиннее: 185 шагов против 95 у BFS на том же лабиринте. На открытом лабиринте без стен DFS нашёл путь в **1129 шагов** вместо 95 у BFS — наглядная демонстрация того, что DFS не гарантирует оптимальности. ### 3. A* — меньше всего посещённых клеток На medium_50x50 A* посетил всего **588 клеток** против 1639 у BFS — в 2.8 раза меньше. При этом путь тот же оптимальный (95 шагов). Манхэттенская эвристика направляет поиск к выходу и отсекает лишние направления. На открытом лабиринте без стен A* тратит больше времени (5.566 мс против 3.296 мс у BFS) — эвристика считается для каждого узла, а без препятствий нет выигрыша в отсечении. ### 4. Большой лабиринт (100×100) — путь не найден Все три алгоритма исчерпали все 6564 доступные клетки и не нашли пути. При плотности стен 30% на данном лабиринте выход оказался недостижим. Все алгоритмы корректно вернули пустой результат. ### 5. Лабиринт без выхода Все алгоритмы корректно обработали случай недостижимого выхода, посетив все 260 доступных клеток. --- ## Выводы ### Когда какой алгоритм выбирать | Задача | Рекомендация | |---|---| | Нужен кратчайший путь | BFS или A* | | Нужно быстро найти хоть какой-то путь | DFS | | Большой лабиринт, нужна оптимальность | A* (посещает меньше клеток) | | Лабиринт без препятствий | BFS (A* теряет преимущество) | | Обнаружить недостижимый выход | Любой — все обходят все клетки | ### Как ООП и паттерны помогли **Без паттернов** весь код был бы в одной функции: парсинг, алгоритм и вывод перемешаны. Добавление нового алгоритма требовало бы правки основного кода. **С паттернами:** - `Builder` — смена формата файла (txt → JSON) не затрагивает логику поиска - `Strategy` — новый алгоритм добавляется одним классом без правок `MazeSolver` - `Observer` — `ConsoleView` отключается или заменяется GUI без правок логики - `Command` — история ходов и отмена реализуются без изменения `Player` Каждый класс отвечает за одну вещь, код можно тестировать по частям независимо.