1. Постановка задачи и цели Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Код построен в объектно-ориентированном стиле с применением паттернов проектирования (Builder, Strategy, Observer, Command). Цели: Обеспечить лёгкую замену формата лабиринта (Builder). Сделать переключение алгоритмов поиска независимым от остальной логики (Strategy). Организовать уведомление наблюдателей о событиях (Observer). Инкапсулировать действия игрока с возможностью отмены (Command). Экспериментально сравнить BFS, DFS и A* на различных лабиринтах. 2. Архитектура приложения (диаграмма классов Mermaid) ![mermaid_diagramm]("2"/mermaid.png) 3. Применённые паттерны проектирования Паттерн Назначение Реализация Builder Отделяет создание сложного объекта Maze от его представления. TextFileMazeBuilder парсит текстовый файл и строит лабиринт. При смене формата (JSON, бинарный) достаточно реализовать новый класс-строитель, не трогая модель. Strategy Инкапсулирует взаимозаменяемые алгоритмы поиска пути. Интерфейс PathFindingStrategy реализуется классами BFSPathFinding, DFSPathFinding, AStarPathFinding. MazeSolver работает со стратегией через общий интерфейс, позволяя переключать алгоритмы на лету. Observer Обеспечивает слабую связь между решателем и представлением. ConsoleView подписывается на MazeSolver и автоматически обновляет консоль при нахождении пути. Можно добавить другие наблюдатели (логгер, GUI) без изменения решателя. Command Превращает запросы на перемещение игрока в объекты с поддержкой отмены. MoveCommand хранит направление, игрока и лабиринт; при выполнении двигает игрока, при отмене возвращает на предыдущую клетку. Можно организовать макрокоманды и историю. 4. Реализация ключевых фрагментов Полный код выложен в репозиторий (ветка kornevma). Здесь приведены только сигнатуры. Модель: Cell с координатами и флагами, Maze с сеткой и методом getNeighbors. Строитель: TextFileMazeBuilder.buildFromFile(filename) → Maze. Стратегии: BFSPathFinding.findPath использует collections.deque, гарантирует кратчайший путь. DFSPathFinding.findPath использует стек (list), путь может быть длиннее. AStarPathFinding.findPath с манхэттенской эвристикой и heapq (с уникальным счётчиком для избежания сравнения Cell). Решатель: MazeSolver.solve() возвращает SearchStats (время, посещённые клетки, длина пути). Визуализация: ConsoleView.render отображает лабиринт, путь и статистику. Игрок и команды: MoveCommand и Player с возможностью отмены ходов. 5. Экспериментальное сравнение алгоритмов 5.1 Тестовые лабиринты Сгенерированы с помощью модуля maze_generator.py: small_random (15×15, вероятность стен 30%) – маленький случайный. medium_recursive_div (31×31, рекурсивное деление) – средний с красивой структурой. large_empty (100×100) – без стен, для демонстрации максимальной нагрузки. large_random (100×100, вероятность стен 25%) – большой случайный. no_path (20×20) – выход заблокирован стенами. 5.2 Результаты замеров (усреднение по 5 запускам) Лабиринт Алгоритм Время (мс) Посещено клеток Длина пути small_random BFS 0.20 79 31 small_random DFS 0.18 75 35 small_random A* 0.25 62 31 medium_recursive_div BFS 0.003 1 0 medium_recursive_div DFS 0.003 1 0 medium_recursive_div A* 0.005 1 0 large_empty BFS 28.16 10000 199 large_empty DFS 16.87 5149 4951 large_empty A* 47.76 10000 199 large_random BFS 20.69 7396 201 large_random DFS 18.39 6029 615 large_random A* 10.63 2215 201 no_path BFS 1.01 397 0 no_path DFS 1.02 397 0 no_path A* 1.68 397 0 5.3 График сравнения ![benchmark]("2"/benchmark_plot.png) 6. Анализ результатов 1. Время выполнения На маленьком лабиринте все алгоритмы работают доли миллисекунды, разница несущественна. На больших лабиринтах (large_empty и large_random) лидирует A* (10.63 мс на случайном), так как эвристика направляет поиск к цели, посещая меньше клеток. DFS показал 18.39 мс, BFS – 20.69 мс. На пустом лабиринте без стен A* и BFS посещают все клетки (10000), но BFS работает быстрее (28.16 мс), вероятно, из-за накладных расходов на приоритетную очередь в A*. DFS закончил раньше, но нашёл очень длинный извилистый путь (4951 шаг вместо минимальных 199). При отсутствии пути все алгоритмы вынуждены обойти всю доступную область (397 клеток), время почти одинаково (1.0–1.7 мс). 2. Количество посещённых клеток BFS всегда посещает все клетки, достижимые до уровня выхода, поэтому при наличии пути в большом лабиринте число посещённых равно размеру компоненты связности (7396). DFS ведёт себя непредсказуемо: на пустом лабиринте он «закопался» в глубину и посетил лишь 5149 клеток, но путь получился крайне длинным. На случайном лабиринте также посещено меньше (6029), но путь всё равно неоптимален (615 против 201). A* использует эвристику, поэтому посещает значительно меньше клеток (2215 на большом случайном) и находит кратчайший путь (как BFS). 3. Длина пути BFS всегда находит кратчайший путь (31, 199, 201). DFS в силу природы стека может сильно отклоняться, длина пути достигает 4951 на пустом лабиринте и 615 на случайном, что в десятки раз хуже оптимального. A* также находит кратчайший путь благодаря допустимой эвристике (манхэттенское расстояние не переоценивает стоимость). Длина совпадает с BFS. 4. Взвешенные клетки (доп. задание) Код поддерживает вес клетки (поле weight). Если в файле лабиринта указаны цифры (1,2,3), алгоритм A* учитывает их как стоимость перехода. Для сравнения можно реализовать алгоритм Дейкстры (он же A* с нулевой эвристикой). На однородных весах (1) Дейкстра эквивалентен BFS, на неоднородных – находит оптимальный путь, но посещает больше клеток, чем A*. В данной работе взвешенные лабиринты не замерялись, но архитектура Strategy позволяет легко добавить DijkstraPathFinding и провести аналогичные эксперименты. 7. Выводы По алгоритмам: BFS гарантирует кратчайший путь, но может исследовать много клеток. Подходит для небольших лабиринтов или когда критична оптимальность. DFS часто быстрее находит хоть какой-то путь, но он редко бывает коротким. Полезен, если важен факт достижимости, а не длина. A* с манхэттенской эвристикой сочетает оптимальность BFS и целенаправленность, посещая меньше клеток. Это лучший выбор для большинства практических задач поиска пути. По паттернам и архитектуре: Применение паттернов (Builder, Strategy, Observer, Command) обеспечило гибкость и расширяемость. Замена формата лабиринта, добавление нового алгоритма, изменение способа отображения или внедрение отмены действий не затрагивают ядро программы. Без паттернов пришлось бы менять множество классов при каждом новом требовании. Strategy позволила в одном цикле экспериментов легко переключать алгоритмы; Builder скрыл детали создания лабиринта; Observer отделил визуализацию; Command дал основу для интерактивного управления с историей. Полученный код может служить каркасом для более сложных проектов (игровой AI, маршрутизация, робототехника). Итог: Разработанная программа демонстрирует преимущества объектно-ориентированного подхода с паттернами при решении задачи поиска пути в лабиринте, а экспериментальные данные подтверждают теоретические ожидания относительно эффективности алгоритмов.