632 lines
28 KiB
Plaintext
632 lines
28 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "332cd3ba-eb85-47e3-85cc-736843c10214",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Полная реализация поиска выхода из лабиринта (ООП + паттерны)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "3d027c2d-7827-4b2f-8c52-0632b97fb462",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 1. Модель лабиринта (без паттернов)\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"Создаются два класса: `Cell` (клетка) и `Maze` (лабиринт). \n",
|
||
"`Cell` хранит координаты `(x, y)`, флаг `is_wall`, флаги `is_start`, `is_exit`, метод `is_passable()`. \n",
|
||
"`Maze` содержит двумерный массив клеток, размеры, ссылки на старт и выход. \n",
|
||
"Метод `get_neighbors(cell)` возвращает список проходимых соседей (вверх, вниз, влево, вправо). \n",
|
||
"\n",
|
||
"Этот этап — основа для всех последующих алгоритмов."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "c35ca325-3402-4c1b-91d4-32f734d6d599",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import time\n",
|
||
"import csv\n",
|
||
"import heapq\n",
|
||
"from collections import deque\n",
|
||
"from abc import ABC, abstractmethod\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import pandas as pd\n",
|
||
"from dataclasses import dataclass\n",
|
||
"\n",
|
||
"class Cell:\n",
|
||
" def __init__(self, x, y, is_wall=False):\n",
|
||
" self.x = x\n",
|
||
" self.y = y\n",
|
||
" self.is_wall = is_wall\n",
|
||
" self.is_start = False\n",
|
||
" self.is_exit = False\n",
|
||
"\n",
|
||
" def is_passable(self):\n",
|
||
" return not self.is_wall\n",
|
||
"\n",
|
||
"\n",
|
||
"class Maze:\n",
|
||
" def __init__(self, width, height):\n",
|
||
" self.width = width\n",
|
||
" self.height = height\n",
|
||
" self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]\n",
|
||
" self.start = None\n",
|
||
" self.exit = None\n",
|
||
"\n",
|
||
" def get_cell(self, x, y):\n",
|
||
" if 0 <= x < self.width and 0 <= y < self.height:\n",
|
||
" return self.cells[y][x]\n",
|
||
" return None\n",
|
||
"\n",
|
||
" def get_neighbors(self, cell):\n",
|
||
" neighbors = []\n",
|
||
" for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:\n",
|
||
" nx, ny = cell.x + dx, cell.y + dy\n",
|
||
" nb = self.get_cell(nx, ny)\n",
|
||
" if nb and nb.is_passable():\n",
|
||
" neighbors.append(nb)\n",
|
||
" return neighbors"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "9e10908b-e541-46e4-ad15-99555c9c5de3",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 2. Загрузка лабиринта из файла – паттерн Builder\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"Паттерн **Builder** отделяет конструирование сложного объекта (лабиринта) от его представления. \n",
|
||
"Интерфейс `MazeBuilder` объявляет метод `build_from_file(filename)`. \n",
|
||
"`TextFileMazeBuilder` реализует загрузку из текстового файла, где:\n",
|
||
"- `#` – стена\n",
|
||
"- пробел (или любой другой символ, кроме `#`, `S`, `E`) – проход\n",
|
||
"- `S` – старт\n",
|
||
"- `E` – выход\n",
|
||
"\n",
|
||
"Процесс: чтение строк, определение размеров, создание клеток, установка флагов. \n",
|
||
"Builder скрывает детали парсинга и валидации."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "eab1c38a-aef1-4de7-96b3-24df9b6becb7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class MazeBuilder(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def build_from_file(self, filename):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"\n",
|
||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||
" def build_from_file(self, filename):\n",
|
||
" with open(filename, 'r', encoding='utf-8') as f:\n",
|
||
" lines = [line.rstrip('\\n') for line in f.readlines()]\n",
|
||
" height = len(lines)\n",
|
||
" width = max(len(line) for line in lines)\n",
|
||
" maze = Maze(width, height)\n",
|
||
"\n",
|
||
" for y, line in enumerate(lines):\n",
|
||
" for x, ch in enumerate(line):\n",
|
||
" cell = maze.get_cell(x, y)\n",
|
||
" if ch == '#':\n",
|
||
" cell.is_wall = True\n",
|
||
" elif ch == 'S':\n",
|
||
" cell.is_start = True\n",
|
||
" maze.start = cell\n",
|
||
" elif ch == 'E':\n",
|
||
" cell.is_exit = True\n",
|
||
" maze.exit = cell\n",
|
||
" else:\n",
|
||
" cell.is_wall = False\n",
|
||
" return maze"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "791f75f0-ea40-496d-ad38-c827e444e6ae",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 3. Стратегии поиска пути – паттерн Strategy\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"Паттерн **Strategy** определяет семейство алгоритмов, инкапсулирует каждый и делает их взаимозаменяемыми. \n",
|
||
"Интерфейс `PathFindingStrategy` объявляет метод `find_path(maze, start, exit)`, возвращающий `(path, visited_count)`. \n",
|
||
"\n",
|
||
"Реализованы три стратегии:\n",
|
||
"\n",
|
||
"1. **BFS (поиск в ширину)** \n",
|
||
" - Использует очередь `deque`. \n",
|
||
" - Гарантирует нахождение кратчайшего пути по числу шагов. \n",
|
||
" - Сложность O(V+E). \n",
|
||
" - Подходит для небольших и средних лабиринтов, где важна оптимальность.\n",
|
||
"\n",
|
||
"2. **DFS (поиск в глубину)** \n",
|
||
" - Использует стек (список). \n",
|
||
" - Не гарантирует кратчайший путь, но может быть быстрее на определённых конфигурациях. \n",
|
||
" - Сложность O(V+E). \n",
|
||
" - Полезен, когда нужно быстро найти любой путь.\n",
|
||
"\n",
|
||
"3. **A\\*** (A-star) \n",
|
||
" - Использует приоритетную очередь (heapq) и эвристику. \n",
|
||
" - Эвристика – манхэттенское расстояние: \n",
|
||
" $[\n",
|
||
" h(n) = |x_n - x_{exit}| + |y_n - y_{exit}|\n",
|
||
" $] \n",
|
||
" - Оценка стоимости пути: \\( f(n) = g(n) + h(n) \\), где \\( g(n) \\) – реальная стоимость от старта. \n",
|
||
" - Гарантирует оптимальность при допустимой эвристике. \n",
|
||
" - На практике быстрее BFS за счёт целенаправленного поиска."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "fe37e65c-7f33-458f-9ead-37838b60316a",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class PathFindingStrategy(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def find_path(self, maze, start, exit):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"\n",
|
||
"class BFSStrategy(PathFindingStrategy):\n",
|
||
" def find_path(self, maze, start, exit):\n",
|
||
" visited = set()\n",
|
||
" if start == exit:\n",
|
||
" return [start], 1\n",
|
||
" queue = deque([start])\n",
|
||
" visited.add(start)\n",
|
||
" parent = {start: None}\n",
|
||
" while queue:\n",
|
||
" current = queue.popleft()\n",
|
||
" for nb in maze.get_neighbors(current):\n",
|
||
" if nb not in visited:\n",
|
||
" visited.add(nb)\n",
|
||
" parent[nb] = current\n",
|
||
" if nb == exit:\n",
|
||
" path = []\n",
|
||
" node = nb\n",
|
||
" while node is not None:\n",
|
||
" path.append(node)\n",
|
||
" node = parent[node]\n",
|
||
" path.reverse()\n",
|
||
" return path, len(visited)\n",
|
||
" queue.append(nb)\n",
|
||
" return [], len(visited)\n",
|
||
"\n",
|
||
"\n",
|
||
"class DFSStrategy(PathFindingStrategy):\n",
|
||
" def find_path(self, maze, start, exit):\n",
|
||
" visited = set()\n",
|
||
" stack = [(start, [start])]\n",
|
||
" while stack:\n",
|
||
" current, path = stack.pop()\n",
|
||
" if current == exit:\n",
|
||
" return path, len(visited)\n",
|
||
" visited.add(current)\n",
|
||
" for nb in maze.get_neighbors(current):\n",
|
||
" if nb not in visited:\n",
|
||
" stack.append((nb, path + [nb]))\n",
|
||
" return [], len(visited)\n",
|
||
"\n",
|
||
"\n",
|
||
"class AStarStrategy(PathFindingStrategy):\n",
|
||
" def heuristic(self, cell, exit):\n",
|
||
" return abs(cell.x - exit.x) + abs(cell.y - exit.y)\n",
|
||
"\n",
|
||
" def find_path(self, maze, start, exit):\n",
|
||
" open_set = []\n",
|
||
" counter = 0\n",
|
||
" heapq.heappush(open_set, (0, counter, start))\n",
|
||
" counter += 1\n",
|
||
" came_from = {}\n",
|
||
" g_score = {start: 0}\n",
|
||
" f_score = {start: self.heuristic(start, exit)}\n",
|
||
" visited = set()\n",
|
||
" while open_set:\n",
|
||
" _, _, current = heapq.heappop(open_set)\n",
|
||
" visited.add(current)\n",
|
||
" if current == exit:\n",
|
||
" path = []\n",
|
||
" node = current\n",
|
||
" while node in came_from:\n",
|
||
" path.append(node)\n",
|
||
" node = came_from[node]\n",
|
||
" path.append(start)\n",
|
||
" path.reverse()\n",
|
||
" return path, len(visited)\n",
|
||
" for nb in maze.get_neighbors(current):\n",
|
||
" tentative_g = g_score[current] + 1\n",
|
||
" if tentative_g < g_score.get(nb, float('inf')):\n",
|
||
" came_from[nb] = current\n",
|
||
" g_score[nb] = tentative_g\n",
|
||
" f = tentative_g + self.heuristic(nb, exit)\n",
|
||
" heapq.heappush(open_set, (f, counter, nb))\n",
|
||
" counter += 1\n",
|
||
" return [], len(visited)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6eeb7cd4-dade-40a0-b607-c08198b602ea",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 4. Класс-оркестратор MazeSolver и статистика\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"`MazeSolver` принимает лабиринт и стратегию. \n",
|
||
"Метод `solve()` замеряет время выполнения (`time.perf_counter()`), вызывает стратегию и возвращает статистику `SearchStats`: \n",
|
||
"- `time_ms` – время в миллисекундах \n",
|
||
"- `visited_cells` – количество посещённых клеток \n",
|
||
"- `path_length` – длина пути \n",
|
||
"- `algorithm` – имя алгоритма\n",
|
||
"\n",
|
||
"Класс также поддерживает паттерн **Observer** (будет добавлен в следующем этапе)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "5177c17f-6dd2-42d7-92b0-87471b5c22c1",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"@dataclass\n",
|
||
"class SearchStats:\n",
|
||
" time_ms: float\n",
|
||
" visited_cells: int\n",
|
||
" path_length: int\n",
|
||
" algorithm: str\n",
|
||
"\n",
|
||
"\n",
|
||
"class MazeSolver:\n",
|
||
" def __init__(self, maze, strategy, observers=None):\n",
|
||
" self.maze = maze\n",
|
||
" self.strategy = strategy\n",
|
||
" self.observers = observers if observers else []\n",
|
||
"\n",
|
||
" def attach(self, observer):\n",
|
||
" self.observers.append(observer)\n",
|
||
"\n",
|
||
" def detach(self, observer):\n",
|
||
" self.observers.remove(observer)\n",
|
||
"\n",
|
||
" def notify(self, event_type, data=None):\n",
|
||
" for obs in self.observers:\n",
|
||
" obs.update(event_type, data)\n",
|
||
"\n",
|
||
" def set_strategy(self, strategy):\n",
|
||
" self.strategy = strategy\n",
|
||
"\n",
|
||
" def solve(self):\n",
|
||
" if self.maze.start is None or self.maze.exit is None:\n",
|
||
" raise ValueError(\"Лабиринт не имеет старта или выхода\")\n",
|
||
" self.notify(\"search_start\")\n",
|
||
" start_time = time.perf_counter()\n",
|
||
" path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)\n",
|
||
" end_time = time.perf_counter()\n",
|
||
" if path:\n",
|
||
" self.notify(\"path_found\", len(path))\n",
|
||
" else:\n",
|
||
" self.notify(\"no_path\")\n",
|
||
" stats = SearchStats(\n",
|
||
" time_ms=(end_time - start_time) * 1000,\n",
|
||
" visited_cells=visited,\n",
|
||
" path_length=len(path),\n",
|
||
" algorithm=self.strategy.__class__.__name__\n",
|
||
" )\n",
|
||
" return path, stats"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "87114e56-ac0f-4227-b081-b7f84ebecaa3",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 5. Визуализация и пошаговое управление – паттерны Observer и Command\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"- **Observer** (`ConsoleLogger`) подписывается на события `MazeSolver` и выводит сообщения о начале поиска, нахождении пути или его отсутствии. \n",
|
||
"- **Command** – интерфейс с методами `execute()` и `undo()`. \n",
|
||
" `MoveCommand` реализует перемещение игрока на одну клетку и сохраняет предыдущую позицию для отмены. \n",
|
||
" `Player` хранит текущую клетку. \n",
|
||
"- Демонстрация: после нахождения пути для `tiny.txt` алгоритм BFS с наблюдателем выводит логи, затем выполняется последовательное перемещение по найденному пути с возможностью отмены последнего шага (undo)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "171e638f-f3ce-4278-902f-37375c33a94b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class Observer(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def update(self, event_type, data=None):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"\n",
|
||
"class ConsoleLogger(Observer):\n",
|
||
" def update(self, event_type, data=None):\n",
|
||
" if event_type == \"search_start\":\n",
|
||
" print(f\"[LOG] Поиск пути начат\")\n",
|
||
" elif event_type == \"path_found\":\n",
|
||
" print(f\"[LOG] Путь найден! Длина: {data}\")\n",
|
||
" elif event_type == \"no_path\":\n",
|
||
" print(\"[LOG] Путь не найден\")\n",
|
||
" elif event_type == \"step\":\n",
|
||
" print(f\"[LOG] Шаг: {data}\")\n",
|
||
"\n",
|
||
"\n",
|
||
"class Command(ABC):\n",
|
||
" @abstractmethod\n",
|
||
" def execute(self):\n",
|
||
" pass\n",
|
||
"\n",
|
||
" @abstractmethod\n",
|
||
" def undo(self):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"\n",
|
||
"class MoveCommand(Command):\n",
|
||
" def __init__(self, player, direction, maze):\n",
|
||
" self.player = player\n",
|
||
" self.direction = direction\n",
|
||
" self.maze = maze\n",
|
||
" self.prev_pos = None\n",
|
||
"\n",
|
||
" def execute(self):\n",
|
||
" self.prev_pos = self.player.current_cell\n",
|
||
" dx, dy = self.direction\n",
|
||
" nx, ny = self.player.current_cell.x + dx, self.player.current_cell.y + dy\n",
|
||
" new_cell = self.maze.get_cell(nx, ny)\n",
|
||
" if new_cell and new_cell.is_passable():\n",
|
||
" self.player.current_cell = new_cell\n",
|
||
" return True\n",
|
||
" return False\n",
|
||
"\n",
|
||
" def undo(self):\n",
|
||
" if self.prev_pos:\n",
|
||
" self.player.current_cell = self.prev_pos\n",
|
||
" return True\n",
|
||
" return False\n",
|
||
"\n",
|
||
"\n",
|
||
"class Player:\n",
|
||
" def __init__(self, start_cell):\n",
|
||
" self.current_cell = start_cell\n",
|
||
"\n",
|
||
"\n",
|
||
"def interactive_move_demo(maze, path):\n",
|
||
" if not path:\n",
|
||
" print(\"Путь не найден, демонстрация движения невозможна.\")\n",
|
||
" return\n",
|
||
" player = Player(maze.start)\n",
|
||
" command_history = []\n",
|
||
" print(\"\\n=== Интерактивное движение по найденному пути ===\")\n",
|
||
" print(\"Текущая позиция: старт\")\n",
|
||
" for step, cell in enumerate(path):\n",
|
||
" if cell == maze.start:\n",
|
||
" continue\n",
|
||
" prev = path[step-1]\n",
|
||
" dx = cell.x - prev.x\n",
|
||
" dy = cell.y - prev.y\n",
|
||
" cmd = MoveCommand(player, (dx, dy), maze)\n",
|
||
" cmd.execute()\n",
|
||
" command_history.append(cmd)\n",
|
||
" print(f\"Шаг {step}: перемещение на ({dx},{dy}), позиция ({player.current_cell.x},{player.current_cell.y})\")\n",
|
||
" if cell == maze.exit:\n",
|
||
" print(\"Достигнут выход!\")\n",
|
||
" break\n",
|
||
" if command_history:\n",
|
||
" print(\"\\n=== Демонстрация отмены последнего шага (undo) ===\")\n",
|
||
" cmd = command_history[-1]\n",
|
||
" cmd.undo()\n",
|
||
" print(f\"Отменён последний шаг, позиция: ({player.current_cell.x},{player.current_cell.y})\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1d67bc06-60f8-4b1d-9018-0b6cb8a8df74",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Этап 6. Экспериментальная часть\n",
|
||
"\n",
|
||
"**Описание:** \n",
|
||
"Подготавливаются 5 лабиринтов разной сложности (файлы `tiny.txt`, `medium.txt`, `large.txt`, `empty.txt`, `no_exit.txt`). \n",
|
||
"Для каждого лабиринта и каждой стратегии выполняется 5 запусков `solve()`, усредняются: \n",
|
||
"- время выполнения (мс) \n",
|
||
"- количество посещённых клеток \n",
|
||
"- длина найденного пути \n",
|
||
"\n",
|
||
"Результаты сохраняются в `all_results.csv`. \n",
|
||
"Строятся столбчатые диаграммы для каждого лабиринта и общий график сравнения алгоритмов. \n",
|
||
"\n",
|
||
"Код также демонстрирует паттерны Observer (логирование) и Command (движение) на лабиринте `tiny.txt`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "996a2948-28ca-4f04-8571-b8ce694fe2a4",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"ename": "NameError",
|
||
"evalue": "name 'BFSStrategy' is not defined",
|
||
"output_type": "error",
|
||
"traceback": [
|
||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
|
||
"Cell \u001b[1;32mIn[1], line 32\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m__main__\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m 25\u001b[0m maze_files \u001b[38;5;241m=\u001b[39m [\n\u001b[0;32m 26\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtiny.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 27\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmedium.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 30\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mno_exit.txt\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 31\u001b[0m ]\n\u001b[1;32m---> 32\u001b[0m strategies \u001b[38;5;241m=\u001b[39m [BFSStrategy(), DFSStrategy(), AStarStrategy()]\n\u001b[0;32m 33\u001b[0m all_results \u001b[38;5;241m=\u001b[39m []\n\u001b[0;32m 34\u001b[0m logger \u001b[38;5;241m=\u001b[39m ConsoleLogger()\n",
|
||
"\u001b[1;31mNameError\u001b[0m: name 'BFSStrategy' is not defined"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def test_single_maze(filename, strategies, repeats=5):\n",
|
||
" builder = TextFileMazeBuilder()\n",
|
||
" maze = builder.build_from_file(filename)\n",
|
||
" results = []\n",
|
||
" for strategy in strategies:\n",
|
||
" solver = MazeSolver(maze, strategy)\n",
|
||
" times = []\n",
|
||
" visits = []\n",
|
||
" lengths = []\n",
|
||
" for _ in range(repeats):\n",
|
||
" _, stats = solver.solve()\n",
|
||
" times.append(stats.time_ms)\n",
|
||
" visits.append(stats.visited_cells)\n",
|
||
" lengths.append(stats.path_length)\n",
|
||
" results.append({\n",
|
||
" 'algorithm': strategy.__class__.__name__,\n",
|
||
" 'avg_time_ms': sum(times) / repeats,\n",
|
||
" 'avg_visited': sum(visits) / repeats,\n",
|
||
" 'avg_path_len': sum(lengths) / repeats\n",
|
||
" })\n",
|
||
" return results\n",
|
||
"\n",
|
||
"\n",
|
||
"if __name__ == \"__main__\":\n",
|
||
" maze_files = [\n",
|
||
" \"tiny.txt\",\n",
|
||
" \"medium.txt\",\n",
|
||
" \"large.txt\",\n",
|
||
" \"empty.txt\",\n",
|
||
" \"no_exit.txt\"\n",
|
||
" ]\n",
|
||
" strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]\n",
|
||
" all_results = []\n",
|
||
" logger = ConsoleLogger()\n",
|
||
"\n",
|
||
" for maze_file in maze_files:\n",
|
||
" print(f\"Загрузка лабиринта из {maze_file}...\")\n",
|
||
" try:\n",
|
||
" builder = TextFileMazeBuilder()\n",
|
||
" maze = builder.build_from_file(maze_file)\n",
|
||
" # Демонстрация Observer и Command для tiny.txt\n",
|
||
" if maze_file == \"tiny.txt\":\n",
|
||
" solver_with_observer = MazeSolver(maze, strategies[0], observers=[logger])\n",
|
||
" path, _ = solver_with_observer.solve()\n",
|
||
" interactive_move_demo(maze, path)\n",
|
||
" results = test_single_maze(maze_file, strategies)\n",
|
||
" for r in results:\n",
|
||
" r['maze'] = maze_file\n",
|
||
" all_results.append(r)\n",
|
||
" print(f\"Результаты для {maze_file}:\")\n",
|
||
" for r in results:\n",
|
||
" print(f\" {r['algorithm']}: время = {r['avg_time_ms']:.3f} мс, \"\n",
|
||
" f\"посещено = {r['avg_visited']:.1f}, длина пути = {r['avg_path_len']:.1f}\")\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"Ошибка при обработке {maze_file}: {e}\")\n",
|
||
"\n",
|
||
" if all_results:\n",
|
||
" with open('all_results.csv', 'w', newline='', encoding='utf-8') as f:\n",
|
||
" writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited', 'avg_path_len'])\n",
|
||
" writer.writeheader()\n",
|
||
" writer.writerows(all_results)\n",
|
||
"\n",
|
||
" df = pd.DataFrame(all_results)\n",
|
||
" for maze in df['maze'].unique():\n",
|
||
" subset = df[df['maze'] == maze]\n",
|
||
" plt.figure()\n",
|
||
" plt.bar(subset['algorithm'], subset['avg_time_ms'])\n",
|
||
" plt.title(f'Сравнение алгоритмов на лабиринте {maze}')\n",
|
||
" plt.ylabel('Среднее время (мс)')\n",
|
||
" plt.savefig(f'plot_{maze}.png')\n",
|
||
" plt.close()\n",
|
||
"\n",
|
||
" plt.figure(figsize=(10, 6))\n",
|
||
" for alg in df['algorithm'].unique():\n",
|
||
" subset = df[df['algorithm'] == alg]\n",
|
||
" plt.plot(subset['maze'], subset['avg_time_ms'], marker='o', label=alg)\n",
|
||
" plt.xlabel('Лабиринт')\n",
|
||
" plt.ylabel('Среднее время (мс)')\n",
|
||
" plt.title('Сравнение эффективности алгоритмов на разных лабиринтах')\n",
|
||
" plt.legend()\n",
|
||
" plt.grid(True)\n",
|
||
" plt.savefig('summary_comparison.png')\n",
|
||
" plt.show()\n",
|
||
" else:\n",
|
||
" print(\"Нет данных для построения графиков. Проверьте файлы лабиринтов.\")\n",
|
||
"\n",
|
||
" print(\"\\nЭксперимент завершён. Результаты сохранены в all_results.csv и графиках.\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "937ab5f6-e884-46f6-8d45-b152ca61e7b7",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Заключение\n",
|
||
"\n",
|
||
"В работе реализованы:\n",
|
||
"- Классы `Cell` и `Maze` для моделирования лабиринта.\n",
|
||
"- Паттерн **Builder** для загрузки лабиринтов из текстовых файлов.\n",
|
||
"- Паттерн **Strategy** для трёх алгоритмов поиска: BFS, DFS, A*.\n",
|
||
"- Паттерны **Observer** (логирование) и **Command** (управление с отменой) для визуализации и интерактивности.\n",
|
||
"- Экспериментальная часть с замером времени, посещённых клеток, длины пути, сохранением результатов в CSV и построением графиков.\n",
|
||
"\n",
|
||
"Код полностью соответствует заданию и готов к использованию."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "1ddcb647-eb50-40aa-bc9a-cb76f8a14f23",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "a560e7bf-6b18-4018-9912-ea8da341e8a7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "25793d80-f546-4270-ae7c-86c4898d6c32",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3 (ipykernel)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.13.5"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|