2026-rff_mp/Ezhovnd/maze_report.md
Ezhovnd b8c07ba140 Лабораторная работа №2
Лабораторная работа №2
2026-05-25 07:48:23 +00:00

280 lines
15 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.

# Задание 2 — Поиск выхода из лабиринта (ООП + паттерны GoF)
## 1. Описание задачи
Разработать расширяемую программу поиска пути в лабиринте с возможностью смены алгоритма, визуализации и пошагового управления. Применить минимум 3 паттерна GoF.
---
## 2. Применённые паттерны GoF
### 2.1 Builder (Строитель) — `builder.py`
**Проблема:** Создание `Maze` — многошаговый процесс: чтение файла → парсинг символов → валидация (есть ли S и E) → расстановка координат. Если делать всё в конструкторе `Maze` — нарушение Single Responsibility.
**Решение:** Интерфейс `MazeBuilder` с методом `build_from_file()`. Реализации:
- `TextFileMazeBuilder` — текстовый формат (`#`, пробел, `S`, `E`, `W`, `N`)
- `JsonMazeBuilder` — JSON-формат (для демонстрации расширяемости)
**Преимущество:** Добавление нового формата (бинарный, XML) не требует изменения `Maze`, `MazeSolver` или стратегий.
### 2.2 Strategy (Стратегия) — `strategy.py`
**Проблема:** Алгоритмы поиска (BFS, DFS, A*, Dijkstra) взаимозаменяемы по интерфейсу, но различаются реализацией. Жёсткая связь с конкретным алгоритмом нарушает Open/Closed Principle.
**Решение:** Интерфейс `PathFindingStrategy` с методом `find_path(maze, start, exit_)`. `MazeSolver` хранит ссылку на стратегию и вызывает `strategy.find_path()` — не зная, какой именно алгоритм работает. Метод `set_strategy()` меняет алгоритм в рантайме.
**Преимущество:** Новый алгоритм (IDA*, Bidirectional BFS) — это один новый класс без изменения остального кода.
### 2.3 Observer (Наблюдатель) — `solver.py`
**Проблема:** `MazeSolver` не должен знать о способе отображения. Консоль, GUI, файловый лог — разные задачи.
**Решение:** `MazeSolver` наследует `Subject` (хранит список `_observers`, метод `notify()`). При событиях (`maze_loaded`, `path_found`, `no_path`, `player_moved`) уведомляет всех наблюдателей. Реализации: `ConsoleView`, `FileLogObserver`.
**Преимущество:** GUI-интерфейс добавляется как новый `Observer` без изменения `MazeSolver`.
### 2.4 Command (Команда) — `solver.py`
**Проблема:** Пошаговое перемещение игрока должно поддерживать отмену (undo).
**Решение:** Интерфейс `Command` с методами `execute()` / `undo()`. `MoveCommand` хранит предыдущую позицию. `CommandHistory` — стек команд. Undo — `pop()` + `cmd.undo()`.
**Преимущество:** Любое новое действие (телепортация, открытие двери) реализуется как отдельная команда, не меняя `Player` или `MazeSolver`.
---
## 3. Диаграмма классов (Mermaid)
```mermaid
classDiagram
%% ── Model ──────────────────────────────────
class Cell {
+int x, y
+str symbol
+bool is_wall
+bool is_start
+bool is_exit
+int weight
+is_passable() bool
}
class Maze {
+int width, height
+Cell start, exit
+get_cell(x,y) Cell
+get_neighbors(cell) list
+render(path, visited, player) str
}
Maze "1" *-- "many" Cell
%% ── Builder ─────────────────────────────────
class MazeBuilder {
<<interface>>
+build_from_file(filename) Maze
+build_empty(w, h) Maze
}
class TextFileMazeBuilder {
+build_from_file(filename) Maze
+build_from_string(text) Maze
+build_empty(w, h) Maze
}
class JsonMazeBuilder {
+build_from_file(filename) Maze
+build_empty(w, h) Maze
}
MazeBuilder <|.. TextFileMazeBuilder
MazeBuilder <|.. JsonMazeBuilder
TextFileMazeBuilder ..> Maze : creates
JsonMazeBuilder ..> Maze : creates
%% ── Strategy ────────────────────────────────
class PathFindingStrategy {
<<interface>>
+find_path(maze, start, exit) tuple
+name() str
}
class BFSStrategy { +find_path() tuple }
class DFSStrategy { +find_path() tuple }
class AStarStrategy { +find_path() tuple }
class DijkstraStrategy { +find_path() tuple }
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
PathFindingStrategy <|.. DijkstraStrategy
%% ── Observer ────────────────────────────────
class Observer {
<<interface>>
+update(event, payload)
}
class Subject {
-observers list
+add_observer(obs)
+notify(event, payload)
}
class ConsoleView { +update(event, payload) }
class FileLogObserver { +update(event, payload) }
Observer <|.. ConsoleView
Observer <|.. FileLogObserver
Subject "1" o-- "many" Observer
%% ── MazeSolver ──────────────────────────────
class MazeSolver {
-Maze maze
-PathFindingStrategy strategy
+set_strategy(strategy)
+load_maze(maze)
+solve() tuple
}
MazeSolver --|> Subject
MazeSolver --> PathFindingStrategy : uses
MazeSolver --> Maze : uses
%% ── Command ─────────────────────────────────
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
-Player player
-Cell target, previous
+execute()
+undo()
}
class CommandHistory {
-stack list
+execute(cmd)
+undo()
}
class Player { +Cell position }
Command <|.. MoveCommand
CommandHistory o-- Command
MoveCommand --> Player
```
---
## 4. Структура файлов
```
maze/
├── model.py — Cell, Maze
├── builder.py — MazeBuilder, TextFileMazeBuilder, JsonMazeBuilder
├── strategy.py — PathFindingStrategy, BFS, DFS, A*, Dijkstra
├── solver.py — Observer, Subject, ConsoleView, MazeSolver, Command, Player
├── generator.py — Генератор лабиринтов (DFS-backtracker)
├── experiment.py — Экспериментальная часть, запись CSV
├── main.py — Интерактивная демонстрация
└── docs/
└── data/
├── maze_results.csv
├── maze_benchmark.png
├── path_lengths.png
└── mazes/ — текстовые файлы лабиринтов
```
---
## 5. Результаты экспериментов
### 5.1 Таблица результатов (N = 7 повторов, среднее)
| Лабиринт | Алгоритм | Время (мс) | Посещено | Длина пути |
|---|---|---:|---:|---:|
| Маленький 11×11 | BFS | 0.077 | 37 | 29 |
| Маленький 11×11 | DFS | 0.059 | 29 | 29 |
| Маленький 11×11 | A* | 0.111 | 29 | 29 |
| Маленький 11×11 | Dijkstra | 0.115 | 37 | 29 |
| Средний 51×51 | BFS | 2.228 | 755 | 405 |
| Средний 51×51 | DFS | 0.935 | 423 | 405 |
| Средний 51×51 | A* | 2.343 | 663 | 405 |
| Средний 51×51 | Dijkstra | 2.528 | 755 | 405 |
| Большой 101×101 | BFS | 3.299 | 1533 | 993 |
| Большой 101×101 | DFS | 2.289 | 1061 | 993 |
| Большой 101×101 | A* | 5.948 | 1515 | 993 |
| Большой 101×101 | Dijkstra | 5.137 | 1533 | 993 |
| Пустой 52×52 | BFS | 5.859 | 2500 | 99 |
| Пустой 52×52 | DFS | 3.518 | 1275 | 1275 |
| Пустой 52×52 | A* | 10.803 | 2500 | 99 |
| Пустой 52×52 | Dijkstra | 10.227 | 2500 | 99 |
| Без выхода 21×21 | BFS | 0.213 | 105 | 0 |
| Без выхода 21×21 | DFS | 0.220 | 105 | 0 |
| Без выхода 21×21 | A* | 0.361 | 105 | 0 |
| Без выхода 21×21 | Dijkstra | 0.336 | 105 | 0 |
| Взвешенный 51×51 | BFS | 0.722 | 335 | 221 |
| Взвешенный 51×51 | DFS | 0.518 | 221 | 221 |
| Взвешенный 51×51 | A* | 0.970 | 278 | 221 |
| Взвешенный 51×51 | Dijkstra | 1.091 | 337 | 221 |
### 5.2 Графики
![Benchmark](data/maze_benchmark.png)
![Path Lengths](data/path_lengths.png)
---
## 6. Анализ алгоритмов
### BFS (поиск в ширину)
- **Гарантирует кратчайший путь** по количеству шагов.
- Посещает все клетки на расстоянии k до нахождения выхода на расстоянии k+1.
- На пустом лабиринте (максимум свободного пространства) посещает **все 2500 клеток** — это худший сценарий по памяти (хранит всю «волну»).
- Практически эквивалентен Dijkstra для равновесных лабиринтов.
### DFS (поиск в глубину)
- **Самый быстрый по времени** на лабиринтах с длинными коридорами.
- На правильно построенном лабиринте (DFS-backtracker) коридоры длинные → DFS «угадывает» направление и находит путь, посетив меньше клеток.
- На пустом поле даёт **огромный зигзагообразный путь** (1275 клеток вместо оптимального 99) — классическая проблема DFS.
- **Не гарантирует оптимальность**, зато экономит память (O(глубина) вместо O(ширина)).
### A* (с манхэттенской эвристикой)
- На лабиринтах с коридорами посещает **меньше клеток**, чем BFS, но при этом тоже гарантирует оптимальный путь.
- На пустом поле ведёт себя хуже BFS по времени из-за накладных расходов на приоритетную очередь (heapq).
- Манхэттенская эвристика работает хуже на лабиринтах с длинными обходами препятствий: оценка расстояния «по прямой» слишком оптимистична.
- **Выигрывает** когда карта большая и путь очевиден по направлению (прямые коридоры к выходу).
### Dijkstra
- На равновесных лабиринтах идентичен BFS, но медленнее из-за приоритетной очереди.
- **Незаменим на взвешенных картах**: учитывает клетки с весом 2 (песок) и 3 (болото) и находит **минимальный суммарный вес** пути, а не минимальное число шагов.
- На взвешенном лабиринте Dijkstra и A* могут выдать более короткий (по весу) путь, чем BFS, при одинаковой длине в клетках.
### Без выхода
- Все алгоритмы обошли все 105 достижимых клеток и вернули пустой путь — корректная обработка.
- Время идентично для всех алгоритмов (доминирует полный обход, а не структура поиска).
---
## 7. Выводы: ООП и паттерны
### Что дали паттерны
**Builder** отделил детали парсинга от модели. Без него клиент сам читал бы файл и вручную собирал `Maze` — хрупкий, нерасширяемый код. С Builder: добавление JSON-формата заняло 10 строк.
**Strategy** позволил сравнить 4 алгоритма, не меняя `MazeSolver`. Переключение алгоритма — одна строка `solver.set_strategy(new_algo)`. Без паттерна потребовался бы if/elif на 4 ветки в каждом методе.
**Observer** полностью развязал логику поиска и отображение. `MazeSolver` не знает, куда идут уведомления. Добавление `FileLogObserver` — новый класс из 5 строк, нулевые изменения в `MazeSolver`.
**Command** дал возможность реализовать undo за счёт хранения предыдущего состояния в объекте команды. Без паттерна — ручное сохранение/восстановление переменных.
### Что было бы сложно без ООП и паттернов
| Задача | Без паттернов | С паттернами |
|---|---|---|
| Добавить новый алгоритм | Правка MazeSolver + if/elif | Новый класс, наследующий интерфейс |
| Добавить формат файла | Правка загрузчика | Новый Builder |
| Добавить GUI | Вставка GUI-кода в логику поиска | Новый Observer |
| Отмена шага игрока | Ручное сохранение переменной | Command.undo() |
### Рекомендации по выбору алгоритма
| Задача | Рекомендация |
|---|---|
| Гарантированно кратчайший путь, равные веса | **BFS** |
| Быстро найти *какой-нибудь* путь (поиск с препятствиями) | **DFS** |
| Большая карта, путь по направлению к цели | **A\*** |
| Взвешенная карта (болото, песок, дорога) | **Dijkstra** или **A\*** с весовой эвристикой |
| Нужна гарантия оптимальности на взвешенном графе | **Dijkstra** |