236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
# Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования)
|
||
|
||
## Цель работы
|
||
|
||
Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 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 | — |
|
||
|
||
*«—» означает путь не найден (все доступные клетки исчерпаны)*
|
||
|
||
### Визуализация
|
||
|
||

|
||
|
||

|
||
|
||

|
||
|
||
---
|
||
|
||
## Анализ результатов
|
||
|
||
### 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`
|
||
|
||
Каждый класс отвечает за одну вещь, код можно тестировать по частям независимо.
|