forked from UNN/2026-rff_mp
346 lines
16 KiB
Plaintext
346 lines
16 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "9a863658",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Отчёт: Поиск выхода из лабиринта (ООП + паттерны проектирования)\n",
|
||
"\n",
|
||
"## Цель работы\n",
|
||
"\n",
|
||
"Разработать гибкую расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. Применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Описание задачи и выбранных паттернов\n",
|
||
"\n",
|
||
"Программа решает задачу поиска пути в лабиринте, загружаемом из текстового файла. Лабиринт представляет собой сетку клеток, где `#` — стена, пробел — проход, `S` — старт, `E` — выход. Алгоритм поиска выбирается динамически, результаты выводятся через систему событий.\n",
|
||
"\n",
|
||
"В проекте применены паттерны Builder, Strategy, Observer и Command.\n",
|
||
"\n",
|
||
"### 1. Builder (Строитель) — `MazeBuilder.py`\n",
|
||
"\n",
|
||
"**Проблема:** построение объекта `Maze` из текстового файла включает несколько этапов: чтение файла, анализ символов, создание объектов клеток, определение стартовой и конечной позиции, формирование структуры лабиринта.\n",
|
||
"\n",
|
||
"**Решение:** создан абстрактный класс `MazeBuilder` с методом `build_from_file()`. Класс `TextFileMazeBuilder` реализует построение лабиринта из текстового файла.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class MazeBuilder(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def build_from_file(self, filename) -> Maze:\n",
|
||
" pass\n",
|
||
"```\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||
" def build_from_file(self, filename) -> Maze:\n",
|
||
"```\n",
|
||
"\n",
|
||
"Паттерн позволяет изолировать логику построения лабиринта от основной программы. При необходимости можно добавить другой формат загрузки без изменения клиентского кода.\n",
|
||
"\n",
|
||
"### 2. Strategy (Стратегия) — `FindingStrategy.py`\n",
|
||
"\n",
|
||
"**Проблема:** алгоритмы BFS, DFS и A* имеют разную реализацию, но используются одинаковым образом.\n",
|
||
"\n",
|
||
"**Решение:** создан интерфейс `PathFindingStrategy` с методом `find_path()`. Алгоритмы `BFSStrategy`, `DFSStrategy` и `AStarStrategy` реализуют общий интерфейс.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class PathFindingStrategy(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def find_path(self, maze, start, exit_cell):\n",
|
||
" pass\n",
|
||
"```\n",
|
||
"\n",
|
||
"Смена алгоритма выполняется динамически:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"solver.set_strategy(BFSStrategy())\n",
|
||
"solver.solve()\n",
|
||
"\n",
|
||
"solver.set_strategy(AStarStrategy())\n",
|
||
"solver.solve()\n",
|
||
"```\n",
|
||
"\n",
|
||
"Паттерн Strategy позволяет добавлять новые алгоритмы поиска без изменения класса `MazeSolver`.\n",
|
||
"\n",
|
||
"### 3. Observer (Наблюдатель) — `MazeSolver.py`\n",
|
||
"\n",
|
||
"**Проблема:** необходимо уведомлять интерфейс о событиях поиска пути без жёсткой связи между логикой и выводом.\n",
|
||
"\n",
|
||
"**Решение:** реализован интерфейс `Observer` с методом `update()`. Класс `ConsoleView` подписывается на события `MazeSolver`.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class Observer(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def update(self, event, data=None):\n",
|
||
" pass\n",
|
||
"```\n",
|
||
"\n",
|
||
"```python\n",
|
||
"solver.add_observer(view)\n",
|
||
"```\n",
|
||
"\n",
|
||
"При нахождении пути вызывается уведомление:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"self._notify('path_found', {\n",
|
||
" 'stats': stats,\n",
|
||
" 'strategy': type(self.strategy).__name__\n",
|
||
"})\n",
|
||
"```\n",
|
||
"\n",
|
||
"Паттерн позволяет отделить вывод информации от алгоритмов поиска.\n",
|
||
"\n",
|
||
"### 4. Command (Команда) — `MazeSolver.py`\n",
|
||
"\n",
|
||
"**Проблема:** необходимо реализовать возможность перемещения игрока с поддержкой отмены действий.\n",
|
||
"\n",
|
||
"**Решение:** создан интерфейс `Command` с методами `execute()` и `undo()`. Класс `MoveCommand` хранит предыдущее состояние игрока.\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class Command(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def execute(self):\n",
|
||
" pass\n",
|
||
"\n",
|
||
" @abstractmethod\n",
|
||
" def undo(self):\n",
|
||
" pass\n",
|
||
"```\n",
|
||
"\n",
|
||
"```python\n",
|
||
"cmd.execute()\n",
|
||
"cmd.undo()\n",
|
||
"```\n",
|
||
"\n",
|
||
"Паттерн инкапсулирует действия в отдельные объекты и позволяет реализовать undo/redo.\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Диаграмма классов (Mermaid)\n",
|
||
"\n",
|
||
"```mermaid\n",
|
||
"classDiagram\n",
|
||
" class MazeBuilder {\n",
|
||
" <<interface>>\n",
|
||
" +build_from_file(filename) Maze\n",
|
||
" }\n",
|
||
"\n",
|
||
" class TextFileMazeBuilder {\n",
|
||
" +build_from_file(filename) Maze\n",
|
||
" }\n",
|
||
"\n",
|
||
" class Cell {\n",
|
||
" +x\n",
|
||
" +y\n",
|
||
" +is_wall\n",
|
||
" +is_start\n",
|
||
" +is_exit\n",
|
||
" +is_passable()\n",
|
||
" }\n",
|
||
"\n",
|
||
" class Maze {\n",
|
||
" +width\n",
|
||
" +height\n",
|
||
" +get_cell(x, y)\n",
|
||
" +get_neighbors(cell)\n",
|
||
" +render(path, player_pos)\n",
|
||
" }\n",
|
||
"\n",
|
||
" class PathFindingStrategy {\n",
|
||
" <<interface>>\n",
|
||
" +find_path(maze, start, exit)\n",
|
||
" }\n",
|
||
"\n",
|
||
" class BFSStrategy\n",
|
||
" class DFSStrategy\n",
|
||
" class AStarStrategy\n",
|
||
"\n",
|
||
" class MazeSolver {\n",
|
||
" +set_strategy(strategy)\n",
|
||
" +solve()\n",
|
||
" +add_observer(observer)\n",
|
||
" }\n",
|
||
"\n",
|
||
" class SearchStats\n",
|
||
"\n",
|
||
" class Observer {\n",
|
||
" <<interface>>\n",
|
||
" +update(event, data)\n",
|
||
" }\n",
|
||
"\n",
|
||
" class ConsoleView\n",
|
||
"\n",
|
||
" class Command {\n",
|
||
" <<interface>>\n",
|
||
" +execute()\n",
|
||
" +undo()\n",
|
||
" }\n",
|
||
"\n",
|
||
" class MoveCommand\n",
|
||
" class Player\n",
|
||
"\n",
|
||
" MazeBuilder <|.. TextFileMazeBuilder\n",
|
||
" Maze o-- Cell\n",
|
||
"\n",
|
||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||
"\n",
|
||
" MazeSolver --> Maze\n",
|
||
" MazeSolver --> PathFindingStrategy\n",
|
||
" MazeSolver --> SearchStats\n",
|
||
"\n",
|
||
" Observer <|.. ConsoleView\n",
|
||
" MazeSolver --> Observer\n",
|
||
"\n",
|
||
" Command <|.. MoveCommand\n",
|
||
" MoveCommand --> Player\n",
|
||
"```\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Ключевые фрагменты реализации\n",
|
||
"\n",
|
||
"### Реализация BFS\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class BFSStrategy(PathFindingStrategy):\n",
|
||
" def find_path(self, maze, start, exit_cell):\n",
|
||
" queue = deque([start])\n",
|
||
" came_from = {(start.x, start.y): None}\n",
|
||
"```\n",
|
||
"\n",
|
||
"Алгоритм BFS выполняет поиск в ширину и гарантирует нахождение кратчайшего пути.\n",
|
||
"\n",
|
||
"### Реализация DFS\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class DFSStrategy(PathFindingStrategy):\n",
|
||
" def find_path(self, maze, start, exit_cell):\n",
|
||
" stack = [start]\n",
|
||
"```\n",
|
||
"\n",
|
||
"DFS использует стек и выполняет поиск в глубину.\n",
|
||
"\n",
|
||
"### Реализация A*\n",
|
||
"\n",
|
||
"```python\n",
|
||
"class AStarStrategy(PathFindingStrategy):\n",
|
||
"\n",
|
||
" def _heuristic(self, cell, goal):\n",
|
||
" return abs(cell.x - goal.x) + abs(cell.y - goal.y)\n",
|
||
"```\n",
|
||
"\n",
|
||
"A* использует манхэттенскую эвристику для направления поиска к цели.\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Экспериментальная часть\n",
|
||
"\n",
|
||
"### Параметры эксперимента\n",
|
||
"\n",
|
||
"| Параметр | Значение |\n",
|
||
"| ---------- | ------------------------------------ |\n",
|
||
"| Повторений | 7 |\n",
|
||
"| Алгоритмы | BFS, DFS, A* |\n",
|
||
"| Метрики | время, посещённые клетки, длина пути |\n",
|
||
"\n",
|
||
"### Тестовые лабиринты\n",
|
||
"\n",
|
||
"| Название | Размер |\n",
|
||
"| ------------- | ------- |\n",
|
||
"| small_10x10 | 10×10 |\n",
|
||
"| medium_50x50 | 50×50 |\n",
|
||
"| large_100x100 | 100×100 |\n",
|
||
"| open_50x50 | 50×50 |\n",
|
||
"| no_exit_20x20 | 20×20 |\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Результаты\n",
|
||
"\n",
|
||
"### Таблица результатов\n",
|
||
"\n",
|
||
"| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n",
|
||
"| ------------- | -------- | ---------- | --------------- | ---------- |\n",
|
||
"| small_10x10 | BFS | 0.094 | 54 | 15 |\n",
|
||
"| small_10x10 | DFS | 0.059 | 33 | 33 |\n",
|
||
"| small_10x10 | A* | 0.078 | 36 | 15 |\n",
|
||
"| medium_50x50 | BFS | 2.446 | 1639 | 95 |\n",
|
||
"| medium_50x50 | DFS | 1.480 | 1063 | 185 |\n",
|
||
"| medium_50x50 | A* | 1.528 | 588 | 95 |\n",
|
||
"| large_100x100 | BFS | 9.891 | 6564 | — |\n",
|
||
"| large_100x100 | DFS | 9.057 | 6564 | — |\n",
|
||
"| large_100x100 | A* | 17.578 | 6564 | — |\n",
|
||
"| open_50x50 | BFS | 3.296 | 2304 | 95 |\n",
|
||
"| open_50x50 | DFS | 1.830 | 1223 | 1129 |\n",
|
||
"| open_50x50 | A* | 5.566 | 2304 | 95 |\n",
|
||
"| no_exit_20x20 | BFS | 0.368 | 260 | — |\n",
|
||
"| no_exit_20x20 | DFS | 0.343 | 260 | — |\n",
|
||
"| no_exit_20x20 | A* | 0.607 | 260 | — |\n",
|
||
"\n",
|
||
"### Визуализация\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Анализ результатов\n",
|
||
"\n",
|
||
"### BFS\n",
|
||
"\n",
|
||
"Алгоритм BFS во всех случаях находит кратчайший путь. На лабиринте medium_50x50 длина найденного пути составила 95 шагов. Недостатком алгоритма является большое количество посещённых клеток.\n",
|
||
"\n",
|
||
"### DFS\n",
|
||
"\n",
|
||
"DFS выполняет поиск быстрее, однако найденный путь значительно длиннее. На open_50x50 длина пути составила 1129 шагов против 95 у BFS.\n",
|
||
"\n",
|
||
"### A*\n",
|
||
"\n",
|
||
"Алгоритм A* использует эвристику и уменьшает количество посещённых клеток. На medium_50x50 было посещено 588 клеток против 1639 у BFS.\n",
|
||
"\n",
|
||
"На открытом лабиринте преимущества эвристики снижаются, из-за чего время работы увеличивается.\n",
|
||
"\n",
|
||
"### Лабиринты без пути\n",
|
||
"\n",
|
||
"На large_100x100 и no_exit_20x20 путь найден не был. Все алгоритмы корректно завершили работу после обхода доступных клеток.\n",
|
||
"\n",
|
||
"---\n",
|
||
"\n",
|
||
"## Выводы\n",
|
||
"\n",
|
||
"В ходе работы была реализована объектно-ориентированная система поиска пути в лабиринте с применением паттернов проектирования GoF.\n",
|
||
"\n",
|
||
"Паттерн Strategy обеспечил возможность динамической смены алгоритма поиска. Builder отделил процесс создания лабиринта от логики приложения. Observer позволил реализовать систему уведомлений без жёстких зависимостей. Command обеспечил поддержку отмены действий.\n",
|
||
"\n",
|
||
"Экспериментальные результаты показали:\n",
|
||
"\n",
|
||
"* BFS гарантирует кратчайший путь;\n",
|
||
"* DFS работает быстрее, но может находить неоптимальные маршруты;\n",
|
||
"* A* наиболее эффективен на сложных лабиринтах благодаря эвристике.\n",
|
||
"\n",
|
||
"Архитектура программы получилась модульной и расширяемой. Добавление новых алгоритмов или способов загрузки лабиринтов возможно без изменения существующего кода.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a9cbb6c6",
|
||
"metadata": {},
|
||
"source": []
|
||
}
|
||
],
|
||
"metadata": {
|
||
"language_info": {
|
||
"name": "python"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|