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

11 KiB
Raw Blame History

Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования)

Цель работы

Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 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)

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
  • ObserverConsoleView отключается или заменяется GUI без правок логики
  • Command — история ходов и отмена реализуются без изменения Player

Каждый класс отвечает за одну вещь, код можно тестировать по частям независимо.