forked from UNN/2026-rff_mp
253 lines
9.9 KiB
Markdown
253 lines
9.9 KiB
Markdown
|
|
# Отчёт: Задание 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 |
|
|||
|
|
|
|||
|
|
### Графики
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
## Анализ эффективности алгоритмов
|
|||
|
|
|
|||
|
|
В ходе экспериментов были получены следующие результаты.
|
|||
|
|
|
|||
|
|
### BFS
|
|||
|
|
Преимущества:
|
|||
|
|
всегда находит кратчайший путь;
|
|||
|
|
простая реализация.
|
|||
|
|
Недостатки:
|
|||
|
|
посещает большое количество клеток;
|
|||
|
|
требует много памяти.
|
|||
|
|
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.
|
|||
|
|
|
|||
|
|
### DFS
|
|||
|
|
Преимущества:
|
|||
|
|
простая реализация;
|
|||
|
|
самым быстрым находит произвольный путь.
|
|||
|
|
Недостатки:
|
|||
|
|
не гарантирует кратчайший путь;
|
|||
|
|
может уходить в тупики.
|
|||
|
|
Подходит для быстрого поиска любого решения.
|
|||
|
|
|
|||
|
|
### A
|
|||
|
|
Преимущества:
|
|||
|
|
высокая скорость;
|
|||
|
|
посещает меньше клеток;
|
|||
|
|
Недостатки:
|
|||
|
|
требует выбора хорошей эвристики.
|
|||
|
|
Показал хорошие результаты на больших лабиринтах.
|
|||
|
|
|
|||
|
|
## Анализ применимости паттернов
|
|||
|
|
|
|||
|
|
### Builder
|
|||
|
|
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
|
|||
|
|
Strategy
|
|||
|
|
Без Strategy пришлось бы:
|
|||
|
|
хранить все алгоритмы внутри одного класса;
|
|||
|
|
использовать большое количество условных операторов;
|
|||
|
|
изменять код MazeSolver при добавлении новых алгоритмов
|
|||
|
|
Strategy помог полностью отделить алгоритмы друг от друга.
|
|||
|
|
|
|||
|
|
### Observer
|
|||
|
|
Без Observer логика интерфейса смешивалась бы с логикой поиска.
|
|||
|
|
Это усложнило бы:
|
|||
|
|
добавление GUI;
|
|||
|
|
логирование;
|
|||
|
|
визуализацию.
|
|||
|
|
|
|||
|
|
### Command
|
|||
|
|
Без Command было бы сложно реализовать:
|
|||
|
|
undo;
|
|||
|
|
историю действий;
|
|||
|
|
расширяемую систему управления.
|
|||
|
|
|
|||
|
|
## Выводы
|
|||
|
|
|
|||
|
|
### В проекте были успешно реализованы:
|
|||
|
|
загрузка лабиринта из файла;
|
|||
|
|
несколько алгоритмов поиска пути;
|
|||
|
|
визуализация;
|
|||
|
|
система наблюдателей;
|
|||
|
|
система команд;
|
|||
|
|
экспериментальное сравнение алгоритмов.
|
|||
|
|
|
|||
|
|
### Использование паттернов GoF позволило:
|
|||
|
|
сделать архитектуру гибкой;
|
|||
|
|
уменьшить связанность компонентов;
|
|||
|
|
упростить расширение программы;
|
|||
|
|
облегчить сопровождение кода.
|