# Отчёт: Задание 2 — Поиск выхода из лабиринта ## Цель работы Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов ## Выбранные паттерны и их обоснование ### Builder Для загрузки лабиринта из файла был использован паттерн Builder. Создан интерфейс: class MazeBuilder(): и его реализация: class TextFileMazeBuilder(MazeBuilder): Преимущества использования Builder: пользоватеь не знает деталей создания лабиринта; можно добавить новые форматы (JSON, XML, бинарный); код загрузки изолирован от остальной программы. ### Strategy Для алгоритмов поиска пути использован паттерн Strategy Создан общий интерфейс: class PathFindingStrategy(): Реализованы стратегии: BFSStrategy; DFSStrategy; AStrategy; Каждая стратегия реализует собственный алгоритм поиска пути по правилам. Преимущества паттерна: алгоритмы можно менять во время выполнения; код MazeSolver не зависит от конкретного алгоритма; новые алгоритмы можно добавлять без изменения существующего кода. ### Observer Для уведомления интерфейса о событиях использован паттерн Observer Создан интерфейс: class Observer(): и реализация: class ConsoleView(Observer): MazeSolver хранит список наблюдателей и уведомляет их о событиях: начало поиска; окончание поиска; перемещение игрока. Преимущества: логика интерфейса отделена от логики поиска; можно легко добавить графический интерфейс; ### Command Для пошагового перемещения игрока использован паттерн Command. Создан интерфейс: class Command(): и реализация: class MoveCommand(Command): Каждая команда умеет: execute() — выполнить действие; undo() — отменить действие Преимущества: поддержка undo; возможность расширения системы команд ## Листинги ключевых классов ### Паттерн Strategy class PathFindingStrategy: def findPath(self, maze, start, exit): pass class BFSStrategy(PathFindingStrategy): def findPath(self, maze, start, exit): if exit is None: return [] queue = deque([start]) visited = {start} parent = {start: None} while queue: current = queue.popleft() if current == exit: return self._reconstruct_path(parent, start, exit) for neighbor in maze.getNeighbors(current): if neighbor not in visited: visited.add(neighbor) parent[neighbor] = current queue.append(neighbor) return [] class AStrategy(PathFindingStrategy): def _heuristic(self, cell, exit): if exit is None: return 0 return abs(cell.x - exit.x) + abs(cell.y - exit.y) def findPath(self, maze, start, exit): if exit is None: return [] open_set = [] heapq.heappush(open_set, (0, start)) came_from = {start: None} g_score = {start: 0} while open_set: current = heapq.heappop(open_set)[1] if current == exit: return self._reconstruct_path(came_from, start, exit) for neighbor in maze.getNeighbors(current): tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g f_score = tentative_g + self._heuristic(neighbor, exit) heapq.heappush(open_set, (f_score, neighbor)) return [] ### Паттерн Command class Command: def execute(self): pass def undo(self): pass class MoveCommand(Command): def __init__(self, player, direction, maze): self.player = player self.dx, self.dy = direction self.maze = maze self.executed = False def execute(self): new_x = self.player.currentCell.x + self.dx new_y = self.player.currentCell.y + self.dy new_cell = self.maze.getCell(new_x, new_y) if new_cell and new_cell.isPassable(): self.player.moveTo(new_cell) self.executed = True return True return False def undo(self): if self.executed: self.player.undoMove() self.executed = False return True return False ## Результаты | Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден | |---|---|---|---|---|---| | маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True | | маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True | | маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True | | средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True | | средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True | | средний (50x50) | A | 9.11291999509558 | 99.0 | 99.0 | True | | большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False | | большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False | | пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True | | пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True | | пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True | | без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False | | без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False | | без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False | ### Графики ![Сравнение длины](maze_path_length.png) ![Сравнение времён](maze_time_comparison.png) ## Анализ эффективности алгоритмов В ходе экспериментов были получены следующие результаты. ### BFS Преимущества: всегда находит кратчайший путь; простая реализация. Недостатки: посещает большое количество клеток; требует много памяти. Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах. ### DFS Преимущества: простая реализация; самым быстрым находит произвольный путь. Недостатки: не гарантирует кратчайший путь; может уходить в тупики. Подходит для быстрого поиска любого решения. ### A Преимущества: высокая скорость; посещает меньше клеток; Недостатки: требует выбора хорошей эвристики. Показал хорошие результаты на больших лабиринтах. ## Анализ применимости паттернов ### Builder Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода. Strategy Без Strategy пришлось бы: хранить все алгоритмы внутри одного класса; использовать большое количество условных операторов; изменять код MazeSolver при добавлении новых алгоритмов Strategy помог полностью отделить алгоритмы друг от друга. ### Observer Без Observer логика интерфейса смешивалась бы с логикой поиска. Это усложнило бы: добавление GUI; логирование; визуализацию. ### Command Без Command было бы сложно реализовать: undo; историю действий; расширяемую систему управления. ## Выводы ### В проекте были успешно реализованы: загрузка лабиринта из файла; несколько алгоритмов поиска пути; визуализация; система наблюдателей; система команд; экспериментальное сравнение алгоритмов. ### Использование паттернов GoF позволило: сделать архитектуру гибкой; уменьшить связанность компонентов; упростить расширение программы; облегчить сопровождение кода.