1185 lines
62 KiB
Markdown
1185 lines
62 KiB
Markdown
`файл является компиляцией документации sphinx в папке docs`
|
||
# Лабораторная работа «Поиск выхода из лабиринта»
|
||
|
||
* [Задание](task.md)
|
||
* [Цель работы](task.md#id2)
|
||
* [Общая схема приложения (пример)](task.md#id3)
|
||
* [Выполнение](task.md#id4)
|
||
* [Советы](task.md#id8)
|
||
* [Этап 1. Модель лабиринта](stage1.md)
|
||
* [Класс `Cell`](stage1.md#cell)
|
||
* [Класс `Maze`](stage1.md#maze)
|
||
* [Этап 2. Загрузка лабиринта из файла](stage2.md)
|
||
* [Паттерн Builder](stage2.md#builder)
|
||
* [Класс `MazeBuilder`](stage2.md#mazebuilder)
|
||
* [Класс `TextFileBuilder`](stage2.md#textfilebuilder)
|
||
* [Использование](stage2.md#id2)
|
||
* [Известная ошибка](stage2.md#id3)
|
||
* [Этап 3. Стратегии поиска пути](stage3.md)
|
||
* [Паттерн Strategy](stage3.md#strategy)
|
||
* [Структура пакета](stage3.md#id2)
|
||
* [Класс `PathFindingStrategy`](stage3.md#pathfindingstrategy)
|
||
* [Алгоритмы](stage3.md#id3)
|
||
* [Этап 4. Класс-оркестратор MazeSolver](stage4.md)
|
||
* [Роль в архитектуре](stage4.md#id1)
|
||
* [Класс `SearchStats`](stage4.md#searchstats)
|
||
* [Класс `MazeSolver`](stage4.md#id3)
|
||
* [Этап 5. Визуализация и пошаговое управление](stage5.md)
|
||
* [5.1. Паттерн Observer](stage5.md#observer)
|
||
* [5.2. Паттерн Command](stage5.md#command)
|
||
* [Этап 6. Экспериментальная часть](stage6.md)
|
||
* [Подготовка](stage6.md#id2)
|
||
* [Замеры](stage6.md#id3)
|
||
* [Результаты](stage6.md#id4)
|
||
* [Выводы](stage6.md#id8)
|
||
* [Визуализация](stage6.md#id9)
|
||
* [Этап 7. Отчёт](stage7.md)
|
||
* [Описание задачи](stage7.md#id2)
|
||
* [Диаграмма классов](stage7.md#id3)
|
||
* [Результаты экспериментов](stage7.md#id4)
|
||
* [Выводы](stage7.md#id5)
|
||
* [API Reference](api.md)
|
||
* [Базовые модели](api.md#module-source.models.base)
|
||
* [Загрузка лабиринта](api.md#module-source.build.builder)
|
||
* [Стратегии поиска пути](api.md#module-source.strategy.algorithms)
|
||
* [Оркестратор](api.md#module-source.strategy.solver)
|
||
* [Визуализация](api.md#module-source.view.observer)
|
||
* [Управление игроком](api.md#module-source.view.command)
|
||
# Этап 2. Загрузка лабиринта из файла
|
||
|
||
Во втором этапе разработки необходимо реализовать загрузку лабиринта из текстового файла, где: `#` – стена, ` ` – проход, `S` – старт, `E` – выход.
|
||
|
||
## Систематизация файлов
|
||
|
||
Для удобного хранения лабиринтов было решено сделать систему наименования текстовых файлов в папке `source/templates`.
|
||
|
||
Общая структура:
|
||
|
||
```default
|
||
{размер}_{свойство 1}-{свойство 2}-{свойство n}_{версия}.txt
|
||
```
|
||
|
||
### Размер
|
||
|
||
Формат: `{ширина}x{высота}`
|
||
|
||
| Пример | Значение |
|
||
|-----------|----------------|
|
||
| `10x10` | 10×10 клеток |
|
||
| `50x50` | 50×50 клеток |
|
||
| `100x100` | 100×100 клеток |
|
||
| `30x30` | 30×30 клеток |
|
||
| `20x20` | 20×20 клеток |
|
||
|
||
### Свойства
|
||
|
||
| Свойство | Код | Описание |
|
||
|--------------|-------------|----------------------------------------------------------------------------------------------|
|
||
| Простой путь | `path` | Существует маршрут от S до E |
|
||
| Тупики | `deadends` | Лабиринт специально содержит тупики (могут быть и в других типах, но здесь — гарантированно) |
|
||
| Запутанный | `spaghetti` | Сложная структура с циклами и ложными ходами |
|
||
| Пустой | `empty` | Нет стен (`#`), только пробелы, S и E |
|
||
| Без выхода | `noexit` | В лабиринте отсутствует символ `E` |
|
||
|
||
### Версия
|
||
|
||
Формат: `v{номер}`
|
||
|
||
- `v1`, `v2`, `v10`
|
||
|
||
### Примеры
|
||
|
||
#### Маленькие (10×10, простой путь)
|
||
|
||
```default
|
||
10x10_path_v1.txt
|
||
10x10_path_v2.txt
|
||
...
|
||
10x10_path_v10.txt
|
||
```
|
||
|
||
#### Средние (50×50, тупики)
|
||
|
||
```default
|
||
50x50_deadends_v1.txt
|
||
50x50_deadends_v2.txt
|
||
...
|
||
50x50_deadends_v10.txt
|
||
```
|
||
|
||
#### Большие (100×100, запутанные)
|
||
|
||
```default
|
||
100x100_spaghetti_v1.txt
|
||
100x100_spaghetti_v2.txt
|
||
...
|
||
100x100_spaghetti_v10.txt
|
||
```
|
||
|
||
#### Пустые (30×30)
|
||
|
||
```default
|
||
30x30_empty_v1.txt
|
||
30x30_empty_v2.txt
|
||
...
|
||
30x30_empty_v10.txt
|
||
```
|
||
|
||
#### Без выхода (20×20)
|
||
|
||
```default
|
||
20x20_noexit_v1.txt
|
||
20x20_noexit_v2.txt
|
||
...
|
||
20x20_noexit_v10.txt
|
||
```
|
||
|
||
#### Комбинированные свойства
|
||
|
||
```default
|
||
50x50_deadends-noexit_v1.txt
|
||
100x100_spaghetti-noexit_v1.txt
|
||
10x10_path-empty_v1.txt (избыточно, но допустимо)
|
||
```
|
||
|
||
### Примечание
|
||
|
||
- Регистр имён файлов: **нижний регистр**
|
||
- Разделители: только `_` и `-`
|
||
- Расширение: `.txt`
|
||
- Кодировка: UTF-8
|
||
# Этап 1. Модель лабиринта
|
||
|
||
В первом этапе разработки необходимо создать базовые классы `Cell` и `Maze`, которые представляют карту лабиринта. Паттерны на этом этапе не применяются — только чистые классы.
|
||
|
||
## Класс `Cell`
|
||
|
||
Клетка — минимальная единица лабиринта. Хранит координаты и тип: стена, старт, выход или пустая.
|
||
|
||
По условию задания клетка должна иметь флаги `isWall`, `isStart`, `isExit` и метод `isPassable()`. В реализации флаги оформлены как **свойства** (`@property`) с сеттерами — это позволяет автоматически сбрасывать остальные флаги при установке нового типа.
|
||
|
||
```python
|
||
cell = Cell(1, 1)
|
||
cell.is_wall = True
|
||
```
|
||
|
||
Типы клетки взаимоисключают друг друга — клетка не может быть одновременно стеной и стартом. Логика сброса вынесена в приватный метод `_clear_flags()`.
|
||
|
||
### Символьное представление
|
||
|
||
Для вывода лабиринта в консоль каждая клетка возвращает символ через `__str__`. Символы берутся из `cell_mapping` в `source/settings.py`, что позволяет менять отображение без правки классов:
|
||
|
||
| Тип | Символ по умолчанию |
|
||
|--------|-----------------------|
|
||
| Стена | `#` |
|
||
| Старт | `S` |
|
||
| Выход | `E` |
|
||
| Пустая | |
|
||
|
||
## Класс `Maze`
|
||
|
||
Лабиринт хранит двумерный список клеток и предоставляет методы для работы с ними.
|
||
|
||
По условию задания требовались методы `getCell(x, y)` и `getNeighbors(cell)`. В реализации добавлено несколько вещей сверх задания:
|
||
|
||
### Именование методов
|
||
|
||
Задание написано в стиле Java/pseudocode — названия методов и полей используют `camelCase` (`isWall`, `getCell`, `isPassable`). В Python принят другой стандарт именования — **PEP 8**, который предписывает `snake_case` для методов и атрибутов. Поэтому все названия были приведены к Python стилю:
|
||
|
||
| Задание | Реализация |
|
||
|---------------------------|-----------------------------|
|
||
| `isWall` | `is_wall` |
|
||
| `isStart` | `is_start` |
|
||
| `isExit` | `is_exit` |
|
||
| `isPassable()` | `is_possible()` |
|
||
| `getCell(x, y)` | `get_cell(x, y)` |
|
||
| `getNeighbors(cell)` | `get_neighbors(x, y)` |
|
||
| `buildFromFile(filename)` | `build_from_file(filename)` |
|
||
|
||
Это соответствует стандарту оформления кода на Python и делает API классов идиоматичным для языка.
|
||
|
||
### Индексация `maze[row, col]`
|
||
|
||
Вместо явного вызова `get_cell()` реализованы `__getitem__` и `__setitem__`, что позволяет обращаться к клеткам естественным образом:
|
||
|
||
```python
|
||
maze[0, 0] = cell_mapping['wall'] # установить стену
|
||
cell = maze[2, 3] # получить клетку
|
||
```
|
||
|
||
Обратите внимание: индексация идёт в формате `[row, col]`, то есть сначала строка (Y), потом столбец (X) — аналогично numpy.
|
||
|
||
### Свойства `start` и `exit`
|
||
|
||
Добавлены свойства для быстрого получения стартовой и выходной клетки без ручного обхода:
|
||
|
||
```python
|
||
maze.start # Cell или None
|
||
maze.exit # Cell или None
|
||
```
|
||
|
||
Это оказалось необходимым при реализации алгоритмов поиска пути — стратегии получают `start` и `exit` автоматически из лабиринта.
|
||
|
||
### Свойство `shape`
|
||
|
||
По аналогии с numpy добавлено свойство `shape`, возвращающее `(height, width)`:
|
||
|
||
```python
|
||
rows, cols = maze.shape
|
||
```
|
||
|
||
Используется в стратегиях поиска и тестах для итерации по лабиринту.
|
||
|
||
### `get_neighbors`
|
||
|
||
Метод возвращает список проходимых соседей клетки по четырём направлениям. Стены и клетки за границей лабиринта исключаются автоматически. Если переданные координаты вне границ — возвращает `None`.
|
||
|
||
```python
|
||
neighbors = maze.get_neighbors(2, 2) # список Cell
|
||
```
|
||
|
||
Направления обхода: вниз → вправо → вверх → влево (порядок влияет на поведение DFS).
|
||
# Этап 2. Загрузка лабиринта из файла
|
||
|
||
Во втором этапе реализована загрузка лабиринта из текстового файла с применением паттерна **Builder**.
|
||
|
||
## Паттерн Builder
|
||
|
||
Процесс создания лабиринта из файла включает несколько шагов: чтение файла, валидацию структуры, парсинг символов и заполнение клеток. Builder скрывает эти детали от клиента — снаружи виден только один метод `build_from_file()`, внутри которого сосредоточена вся логика построения.
|
||
|
||
Дополнительное преимущество: в будущем можно легко добавить новый формат (например, JSON или бинарный) через новую реализацию `MazeBuilder` без изменения остального кода.
|
||
|
||
## Класс `MazeBuilder`
|
||
|
||
Абстрактный базовый класс — интерфейс паттерна Builder. Объявляет единственный метод `build_from_file()`, который обязан реализовать каждый конкретный строитель.
|
||
|
||
По условию задания интерфейс назывался `MazeBuilder` с методом `buildFromFile`. В реализации название метода приведено к **PEP 8** — `build_from_file`. Сам класс оформлен через `ABC` — попытка создать объект `MazeBuilder()` напрямую вызовет `TypeError`.
|
||
|
||
## Класс `TextFileBuilder`
|
||
|
||
Конкретная реализация строителя для текстовых файлов. Загружает лабиринт из `.txt` файла где `#` — стена, — проход, `S` — старт, `E` — выход.
|
||
|
||
Процесс построения разбит на три приватных шага:
|
||
|
||
### `_read_file`
|
||
|
||
Читает файл построчно и обрезает символы переноса строки `\n` и `\r`. Возвращает список строк — каждая строка соответствует одной строке лабиринта.
|
||
|
||
### `_test_text_maze`
|
||
|
||
Валидирует структуру: проверяет что все строки одинаковой длины. Если нет — лабиринт некорректен и `_create_maze` выбросит `ValueError`.
|
||
|
||
Реализован как `@staticmethod` — не использует состояние объекта, только входные данные.
|
||
|
||
### `_create_maze`
|
||
|
||
Создаёт объект `Maze` нужного размера и заполняет его клетки символами из файла через `maze[y, x] = symbol`. Тип каждой клетки определяется автоматически через `cell_mapping` в `__setitem__` лабиринта.
|
||
|
||
## Использование
|
||
|
||
```python
|
||
from source.build.builder import TextFileBuilder
|
||
|
||
maze = TextFileBuilder().build_from_file('source/templates/10x10_path_v1.txt')
|
||
print(maze)
|
||
```
|
||
|
||
## Известная ошибка
|
||
|
||
В текущей реализации `_create_maze` есть опечатка при вычислении `width`:
|
||
|
||
```python
|
||
height, width = len(text_maze), len(text_maze) # width всегда равен height
|
||
```
|
||
|
||
Правильная версия:
|
||
|
||
```python
|
||
height, width = len(text_maze), len(text_maze[0])
|
||
```
|
||
|
||
На квадратных лабиринтах (10×10, 50×50) это не проявляется, но на прямоугольных даст некорректный результат.
|
||
# Этап 3. Стратегии поиска пути
|
||
|
||
В третьем этапе реализованы алгоритмы поиска пути с применением паттерна **Strategy**.
|
||
|
||
## Паттерн Strategy
|
||
|
||
Все три алгоритма реализуют один интерфейс `PathFindingStrategy`. Это позволяет переключать алгоритм в любой момент без изменения кода клиента — достаточно передать другой объект стратегии:
|
||
|
||
```python
|
||
solver = MazeSolver(maze, BFSStrategy())
|
||
solver.set_strategy(AStarStrategy())
|
||
```
|
||
|
||
Новый алгоритм добавляется реализацией интерфейса — остальной код трогать не нужно.
|
||
|
||
## Структура пакета
|
||
|
||
Стратегии разбиты по отдельным файлам, а `__init__.py` собирает всё в один импорт:
|
||
|
||
```default
|
||
source/strategy/
|
||
├── __init__.py ← единственный импорт для пользователя
|
||
├── algorithms.py ← базовый класс PathFindingStrategy
|
||
├── bfs.py
|
||
├── dfs.py
|
||
└── astar.py
|
||
```
|
||
|
||
```python
|
||
from source.strategy import BFSStrategy, DFSStrategy, AStarStrategy
|
||
```
|
||
|
||
## Класс `PathFindingStrategy`
|
||
|
||
Абстрактный базовый класс — интерфейс паттерна. Объявляет абстрактный метод `find_path()` и содержит два вспомогательных метода, общих для всех стратегий.
|
||
|
||
По условию задания метод назывался `findPath` — приведён к **PEP 8** как `find_path`.
|
||
|
||
### `_validate`
|
||
|
||
Добавлен в процессе разработки — изначально в задании не было требования к обработке отсутствия старта или выхода. Проблема проявилась при тестировании лабиринтов типа `noexit`: алгоритм падал с `AttributeError` внутри, вместо понятного сообщения.
|
||
|
||
`_validate` подставляет `start` и `exit` из лабиринта если они не переданы явно, и выбрасывает `ValueError` с понятным сообщением если клетки не найдены:
|
||
|
||
```python
|
||
start, exit = self._validate(maze, start, exit)
|
||
```
|
||
|
||
Вынесен в базовый класс чтобы не дублировать в каждом алгоритме.
|
||
|
||
### `_reconstruct_path`
|
||
|
||
Восстанавливает путь по словарю предков `came_from`. Все три алгоритма строят этот словарь одинаково — `{клетка: откуда_пришли}` — поэтому восстановление вынесено в общий метод базового класса.
|
||
|
||
Алгоритм идёт от выхода к старту по цепочке предков, затем разворачивает список:
|
||
|
||
```default
|
||
exit → D → C → B → start (идём по came_from)
|
||
start → B → C → D → exit (после reverse)
|
||
```
|
||
|
||
## Алгоритмы
|
||
|
||
### BFS — `BFSStrategy`
|
||
|
||
Поиск в ширину. Использует `deque` как очередь (FIFO) — каждый раз берём самую старую клетку из начала. Это гарантирует послойный обход и кратчайший путь по количеству шагов.
|
||
|
||
Сложность: O(V + E) по времени и памяти.
|
||
|
||
### DFS — `DFSStrategy`
|
||
|
||
Поиск в глубину. Использует `list` как стек (LIFO) — каждый раз берём самую свежую клетку с конца. Алгоритм ныряет вглубь по одному направлению до тупика, затем возвращается.
|
||
|
||
Не гарантирует кратчайший путь. На запутанных лабиринтах может обойти почти все клетки прежде чем найти выход, хотя по времени часто быстрее BFS из-за меньших накладных расходов на структуру данных.
|
||
|
||
Сложность: O(V + E) по времени и памяти.
|
||
|
||
### A\* — `AStarStrategy`
|
||
|
||
Использует `heapq` как приоритетную очередь. На каждом шаге выбирает клетку с минимальным значением `f = g + h`, где `g` — стоимость пути от старта, `h` — манхэттенская эвристика до выхода.
|
||
|
||
Эвристика направляет поиск в сторону выхода, поэтому A\* обходит меньше клеток чем BFS при том же гарантированно кратчайшем пути.
|
||
|
||
В кортеж приоритетной очереди добавлен счётчик `counter` как tie-breaker — без него `heapq` попытался бы сравнивать объекты `Cell` при одинаковом `f`, что вызвало бы `TypeError`:
|
||
|
||
```python
|
||
heapq.heappush(open_heap, (f, counter, neighbor))
|
||
```
|
||
|
||
Сложность: O(E · log V) в худшем случае.
|
||
# Этап 4. Класс-оркестратор MazeSolver
|
||
|
||
В четвёртом этапе реализован класс `MazeSolver`, который объединяет лабиринт и стратегию поиска, выполняет поиск и собирает статистику.
|
||
|
||
## Роль в архитектуре
|
||
|
||
`MazeSolver` — точка входа для клиентского кода. Он не знает деталей ни одного алгоритма и не работает напрямую с клетками лабиринта — только делегирует задачу стратегии и замеряет время:
|
||
|
||
```python
|
||
solver = MazeSolver(maze, BFSStrategy())
|
||
stats = solver.solve()
|
||
print(stats)
|
||
# Время: 0.041 мс | Посещено клеток: 13 | Длина пути: 13
|
||
```
|
||
|
||
## Класс `SearchStats`
|
||
|
||
Оформлен через `@dataclass` — это избавляет от ручного `__init__` и автоматически даёт `__repr__`. Хранит четыре поля: время выполнения, количество посещённых клеток, длину пути и сам путь как список клеток.
|
||
|
||
`__str__` переопределён для удобного вывода в консоль и отчётах.
|
||
|
||
### Ограничение
|
||
|
||
В текущей реализации `visited_count` и `path_length` всегда равны друг другу — оба вычисляются как `len(path)`. Это потому что стратегии возвращают только финальный путь, а не все посещённые клетки. Чтобы получить точное количество посещений, потребовалось бы дорабатывать каждую стратегию — добавлять счётчик внутри `find_path`. На данном этапе это сознательное упрощение.
|
||
|
||
## Класс `MazeSolver`
|
||
|
||
### `set_strategy`
|
||
|
||
Позволяет менять алгоритм без пересоздания солвера. Это и есть основная демонстрация паттерна Strategy в действии — один объект, разные алгоритмы:
|
||
|
||
```python
|
||
solver = MazeSolver(maze, BFSStrategy())
|
||
stats_bfs = solver.solve()
|
||
|
||
solver.set_strategy(AStarStrategy())
|
||
stats_astar = solver.solve()
|
||
```
|
||
|
||
### `solve`
|
||
|
||
Замеряет время через `time.perf_counter()` — самый точный таймер в Python для коротких интервалов, не зависящий от системных часов. Результат переводится в миллисекунды умножением на 1000.
|
||
|
||
`start` и `exit` можно не передавать — стратегия найдёт их сама через `_validate`. Явная передача нужна только если хочется запустить поиск не от стандартного старта, а от произвольной клетки.
|
||
# Этап 5. Визуализация и пошаговое управление
|
||
|
||
В пятом этапе реализованы два паттерна: **Observer** для отображения событий и **Command** для пошагового управления игроком.
|
||
|
||
## 5.1. Паттерн Observer
|
||
|
||
### Идея
|
||
|
||
`MazeSolver` и игровой цикл не знают как именно отображать происходящее — они просто генерируют события. Наблюдатели подписываются на эти события и реагируют по своему усмотрению. Это позволяет в будущем добавить, например, `FileLogger` или графический интерфейс без изменения основного кода.
|
||
|
||
### Класс `Event`
|
||
|
||
Оформлен через `@dataclass`. Хранит тип события строкой и словарь `payload` с дополнительными данными. Поддерживаются четыре типа событий:
|
||
|
||
| Тип | Когда генерируется |
|
||
|---------------|----------------------------|
|
||
| `maze_loaded` | Лабиринт загружен из файла |
|
||
| `path_found` | Алгоритм нашёл путь |
|
||
| `no_path` | Путь не найден |
|
||
| `move` | Игрок сделал ход |
|
||
|
||
### Класс `Observer`
|
||
|
||
Абстрактный базовый класс с единственным методом `update(event)`. Любой наблюдатель обязан его реализовать.
|
||
|
||
### Класс `ConsoleView`
|
||
|
||
Конкретная реализация наблюдателя. Обрабатывает события через `match/case` и вызывает `render()` для перерисовки лабиринта.
|
||
|
||
Метод `render()` принимает лабиринт, опциональную позицию игрока и опциональный путь. Путь преобразуется в `set` для быстрой проверки принадлежности клетки — это O(1) вместо O(n) при каждом обходе:
|
||
|
||
```python
|
||
path_set = set(path) if path else set()
|
||
```
|
||
|
||
Лабиринт обрамляется рамкой из `+` и `─` для читаемости в консоли. Символы игрока и пути вынесены в константы класса — легко поменять без правки логики:
|
||
|
||
```python
|
||
PLAYER_SYMBOL = "P"
|
||
PATH_SYMBOL = "·"
|
||
```
|
||
|
||
## 5.2. Паттерн Command
|
||
|
||
### Идея
|
||
|
||
Каждое перемещение игрока оборачивается в объект `MoveCommand`. Это позволяет сохранить предыдущее состояние и отменить ход — реализация `undo` становится тривиальной.
|
||
|
||
### Класс `Player`
|
||
|
||
Простой контейнер для текущей клетки игрока. Намеренно минималистичный — вся логика перемещения и проверок находится в команде, а не в игроке.
|
||
|
||
### Класс `Command`
|
||
|
||
Абстрактный интерфейс с двумя методами: `execute()` и `undo()`. `execute()` возвращает `bool` — это отличие от классического варианта паттерна, где команды не возвращают значений. Возврат `False` нужен чтобы не добавлять неуспешный ход в историю.
|
||
|
||
### Класс `MoveCommand`
|
||
|
||
Хранит ссылку на игрока, направление и лабиринт. При `execute()` проверяет проходимость целевой клетки, сохраняет текущую в `_prev_cell` и перемещает игрока. При `undo()` восстанавливает `_prev_cell`.
|
||
|
||
Направления вынесены в словарь `DIRECTIONS` на уровне модуля:
|
||
|
||
```python
|
||
DIRECTIONS = {
|
||
"w": (0, -1), # вверх
|
||
"s": (0, 1), # вниз
|
||
"a": (-1, 0), # влево
|
||
"d": (1, 0), # вправо
|
||
}
|
||
```
|
||
|
||
### Класс `CommandHistory`
|
||
|
||
Стек выполненных команд. Хранит только успешные ходы — неуспешные (`execute()` вернул `False`) в историю не добавляются. `undo()` снимает последнюю команду со стека и вызывает её `undo()`.
|
||
|
||
Пример игрового цикла:
|
||
|
||
```python
|
||
cmd = MoveCommand(player, 'd', maze)
|
||
if cmd.execute():
|
||
history.push(cmd) # добавляем только успешный ход
|
||
|
||
history.undo() # отмена последнего хода
|
||
```
|
||
# Этап 6. Экспериментальная часть
|
||
|
||
В шестом этапе проведено сравнение эффективности трёх стратегий поиска пути на лабиринтах разной сложности. Эксперимент реализован в Jupyter Notebook (`practice/main.ipynb`).
|
||
|
||
## Подготовка
|
||
|
||
Лабиринты загружаются из папки `source/templates` автоматически — все файлы считываются через `os.listdir` и передаются в `TextFileBuilder`. Стратегии собраны в словарь для удобной итерации:
|
||
|
||
```python
|
||
strategies = {
|
||
"BFS": BFSStrategy(),
|
||
"DFS": DFSStrategy(),
|
||
"A*": AStarStrategy(),
|
||
}
|
||
```
|
||
|
||
## Замеры
|
||
|
||
Каждая пара лабиринт + стратегия запускается **10 раз**, результаты усредняются. Это сглаживает разброс из-за кэширования и фоновой активности системы.
|
||
|
||
Лабиринты типа `noexit` пропускаются автоматически — стратегия выбрасывает `ValueError`, который перехватывается через `try/except`, и выполнение продолжается.
|
||
|
||
Результаты собираются в список словарей и затем преобразуются в `DataFrame` через pandas.
|
||
|
||
## Результаты
|
||
|
||
### 10×10 (простой путь)
|
||
|
||
На маленьких лабиринтах все три алгоритма показывают практически одинаковое время (~0.03–0.07 мс) и одинаковую длину пути. Разница незначительна — лабиринт слишком мал чтобы эвристика A\* давала преимущество.
|
||
|
||
### 50×50 (тупики)
|
||
|
||
BFS стабильно быстрее DFS по времени при одинаковой длине пути. DFS заходит в каждый тупик до конца и тратит время на возврат, хотя финальный путь совпадает. A\* показывает время между BFS и DFS.
|
||
|
||
### 100×100 (запутанный, spaghetti)
|
||
|
||
Наиболее показательные результаты:
|
||
|
||
| Стратегия | Время (мс) | Длина пути |
|
||
|-------------|--------------|--------------|
|
||
| BFS | ~9 | ~210 |
|
||
| DFS | ~7 | ~2200 |
|
||
| A\* | ~8 | ~210 |
|
||
|
||
DFS быстрее по времени, но находит путь в 10 раз длиннее — обходит почти весь лабиринт. BFS и A\* находят кратчайший путь, A\* при этом чуть быстрее за счёт эвристики.
|
||
|
||
### 30×30 (пустой)
|
||
|
||
Неожиданный результат: DFS быстрее всех (~0.73 мс против 1.1 у BFS и 2.0 у A\*), хотя находит путь из 379 клеток против 55 у BFS. На пустом поле без стен DFS сразу уходит вглубь без возвратов, тогда как BFS строит очередь и обходит клетки волнами во все стороны — это накладные расходы на структуру данных.
|
||
|
||
A\* на пустом лабиринте медленнее всех — накладные расходы на `heapq` и вычисление эвристики не окупаются когда препятствий нет.
|
||
|
||
## Выводы
|
||
|
||
- **BFS** — надёжный выбор по умолчанию. Всегда находит кратчайший путь, время предсказуемо.
|
||
- **DFS** — быстрый по времени, но путь непредсказуем. На запутанных лабиринтах может пройти весь граф. Подходит когда важна скорость, а не оптимальность пути.
|
||
- **A**\* — лучший выбор для больших лабиринтов с препятствиями. Находит кратчайший путь быстрее BFS за счёт эвристики. На простых или пустых лабиринтах проигрывает из-за накладных расходов на приоритетную очередь.
|
||
|
||
## Визуализация
|
||
|
||
![[results.png]]
|
||
# Этап 7. Отчёт
|
||
|
||
## Описание задачи
|
||
|
||
Разработать программу для загрузки лабиринта из файла, поиска пути с выбором алгоритма и сравнения их эффективности. Применены четыре паттерна GoF:
|
||
|
||
| Паттерн | Где применён |
|
||
|--------------|-----------------------------------------------|
|
||
| **Builder** | `TextFileBuilder` |
|
||
| **Strategy** | `BFSStrategy`, `DFSStrategy`, `AStarStrategy` |
|
||
| **Observer** | `ConsoleView` |
|
||
| **Command** | `MoveCommand`, `CommandHistory` |
|
||
|
||
## Диаграмма классов
|
||
|
||
## Результаты экспериментов
|
||
|
||
| Лабиринт | Быстрее всех | Кратчайший путь |
|
||
|-------------------|----------------|-------------------|
|
||
| 10×10 path | все одинаково | все одинаково |
|
||
| 50×50 deadends | BFS | BFS = A\* |
|
||
| 100×100 spaghetti | DFS | BFS = A\* |
|
||
| 30×30 empty | DFS | BFS = A\* |
|
||
|
||
- **BFS** — надёжный выбор, всегда кратчайший путь.
|
||
- **DFS** — быстрый, но путь длиннее. На 100×100 обошёл в 10 раз больше клеток.
|
||
- **A\*** — лучший на больших лабиринтах с препятствиями, проигрывает на простых из-за накладных расходов на `heapq`.
|
||
|
||
## Выводы
|
||
|
||
Паттерны сделали код расширяемым: новый алгоритм — один класс, новый формат файла — один класс, новый способ отображения — один класс. Без паттернов каждое такое изменение потребовало бы правки существующего кода.
|
||
# Задание
|
||
|
||
## Цель работы
|
||
|
||
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
|
||
|
||
## Общая схема приложения (пример)
|
||
|
||
## Выполнение
|
||
|
||
### Этап 1. Модель лабиринта (без паттернов, просто классы)
|
||
|
||
**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта.
|
||
|
||
- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена).
|
||
- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` – возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).
|
||
|
||
**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем.
|
||
|
||
### Этап 2. Загрузка лабиринта из файла – применение паттерна **Builder**
|
||
|
||
**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` – стена, ` ` (пробел) – проход, `S` – старт, `E` – выход.
|
||
|
||
- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`.
|
||
- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`.
|
||
|
||
Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`.
|
||
|
||
### Этап 3. Стратегии поиска пути – паттерн **Strategy**
|
||
|
||
**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода.
|
||
|
||
- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.
|
||
- Реализовать минимум 3 стратегии:
|
||
- **BFS** (поиск в ширину) – гарантирует кратчайший путь по количеству шагов.
|
||
- **DFS** (поиск в глубину) – быстрый, но не обязательно кратчайший.
|
||
- **A**\* (с эвристикой, например, манхэттенское расстояние) – компромисс между скоростью и оптимальностью.
|
||
- (Опционально) **Дейкстра** – полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS.
|
||
|
||
Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A\* – приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток.
|
||
|
||
Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс.
|
||
|
||
### Этап 4. Класс-оркестратор – **MazeSolver** (использует Strategy)
|
||
|
||
**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику.
|
||
|
||
- `MazeSolver` содержит поля `maze` и `strategy`.
|
||
- Метод `setStrategy(strategy)` для динамической смены алгоритма.
|
||
- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути).
|
||
- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии.
|
||
|
||
### Этап 5. Визуализация и пошаговое управление – паттерны **Observer** и **Command** (по желанию)
|
||
|
||
**5.1. Наблюдатель (Observer)** – обновление консольного интерфейса.
|
||
|
||
- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`).
|
||
- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли.
|
||
- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния.
|
||
|
||
**5.2. Команда (Command)** – для пошагового перемещения игрока по найденному пути (или ручного управления).
|
||
|
||
- Создать интерфейс `Command` с методами `execute()` и `undo()`.
|
||
- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены.
|
||
- Создать класс `Player`, хранящий текущую клетку.
|
||
- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн.
|
||
|
||
*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command – для демонстрации undo при ручном исследовании лабиринта.*
|
||
|
||
### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных)
|
||
|
||
**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности.
|
||
|
||
1. **Подготовка тестовых лабиринтов:**
|
||
- Маленький (10×10) с простым путём.
|
||
- Средний (50×50) с тупиками.
|
||
- Большой (100×100) с запутанной структурой.
|
||
- «Пустой» лабиринт (без стен) – для демонстрации максимальной производительности.
|
||
- «Без выхода» – чтобы проверить обработку отсутствия пути.
|
||
2. **Замеры:**
|
||
- Для каждого лабиринта и каждой стратегии запустить `solve()` 5–10 раз, усреднить время, количество посещённых клеток, длину пути.
|
||
- Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`.
|
||
3. **Анализ:**
|
||
- Построить графики для каждого лабиринта.
|
||
- Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях).
|
||
4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото – вес 3, песок – вес 2, асфальт – вес 1) и сравнить Дейкстру с A\* на взвешенном графе.
|
||
|
||
### Этап 7. Отчёт
|
||
|
||
**Структура отчёта:**
|
||
|
||
1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).
|
||
2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий.
|
||
3. Результаты экспериментов (таблицы, графики).
|
||
4. Анализ эффективности алгоритмов и применимости паттернов.
|
||
5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них.
|
||
|
||
## Советы
|
||
|
||
- Для A\* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`.
|
||
- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь.
|
||
- Для BFS/DFS используй `deque` (очередь) и `list` (стек).
|
||
- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки.
|
||
|
||
# API Reference
|
||
|
||
## Базовые модели
|
||
|
||
### *class* source.models.base.Cell(x: int, y: int, is_wall: bool = False, is_start: bool = False, is_exit: bool = False)
|
||
|
||
Базовые классы: `object`
|
||
|
||
Представляет одну клетку поля лабиринта.
|
||
|
||
Каждая клетка хранит свои координаты и один из четырёх возможных
|
||
типов: стена, старт, выход или пустая клетка. Типы взаимоисключают
|
||
друг друга: установка одного автоматически сбрасывает остальные.
|
||
|
||
#### \_\_init_\_(x: int, y: int, is_wall: bool = False, is_start: bool = False, is_exit: bool = False)
|
||
|
||
Инициализирует клетку с заданными координатами и типом.
|
||
|
||
* **Параметры:**
|
||
* **x** – Координата клетки по оси X.
|
||
* **y** – Координата клетки по оси Y.
|
||
* **is_wall** – Если True — клетка является стеной.
|
||
* **is_start** – Если True — клетка является стартом.
|
||
* **is_exit** – Если True — клетка является выходом.
|
||
|
||
#### *property* is_exit *: bool*
|
||
|
||
True, если клетка является выходом из лабиринта.
|
||
|
||
#### is_possible() → bool
|
||
|
||
Проверяет, можно ли переместиться в эту клетку.
|
||
|
||
* **Результат:**
|
||
True, если клетка не является стеной, иначе False.
|
||
|
||
#### *property* is_start *: bool*
|
||
|
||
True, если клетка является стартовой позицией.
|
||
|
||
#### *property* is_wall *: bool*
|
||
|
||
True, если клетка является стеной.
|
||
|
||
### *class* source.models.base.Maze(size: tuple[int, int] = (10, 10))
|
||
|
||
Базовые классы: `object`
|
||
|
||
Представляет двумерный лабиринт из клеток Cell.
|
||
|
||
Лабиринт хранится как список списков клеток. Доступ к отдельным
|
||
клеткам и их изменение возможны через индексацию вида maze[row, col].
|
||
|
||
#### \_\_init_\_(size: tuple[int, int] = (10, 10))
|
||
|
||
Создаёт пустой лабиринт заданного размера.
|
||
|
||
* **Параметры:**
|
||
**size** – Кортеж (width, height) — ширина и высота лабиринта в клетках.
|
||
|
||
#### *property* exit *: [Cell](#source.models.base.Cell) | None*
|
||
|
||
#### get_cell(x: int, y: int) → [Cell](#source.models.base.Cell) | None
|
||
|
||
Возвращает клетку по координатам или None, если координаты вне границ.
|
||
|
||
* **Параметры:**
|
||
* **x** – Координата по оси X.
|
||
* **y** – Координата по оси Y.
|
||
* **Результат:**
|
||
Объект Cell, если координаты корректны, иначе None.
|
||
|
||
#### get_neighbors(x: int, y: int) → list[[Cell](#source.models.base.Cell)] | None
|
||
|
||
Возвращает список проходимых соседей клетки (вверх, вправо, вниз, влево).
|
||
|
||
* **Параметры:**
|
||
* **x** – Координата клетки по оси X.
|
||
* **y** – Координата клетки по оси Y.
|
||
* **Результат:**
|
||
Список проходимых соседних клеток, или None если (x, y) вне границ.
|
||
|
||
#### *property* shape *: tuple[int, int]*
|
||
|
||
#### *property* start *: [Cell](#source.models.base.Cell) | None*
|
||
|
||
## Загрузка лабиринта
|
||
|
||
### *class* source.build.builder.MazeBuilder
|
||
|
||
Базовые классы: `ABC`
|
||
|
||
#### *abstractmethod* build_from_file(filename: str) → [Maze](#source.models.base.Maze)
|
||
|
||
Возвращает объект лабиринта по указанному пути файлу.
|
||
|
||
* **Параметры:**
|
||
**filename** (*str*) – Путь к файлу
|
||
* **Исключение:**
|
||
**TypeError** – Если введен путь файла с нерассмотренным расширением
|
||
* **Результат:**
|
||
Объект лабиринта
|
||
* **Тип результата:**
|
||
[Maze](#source.models.base.Maze)
|
||
|
||
### *class* source.build.builder.TextFileBuilder
|
||
|
||
Базовые классы: [`MazeBuilder`](#source.build.builder.MazeBuilder)
|
||
|
||
#### build_from_file(filename: str) → [Maze](#source.models.base.Maze)
|
||
|
||
Возвращает объект лабиринта по указанному пути файлу.
|
||
|
||
* **Параметры:**
|
||
**filename** (*str*) – Путь к файлу
|
||
* **Исключение:**
|
||
**TypeError** – Если введен путь файла с нерассмотренным расширением
|
||
* **Результат:**
|
||
Объект лабиринта
|
||
* **Тип результата:**
|
||
[Maze](#source.models.base.Maze)
|
||
|
||
## Стратегии поиска пути
|
||
|
||
### *class* source.strategy.algorithms.PathFindingStrategy
|
||
|
||
Базовые классы: `ABC`
|
||
|
||
Интерфейс стратегии поиска пути в лабиринте.
|
||
|
||
#### *abstractmethod* find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) = None, exit: [Cell](#source.models.base.Cell) = None) → list[[Cell](#source.models.base.Cell)]
|
||
|
||
Найти путь от start до exit.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **start** – Стартовая клетка.
|
||
* **exit** – Целевая клетка.
|
||
* **Результат:**
|
||
Список клеток пути (от start до exit включительно).
|
||
Пустой список, если путь не найден.
|
||
|
||
<a id="module-source.strategy.bfs"></a>
|
||
|
||
### *class* source.strategy.bfs.BFSStrategy
|
||
|
||
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
|
||
|
||
Поиск в ширину (Breadth-First Search).
|
||
|
||
Гарантирует кратчайший путь по количеству шагов.
|
||
Сложность: O(V + E) по времени и памяти.
|
||
|
||
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
|
||
|
||
Найти путь от start до exit.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **start** – Стартовая клетка.
|
||
* **exit** – Целевая клетка.
|
||
* **Результат:**
|
||
Список клеток пути (от start до exit включительно).
|
||
Пустой список, если путь не найден.
|
||
|
||
<a id="module-source.strategy.dfs"></a>
|
||
|
||
### *class* source.strategy.dfs.DFSStrategy
|
||
|
||
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
|
||
|
||
Поиск в глубину (Depth-First Search).
|
||
|
||
Находит путь, но не гарантирует кратчайший.
|
||
|
||
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
|
||
|
||
Найти путь от start до exit.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **start** – Стартовая клетка.
|
||
* **exit** – Целевая клетка.
|
||
* **Результат:**
|
||
Список клеток пути (от start до exit включительно).
|
||
Пустой список, если путь не найден.
|
||
|
||
<a id="module-source.strategy.astar"></a>
|
||
|
||
### *class* source.strategy.astar.AStarStrategy
|
||
|
||
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
|
||
|
||
Алгоритм A\* с манхэттенской эвристикой.
|
||
|
||
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
|
||
|
||
Найти путь от start до exit.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **start** – Стартовая клетка.
|
||
* **exit** – Целевая клетка.
|
||
* **Результат:**
|
||
Список клеток пути (от start до exit включительно).
|
||
Пустой список, если путь не найден.
|
||
|
||
## Оркестратор
|
||
|
||
### *class* source.strategy.solver.MazeSolver(maze: [Maze](#source.models.base.Maze), strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy))
|
||
|
||
Базовые классы: `object`
|
||
|
||
Оркестратор поиска пути в лабиринте.
|
||
|
||
Принимает лабиринт и стратегию поиска, выполняет поиск
|
||
и возвращает результат вместе со статистикой выполнения.
|
||
|
||
### Пример
|
||
|
||
solver = MazeSolver(maze, BFSStrategy())
|
||
stats = solver.solve()
|
||
print(stats)
|
||
|
||
solver.set_strategy(AStarStrategy())
|
||
stats = solver.solve()
|
||
|
||
#### \_\_init_\_(maze: [Maze](#source.models.base.Maze), strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy)) → None
|
||
|
||
Инициализирует солвер с лабиринтом и стратегией поиска.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **strategy** – Стратегия поиска пути.
|
||
|
||
#### set_strategy(strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy)) → None
|
||
|
||
Заменяет текущую стратегию поиска.
|
||
|
||
* **Параметры:**
|
||
**strategy** – Новая стратегия поиска пути.
|
||
|
||
#### solve(start: [Cell](#source.models.base.Cell) = None, exit: [Cell](#source.models.base.Cell) = None) → [SearchStats](#source.strategy.solver.SearchStats)
|
||
|
||
Выполняет поиск пути и собирает статистику.
|
||
|
||
Если start или exit не переданы явно, стратегия найдёт
|
||
их самостоятельно по флагам is_start / is_exit в лабиринте.
|
||
|
||
* **Параметры:**
|
||
* **start** – Стартовая клетка (опционально).
|
||
* **exit** – Конечная клетка (опционально).
|
||
* **Результат:**
|
||
Объект SearchStats с временем выполнения, количеством
|
||
посещённых клеток и длиной найденного пути.
|
||
|
||
### *class* source.strategy.solver.SearchStats(elapsed_ms: float, visited_count: int, path_length: int, path: list[[Cell](#source.models.base.Cell)])
|
||
|
||
Базовые классы: `object`
|
||
|
||
Статистика выполнения поиска пути.
|
||
|
||
#### elapsed_ms
|
||
|
||
Время выполнения в миллисекундах.
|
||
|
||
* **Type:**
|
||
float
|
||
|
||
#### visited_count
|
||
|
||
Количество посещённых клеток.
|
||
|
||
* **Type:**
|
||
int
|
||
|
||
#### path_length
|
||
|
||
Длина найденного пути (0 если путь не найден).
|
||
|
||
* **Type:**
|
||
int
|
||
|
||
#### path
|
||
|
||
Найденный путь — список клеток от старта до выхода.
|
||
|
||
* **Type:**
|
||
list[[source.models.base.Cell](#source.models.base.Cell)]
|
||
|
||
#### \_\_init_\_(elapsed_ms: float, visited_count: int, path_length: int, path: list[[Cell](#source.models.base.Cell)]) → None
|
||
|
||
#### elapsed_ms *: float*
|
||
|
||
#### path *: list[[Cell](#source.models.base.Cell)]*
|
||
|
||
#### path_length *: int*
|
||
|
||
#### visited_count *: int*
|
||
|
||
## Визуализация
|
||
|
||
### *class* source.view.observer.ConsoleView
|
||
|
||
Базовые классы: [`Observer`](#source.view.observer.Observer)
|
||
|
||
Отображает состояние лабиринта и события в консоли.
|
||
|
||
#### PATH_SYMBOL *= '·'*
|
||
|
||
#### PLAYER_SYMBOL *= 'P'*
|
||
|
||
#### render(maze: [Maze](#source.models.base.Maze), player: [Cell](#source.models.base.Cell) | None = None, path: list[[Cell](#source.models.base.Cell)] | None = None) → None
|
||
|
||
Рисует лабиринт в консоли.
|
||
|
||
Путь отмечается символом „·“, позиция игрока — „P“.
|
||
|
||
* **Параметры:**
|
||
* **maze** – Объект лабиринта.
|
||
* **player** – Текущая клетка игрока (опционально).
|
||
* **path** – Список клеток найденного пути (опционально).
|
||
|
||
#### update(event: [Event](#source.view.observer.Event)) → None
|
||
|
||
Реагирует на события и выводит информацию в консоль.
|
||
|
||
* **Параметры:**
|
||
**event** – Объект события.
|
||
|
||
### *class* source.view.observer.Event(type: str, payload: dict = None)
|
||
|
||
Базовые классы: `object`
|
||
|
||
Событие, передаваемое наблюдателям.
|
||
|
||
#### type
|
||
|
||
Тип события („maze_loaded“, „path_found“, „move“, „no_path“).
|
||
|
||
* **Type:**
|
||
str
|
||
|
||
#### payload
|
||
|
||
Дополнительные данные события.
|
||
|
||
* **Type:**
|
||
dict
|
||
|
||
#### \_\_init_\_(type: str, payload: dict = None) → None
|
||
|
||
#### payload *: dict* *= None*
|
||
|
||
#### type *: str*
|
||
|
||
### *class* source.view.observer.Observer
|
||
|
||
Базовые классы: `ABC`
|
||
|
||
Интерфейс наблюдателя за событиями лабиринта.
|
||
|
||
#### *abstractmethod* update(event: [Event](#source.view.observer.Event)) → None
|
||
|
||
Обрабатывает входящее событие.
|
||
|
||
* **Параметры:**
|
||
**event** – Объект события с типом и данными.
|
||
|
||
## Управление игроком
|
||
|
||
### *class* source.view.command.Command
|
||
|
||
Базовые классы: `ABC`
|
||
|
||
Интерфейс команды с поддержкой отмены.
|
||
|
||
#### *abstractmethod* execute() → bool
|
||
|
||
Выполняет команду.
|
||
|
||
* **Результат:**
|
||
True если команда выполнена успешно, False иначе.
|
||
|
||
#### *abstractmethod* undo() → None
|
||
|
||
Отменяет команду, восстанавливая предыдущее состояние.
|
||
|
||
### *class* source.view.command.CommandHistory
|
||
|
||
Базовые классы: `object`
|
||
|
||
Хранит историю выполненных команд и позволяет отменять их.
|
||
|
||
### Пример
|
||
|
||
history = CommandHistory()
|
||
cmd = MoveCommand(player, „w“, maze)
|
||
if cmd.execute():
|
||
|
||
> history.push(cmd)
|
||
|
||
history.undo() # отменяет последний успешный ход
|
||
|
||
#### \_\_init_\_() → None
|
||
|
||
#### clear() → None
|
||
|
||
Очищает историю команд.
|
||
|
||
#### push(command: [Command](#source.view.command.Command)) → None
|
||
|
||
Добавляет выполненную команду в историю.
|
||
|
||
* **Параметры:**
|
||
**command** – Успешно выполненная команда.
|
||
|
||
#### undo() → bool
|
||
|
||
Отменяет последнюю команду из истории.
|
||
|
||
* **Результат:**
|
||
True если отмена выполнена, False если история пуста.
|
||
|
||
### *class* source.view.command.MoveCommand(player: [Player](#source.view.command.Player), direction: str, maze: [Maze](#source.models.base.Maze))
|
||
|
||
Базовые классы: [`Command`](#source.view.command.Command)
|
||
|
||
Перемещает игрока в заданном направлении.
|
||
|
||
Сохраняет предыдущую клетку для возможности отмены хода.
|
||
|
||
#### \_\_init_\_(player: [Player](#source.view.command.Player), direction: str, maze: [Maze](#source.models.base.Maze)) → None
|
||
|
||
Инициализирует команду перемещения.
|
||
|
||
* **Параметры:**
|
||
* **player** – Объект игрока.
|
||
* **direction** – Направление („w“, „a“, „s“, „d“).
|
||
* **maze** – Объект лабиринта для проверки проходимости.
|
||
* **Исключение:**
|
||
**ValueError** – Если направление не распознано.
|
||
|
||
#### execute() → bool
|
||
|
||
Перемещает игрока если целевая клетка проходима.
|
||
|
||
* **Результат:**
|
||
True если перемещение выполнено, False если клетка непроходима.
|
||
|
||
#### undo() → None
|
||
|
||
Возвращает игрока на предыдущую клетку.
|
||
|
||
### *class* source.view.command.Player(cell: [Cell](#source.models.base.Cell))
|
||
|
||
Базовые классы: `object`
|
||
|
||
Хранит текущее положение игрока в лабиринте.
|
||
|
||
#### cell
|
||
|
||
Текущая клетка игрока.
|
||
|
||
#### \_\_init_\_(cell: [Cell](#source.models.base.Cell)) → None
|
||
|
||
Инициализирует игрока на заданной клетке.
|
||
|
||
* **Параметры:**
|
||
**cell** – Начальная клетка игрока. |