2026-rff_mp/MashinDD/lab2/docs/report.md

236 lines
11 KiB
Markdown
Raw Normal View History

2026-05-17 13:50:48 +00:00
# Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования)
## Цель работы
Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 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 {
<<interface>>
+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 {
<<interface>>
+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 {
<<interface>>
+update(event, data)
}
class ConsoleView { +update(event, data) }
class Command {
<<interface>>
+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`
Каждый класс отвечает за одну вещь, код можно тестировать по частям независимо.