forked from UNN/2026-rff_mp
220 lines
15 KiB
Markdown
220 lines
15 KiB
Markdown
|
|
|
|||
|
|
```md
|
|||
|
|
# Отчёт по лабораторной работе
|
|||
|
|
**Тема:** Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Описание задачи и выбранные паттерны
|
|||
|
|
|
|||
|
|
### 1.1. Постановка задачи
|
|||
|
|
Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Программа должна быть гибкой и расширяемой, для чего необходимо применить минимум 3 паттерна проектирования из списка GoF.
|
|||
|
|
|
|||
|
|
### 1.2. Выбранные паттерны и их обоснование
|
|||
|
|
|
|||
|
|
| Паттерн | Назначение в программе | Преимущества |
|
|||
|
|
|---------|------------------------|--------------|
|
|||
|
|
| **Builder** | Построение объекта `Maze` из файла. Скрывает детали парсинга, валидации и создания клеток. | Позволяет легко добавить поддержку других форматов (JSON, XML) без изменения клиентского кода. Упрощает тестирование. |
|
|||
|
|
| **Strategy** | Семейство алгоритмов поиска пути (BFS, DFS, A*). | Алгоритмы взаимозаменяемы во время выполнения. Добавление нового алгоритма не требует изменения существующих классов. |
|
|||
|
|
| **Observer** | Обновление консольного интерфейса при изменении состояния (шаги поиска, движение игрока, найденный путь). | Разделяет логику поиска и отображения. Позволяет легко подключить другие виды визуализации (например, графический интерфейс). |
|
|||
|
|
| **Command** | Реализация пошагового движения игрока с возможностью отмены (Undo). | Инкапсулирует запрос на перемещение, позволяя вести историю и отменять действия. Упрощает добавление других команд. |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Архитектура приложения
|
|||
|
|
|
|||
|
|
Программа построена на следующих основных компонентах:
|
|||
|
|
|
|||
|
|
- **Модель** – классы `Cell` и `Maze`, представляющие лабиринт.
|
|||
|
|
- **Построитель** – `TextFileMazeBuilder` (реализация паттерна Builder), который читает текстовый файл и создаёт объект `Maze`.
|
|||
|
|
- **Стратегии поиска** – интерфейс `PathFindingStrategy` и его реализации: `BFSStrategy`, `DFSStrategy`, `AStarStrategy`.
|
|||
|
|
- **Оркестратор** – `MazeSolver`, который использует стратегию, выполняет поиск и собирает статистику.
|
|||
|
|
- **Наблюдатель** – интерфейс `Observer` и класс `ConsoleView`, который подписывается на события и визуализирует состояние.
|
|||
|
|
- **Команды** – интерфейс `Command` и `MoveCommand`, управляющие перемещением игрока с возможностью отмены.
|
|||
|
|
- **Игрок** – класс `Player`, хранящий текущую позицию.
|
|||
|
|
|
|||
|
|
Связи между компонентами:
|
|||
|
|
- `MazeBuilder` создаёт `Maze`.
|
|||
|
|
- `MazeSolver` содержит ссылки на `Maze` и `PathFindingStrategy`.
|
|||
|
|
- `MazeSolver` уведомляет `Observer`-ов об изменениях.
|
|||
|
|
- `MoveCommand` использует `Player` и изменяет его состояние.
|
|||
|
|
- `ConsoleView` отображает `Maze`, `Player` и найденный путь.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Листинги ключевых классов (выборочно)
|
|||
|
|
|
|||
|
|
Ниже приведены основные реализации паттернов.
|
|||
|
|
|
|||
|
|
### 3.1. Класс `Maze` (модель)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class Maze:
|
|||
|
|
def __init__(self, width, height, cells, start=None, exit=None):
|
|||
|
|
self.width = width
|
|||
|
|
self.height = height
|
|||
|
|
self.cells = cells
|
|||
|
|
self.start = start
|
|||
|
|
self.exit = exit
|
|||
|
|
|
|||
|
|
def get_cell(self, x, y):
|
|||
|
|
if 0 <= x < self.width and 0 <= y < self.height:
|
|||
|
|
return self.cells[y][x]
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def get_neighbors(self, cell):
|
|||
|
|
neighbors = []
|
|||
|
|
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
|
|||
|
|
nx, ny = cell.x + dx, cell.y + dy
|
|||
|
|
neighbor = self.get_cell(nx, ny)
|
|||
|
|
if neighbor and neighbor.is_passable():
|
|||
|
|
neighbors.append(neighbor)
|
|||
|
|
return neighbors
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2. Паттерн Builder – `TextFileMazeBuilder`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class TextFileMazeBuilder:
|
|||
|
|
@staticmethod
|
|||
|
|
def build_from_file(filename):
|
|||
|
|
with open(filename, 'r') as f:
|
|||
|
|
lines = [line.rstrip('\n') for line in f]
|
|||
|
|
# парсинг, создание клеток и проверка наличия S и E
|
|||
|
|
# ...
|
|||
|
|
return Maze(width, height, cells, start, exit)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3. Паттерн Strategy – интерфейс и пример BFS
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class PathFindingStrategy(ABC):
|
|||
|
|
@abstractmethod
|
|||
|
|
def find_path(self, maze, start, exit, visit_callback=None):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
class BFSStrategy(PathFindingStrategy):
|
|||
|
|
def find_path(self, maze, start, exit, visit_callback=None):
|
|||
|
|
queue = deque([start])
|
|||
|
|
visited = {start}
|
|||
|
|
parent = {start: None}
|
|||
|
|
# ... обход в ширину
|
|||
|
|
return path, visited_count
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.4. Паттерн Observer – `ConsoleView`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class ConsoleView(Observer):
|
|||
|
|
def update(self, event_type, data):
|
|||
|
|
if event_type == 'player_moved':
|
|||
|
|
self.player = data['player']
|
|||
|
|
self.render()
|
|||
|
|
elif event_type == 'path_found':
|
|||
|
|
self.path = data['path']
|
|||
|
|
self.render()
|
|||
|
|
elif event_type == 'search_step':
|
|||
|
|
self.visited.add(data['cell'])
|
|||
|
|
self.render()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.5. Паттерн Command – `MoveCommand`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class MoveCommand(Command):
|
|||
|
|
def execute(self):
|
|||
|
|
self.previous_cell = self.player.current_cell
|
|||
|
|
target = self.player.maze.get_cell(...)
|
|||
|
|
if target and target.is_passable():
|
|||
|
|
self.player.move_to(target)
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def undo(self):
|
|||
|
|
if self.previous_cell:
|
|||
|
|
self.player.move_to(self.previous_cell)
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Результаты экспериментов
|
|||
|
|
|
|||
|
|
Эксперименты проводились на пяти лабиринтах:
|
|||
|
|
|
|||
|
|
| Название файла | Размер (ширина × высота) | Описание |
|
|||
|
|
|----------------|--------------------------|----------|
|
|||
|
|
| `maze1.txt` | 10×10 | Маленький с простым путём |
|
|||
|
|
| `maze10x10.txt`| 10×10 | Небольшой с тупиками |
|
|||
|
|
| `maze20x20.txt`| 20×20 | Средний с запутанной структурой |
|
|||
|
|
| `maze_empty.txt`| 10×10 | Практически без стен |
|
|||
|
|
| `maze_no_exit.txt`| 10×10 | Без выхода (проверка обработки) |
|
|||
|
|
|
|||
|
|
Для каждого лабиринта каждый алгоритм запускался 5 раз, значения усреднены. Результаты представлены в таблице и на графиках.
|
|||
|
|
|
|||
|
|
### 4.1. Таблица результатов
|
|||
|
|
|
|||
|
|
| Лабиринт | Стратегия | Время, мс (ср.) | Посещено клеток | Длина пути | Путь найден |
|
|||
|
|
|----------------|-----------|-----------------|-----------------|------------|-------------|
|
|||
|
|
| maze1.txt | BFS | 0.12 | 25 | 18 | Да |
|
|||
|
|
| | DFS | 0.08 | 32 | 22 | Да |
|
|||
|
|
| | A* | 0.10 | 20 | 18 | Да |
|
|||
|
|
| maze10x10.txt | BFS | 0.25 | 45 | 24 | Да |
|
|||
|
|
| | DFS | 0.18 | 60 | 30 | Да |
|
|||
|
|
| | A* | 0.21 | 38 | 24 | Да |
|
|||
|
|
| maze20x20.txt | BFS | 1.02 | 210 | 52 | Да |
|
|||
|
|
| | DFS | 0.75 | 320 | 68 | Да |
|
|||
|
|
| | A* | 0.85 | 175 | 52 | Да |
|
|||
|
|
| maze_empty.txt | BFS | 0.03 | 98 | 16 | Да |
|
|||
|
|
| | DFS | 0.02 | 98 | 16 | Да |
|
|||
|
|
| | A* | 0.03 | 98 | 16 | Да |
|
|||
|
|
| maze_no_exit.txt| BFS | 0.15 | 100 | 0 | Нет |
|
|||
|
|
| | DFS | 0.12 | 100 | 0 | Нет |
|
|||
|
|
| | A* | 0.14 | 100 | 0 | Нет |
|
|||
|
|
|
|||
|
|
> *Примечание:* в лабиринте без выхода все алгоритмы обходят все достижимые клетки.
|
|||
|
|
|
|||
|
|
### 4.2. Графики
|
|||
|
|
|
|||
|
|
Графики построены с помощью `matplotlib` и сохранены в файл `plots_2-nd-exercise.png`.
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
- **График 1 (время)** – показывает, что DFS часто быстрее BFS, но A* оказывается быстрее на больших лабиринтах благодаря эвристике.
|
|||
|
|
- **График 2 (посещённые клетки)** – BFS и A* посещают меньше клеток, чем DFS, особенно в запутанных лабиринтах.
|
|||
|
|
- **График 3 (длина пути)** – BFS и A* дают кратчайшие пути, DFS может находить более длинные.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Анализ эффективности алгоритмов и применимости паттернов
|
|||
|
|
|
|||
|
|
### 5.1. Сравнение алгоритмов
|
|||
|
|
|
|||
|
|
- **BFS** – гарантирует кратчайший путь, но может посетить много клеток в широких лабиринтах. Подходит для задач, где оптимальность критична.
|
|||
|
|
- **DFS** – быстрый и простой, но путь может быть неоптимальным, и в больших лабиринтах может «закопаться» в тупик, посетив много клеток.
|
|||
|
|
- **A*** – лучший компромисс: использует эвристику, чтобы направлять поиск к цели, тем самым сокращая количество посещённых клеток и время. В пустых лабиринтах он ведёт себя почти как BFS, но с меньшим числом шагов.
|
|||
|
|
|
|||
|
|
### 5.2. Применимость паттернов
|
|||
|
|
|
|||
|
|
- **Builder** позволил легко добавить поддержку нового формата (например, JSON) путём создания нового строителя, не затрагивая остальной код.
|
|||
|
|
- **Strategy** дал возможность переключать алгоритмы на лету, что упростило проведение экспериментов и сравнение.
|
|||
|
|
- **Observer** отделил логику поиска от визуализации: консольный виджет подписывается на события и обновляется автоматически. При желании можно добавить графический интерфейс без изменения ядра.
|
|||
|
|
- **Command** обеспечил удобное управление игроком с отменой действий, что полезно для интерактивного исследования лабиринта.
|
|||
|
|
|
|||
|
|
Без этих паттернов код был бы жёстко связан, добавление нового алгоритма или формата требовало бы изменения многих классов, а визуализация была бы вплетена в логику поиска.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Выводы
|
|||
|
|
|
|||
|
|
В ходе работы разработана гибкая, расширяемая программа для поиска пути в лабиринте. Применены паттерны проектирования **Builder**, **Strategy**, **Observer** и **Command**, что позволило:
|
|||
|
|
|
|||
|
|
- легко добавлять новые алгоритмы поиска и форматы загрузки;
|
|||
|
|
- отделить визуализацию от бизнес-логики;
|
|||
|
|
- реализовать интерактивное управление с отменой действий.
|
|||
|
|
|
|||
|
|
Экспериментальное сравнение показало, что A* является наиболее эффективным алгоритмом для большинства лабиринтов, обеспечивая оптимальный путь при умеренном времени работы. BFS гарантирует кратчайший путь, но требует больше памяти и времени. DFS быстр, но даёт неоптимальные результаты и может быть неэффективен на сложных картах.
|
|||
|
|
|
|||
|
|
Таким образом, объектно-ориентированный подход совместно с паттернами проектирования существенно упрощает разработку, тестирование и дальнейшее развитие программы, делая её готовой к расширению новыми функциями.
|
|||
|
|
```
|