forked from UNN/2026-rff_mp
209 lines
10 KiB
Markdown
209 lines
10 KiB
Markdown
# Отчет по заданию: поиск выхода из лабиринта
|
||
|
||
## 1. Описание задачи и выбранных паттернов
|
||
|
||
Цель работы - реализовать расширяемую программу для загрузки лабиринта из файла,
|
||
поиска пути от старта `S` до выхода `E`, визуализации результата и сравнения
|
||
алгоритмов на лабиринтах разной сложности.
|
||
|
||
В проекте реализованы четыре паттерна GoF:
|
||
|
||
| Паттерн | Где реализован | Зачем нужен |
|
||
|---|---|---|
|
||
| Builder | `MazeBuilder`, `TextFileMazeBuilder` | Изолирует парсинг и валидацию файла от остального приложения. |
|
||
| Strategy | `PathFindingStrategy`, `BFSStrategy`, `DFSStrategy`, `AStarStrategy`, `DijkstraStrategy` | Позволяет менять алгоритм поиска без изменения `MazeSolver`. |
|
||
| Observer | `Observer`, `ConsoleView`, события `search_started`, `path_found`, `path_not_found` | Отделяет вычисления от отображения в консоли. |
|
||
| Command | `Command`, `MoveCommand`, `Player` | Инкапсулирует ход игрока и поддерживает отмену хода. |
|
||
|
||
Диаграмма классов:
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class Cell {
|
||
+int x
|
||
+int y
|
||
+bool is_wall
|
||
+bool is_start
|
||
+bool is_exit
|
||
+int weight
|
||
+is_passable() bool
|
||
}
|
||
|
||
class Maze {
|
||
+list cells
|
||
+int width
|
||
+int height
|
||
+Cell start
|
||
+Cell exit
|
||
+get_cell(x, y) Cell
|
||
+get_neighbors(cell) list
|
||
+to_text(path, player) str
|
||
}
|
||
|
||
class MazeBuilder {
|
||
<<interface>>
|
||
+build_from_file(filename) Maze
|
||
}
|
||
|
||
class TextFileMazeBuilder {
|
||
+build_from_file(filename) Maze
|
||
}
|
||
|
||
class PathFindingStrategy {
|
||
<<interface>>
|
||
+find_path(maze, start, exit) PathResult
|
||
}
|
||
|
||
class BFSStrategy
|
||
class DFSStrategy
|
||
class AStarStrategy
|
||
class DijkstraStrategy
|
||
|
||
class SearchStats {
|
||
+str strategy_name
|
||
+float time_ms
|
||
+int visited_cells
|
||
+int path_length
|
||
+list path
|
||
}
|
||
|
||
class MazeSolver {
|
||
+set_strategy(strategy)
|
||
+add_observer(observer)
|
||
+solve() SearchStats
|
||
}
|
||
|
||
class Observer {
|
||
<<interface>>
|
||
+update(event)
|
||
}
|
||
|
||
class ConsoleView {
|
||
+update(event)
|
||
+render(maze, player_position, path) str
|
||
}
|
||
|
||
class Command {
|
||
<<interface>>
|
||
+execute() bool
|
||
+undo() bool
|
||
}
|
||
|
||
class MoveCommand
|
||
class Player
|
||
|
||
MazeBuilder <|.. TextFileMazeBuilder
|
||
MazeBuilder --> Maze : creates
|
||
PathFindingStrategy <|.. BFSStrategy
|
||
PathFindingStrategy <|.. DFSStrategy
|
||
PathFindingStrategy <|.. AStarStrategy
|
||
PathFindingStrategy <|.. DijkstraStrategy
|
||
MazeSolver --> PathFindingStrategy : uses
|
||
MazeSolver --> Maze : uses
|
||
MazeSolver --> Observer : notifies
|
||
Observer <|.. ConsoleView
|
||
Command <|.. MoveCommand
|
||
MoveCommand --> Player
|
||
Player --> Cell
|
||
```
|
||
|
||
## 2. Ключевые классы
|
||
|
||
Основные файлы проекта:
|
||
|
||
| Файл | Назначение |
|
||
|---|---|
|
||
| `maze_solver/models.py` | Классы `Cell` и `Maze`, поиск соседей, текстовая отрисовка. |
|
||
| `maze_solver/builders.py` | Интерфейс Builder и загрузка лабиринта из `.txt`. |
|
||
| `maze_solver/strategies.py` | BFS, DFS, A* и Дейкстра. |
|
||
| `maze_solver/solver.py` | Оркестратор поиска и сбор статистики. |
|
||
| `maze_solver/observers.py` | Observer и консольное представление. |
|
||
| `maze_solver/commands.py` | Command, игрок и undo перемещения. |
|
||
| `main.py` | CLI для запуска поиска и ручного режима. |
|
||
| `scripts/run_experiments.py` | Замеры и построение SVG-графиков. |
|
||
|
||
Пример запуска:
|
||
|
||
```bash
|
||
python3 main.py --maze data/mazes/small.txt --strategy astar --render
|
||
```
|
||
|
||
## 3. Результаты экспериментов
|
||
|
||
Для каждого лабиринта и каждой стратегии выполнено 10 запусков. В таблице указаны
|
||
средние значения. Длина пути считается в клетках, включая старт и выход.
|
||
|
||
| Лабиринт | Стратегия | Время, мс | Посещено клеток | Длина пути | Путь найден |
|
||
|---|---:|---:|---:|---:|---|
|
||
| Маленький 10x10 | BFS | 0.0423 | 37.0 | 20.0 | да |
|
||
| Маленький 10x10 | DFS | 0.0273 | 24.0 | 22.0 | да |
|
||
| Маленький 10x10 | A* | 0.0677 | 31.0 | 20.0 | да |
|
||
| Маленький 10x10 | Dijkstra | 0.0551 | 37.0 | 20.0 | да |
|
||
| Средний 50x50 | BFS | 1.2769 | 1151.0 | 709.0 | да |
|
||
| Средний 50x50 | DFS | 0.9106 | 784.0 | 709.0 | да |
|
||
| Средний 50x50 | A* | 2.0089 | 1133.0 | 709.0 | да |
|
||
| Средний 50x50 | Dijkstra | 1.7041 | 1151.0 | 709.0 | да |
|
||
| Большой 100x100 | BFS | 5.2983 | 4801.0 | 1685.0 | да |
|
||
| Большой 100x100 | DFS | 2.5044 | 2155.0 | 1685.0 | да |
|
||
| Большой 100x100 | A* | 8.6574 | 4791.0 | 1685.0 | да |
|
||
| Большой 100x100 | Dijkstra | 7.1532 | 4800.0 | 1685.0 | да |
|
||
| Пустой 50x50 | BFS | 2.8927 | 2304.0 | 95.0 | да |
|
||
| Пустой 50x50 | DFS | 0.1404 | 187.0 | 95.0 | да |
|
||
| Пустой 50x50 | A* | 0.2374 | 95.0 | 95.0 | да |
|
||
| Пустой 50x50 | Dijkstra | 4.0408 | 2304.0 | 95.0 | да |
|
||
| Без пути 30x30 | BFS | 0.0015 | 1.0 | 0.0 | нет |
|
||
| Без пути 30x30 | DFS | 0.0013 | 1.0 | 0.0 | нет |
|
||
| Без пути 30x30 | A* | 0.0016 | 1.0 | 0.0 | нет |
|
||
| Без пути 30x30 | Dijkstra | 0.0018 | 1.0 | 0.0 | нет |
|
||
|
||
CSV с результатами сохранен в `reports/results.csv`.
|
||
|
||
Графики:
|
||
|
||
| Лабиринт | Время | Посещенные клетки |
|
||
|---|---|---|
|
||
| Маленький |  |  |
|
||
| Средний |  |  |
|
||
| Большой |  |  |
|
||
| Пустой |  |  |
|
||
| Без пути |  |  |
|
||
|
||
## 4. Анализ эффективности
|
||
|
||
BFS гарантирует кратчайший путь в невзвешенном лабиринте. Это видно на маленьком
|
||
лабиринте: BFS, A* и Дейкстра нашли путь длиной 20, а DFS нашел более длинный путь
|
||
длиной 22. Недостаток BFS - широкий фронт поиска, из-за чего в пустом лабиринте он
|
||
посетил все 2304 доступные клетки.
|
||
|
||
DFS не гарантирует кратчайший путь, но часто работает быстро, потому что уходит
|
||
глубоко по одному направлению. На маленьком лабиринте это дало путь хуже оптимального.
|
||
На сгенерированных идеальных лабиринтах путь между двумя клетками единственный, поэтому
|
||
DFS, BFS, A* и Дейкстра получили одинаковую длину пути.
|
||
|
||
A* использует манхэттенскую эвристику. На пустом лабиринте он посетил только 95 клеток,
|
||
то есть фактически прошел по оптимальному маршруту. В запутанных идеальных лабиринтах
|
||
эвристика помогает слабее: прямое направление к выходу часто упирается в стены, поэтому
|
||
A* посещает почти столько же клеток, сколько BFS, а из-за приоритетной очереди тратит
|
||
больше времени.
|
||
|
||
Дейкстра в невзвешенном лабиринте по результату близок к BFS, но работает медленнее
|
||
из-за приоритетной очереди. Его преимущество проявляется при взвешенных клетках.
|
||
В проекте Builder уже поддерживает символы `2`, `3` и `~` как клетки с повышенной
|
||
стоимостью прохода, поэтому Дейкстру и A* можно использовать для дополнительного
|
||
сравнения на взвешенных картах.
|
||
|
||
Лабиринт "Без пути" проверяет корректную обработку отсутствия решения: стратегии
|
||
возвращают пустой путь, а `MazeSolver` фиксирует длину 0.
|
||
|
||
## 5. Выводы
|
||
|
||
ООП позволило разделить предметную модель, загрузку данных, алгоритмы и интерфейс.
|
||
Паттерн Builder делает формат входного файла заменяемым: можно добавить JSON-builder,
|
||
не меняя `Maze` и стратегии. Strategy позволяет добавлять новые алгоритмы без правок
|
||
в `MazeSolver`. Observer отделяет вычисления от вывода, а Command показывает, как
|
||
инкапсулировать пользовательские действия и поддержать undo.
|
||
|
||
Без этих паттернов код быстро стал бы монолитным: парсинг файла, поиск, статистика,
|
||
печать и ручное управление оказались бы в одном месте. Тогда добавление нового формата,
|
||
алгоритма или режима отображения требовало бы менять уже работающую логику.
|