2026-rff_mp/MashinDD/lab2/docs/report.md
2026-05-17 16:50:48 +03:00

236 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования)
## Цель работы
Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 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`
Каждый класс отвечает за одну вещь, код можно тестировать по частям независимо.