From 012638e1d893b1b7228f839441124a0a3ad2d693 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 13:16:41 +0300 Subject: [PATCH 01/19] =?UTF-8?q?=D0=9A=D0=BB=D0=B5=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=B0=D0=B1=D0=B8=D1=80=D0=B8=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MininaVD/docs2/data2/modelsCell.ipynb | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 MininaVD/docs2/data2/modelsCell.ipynb diff --git a/MininaVD/docs2/data2/modelsCell.ipynb b/MininaVD/docs2/data2/modelsCell.ipynb new file mode 100644 index 0000000..b6b5547 --- /dev/null +++ b/MininaVD/docs2/data2/modelsCell.ipynb @@ -0,0 +1,58 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3a6bb811-80ad-4ef5-95b6-0b7cffc6545e", + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from typing import Optional\n", + "\n", + "@dataclass\n", + "class Cell:\n", + " \"\"\"Клетка лабиринта.\"\"\"\n", + " x: int\n", + " y: int\n", + " is_wall: bool = False\n", + " is_start: bool = False\n", + " is_exit: bool = False\n", + " weight: int = 1 # Для взвешенных лабиринтов (доп. задание)\n", + " \n", + " def is_passable(self) -> bool:\n", + " \"\"\"Проходима ли клетка.\"\"\"\n", + " return not self.is_wall\n", + " \n", + " def __hash__(self) -> int:\n", + " return hash((self.x, self.y))\n", + " \n", + " def __eq__(self, other) -> bool:\n", + " if not isinstance(other, Cell):\n", + " return False\n", + " return self.x == other.x and self.y == other.y" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 8ea42815684c2579915759e87ae1699f12562e42 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 13:20:09 +0300 Subject: [PATCH 02/19] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MininaVD/docs2/data2/models.ipynb | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 MininaVD/docs2/data2/models.ipynb diff --git a/MininaVD/docs2/data2/models.ipynb b/MininaVD/docs2/data2/models.ipynb new file mode 100644 index 0000000..b6b5547 --- /dev/null +++ b/MininaVD/docs2/data2/models.ipynb @@ -0,0 +1,58 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3a6bb811-80ad-4ef5-95b6-0b7cffc6545e", + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from typing import Optional\n", + "\n", + "@dataclass\n", + "class Cell:\n", + " \"\"\"Клетка лабиринта.\"\"\"\n", + " x: int\n", + " y: int\n", + " is_wall: bool = False\n", + " is_start: bool = False\n", + " is_exit: bool = False\n", + " weight: int = 1 # Для взвешенных лабиринтов (доп. задание)\n", + " \n", + " def is_passable(self) -> bool:\n", + " \"\"\"Проходима ли клетка.\"\"\"\n", + " return not self.is_wall\n", + " \n", + " def __hash__(self) -> int:\n", + " return hash((self.x, self.y))\n", + " \n", + " def __eq__(self, other) -> bool:\n", + " if not isinstance(other, Cell):\n", + " return False\n", + " return self.x == other.x and self.y == other.y" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3d10d8f6b8f639cc3e938d616c72c004d3527519 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 13:38:20 +0300 Subject: [PATCH 03/19] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20modelsMaze?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MininaVD/docs2/data2/models.ipynb | 58 ------------------- MininaVD/docs2/data2/modelsMaze.ipynb | 82 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 58 deletions(-) delete mode 100644 MininaVD/docs2/data2/models.ipynb create mode 100644 MininaVD/docs2/data2/modelsMaze.ipynb diff --git a/MininaVD/docs2/data2/models.ipynb b/MininaVD/docs2/data2/models.ipynb deleted file mode 100644 index b6b5547..0000000 --- a/MininaVD/docs2/data2/models.ipynb +++ /dev/null @@ -1,58 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3a6bb811-80ad-4ef5-95b6-0b7cffc6545e", - "metadata": {}, - "outputs": [], - "source": [ - "from dataclasses import dataclass\n", - "from typing import Optional\n", - "\n", - "@dataclass\n", - "class Cell:\n", - " \"\"\"Клетка лабиринта.\"\"\"\n", - " x: int\n", - " y: int\n", - " is_wall: bool = False\n", - " is_start: bool = False\n", - " is_exit: bool = False\n", - " weight: int = 1 # Для взвешенных лабиринтов (доп. задание)\n", - " \n", - " def is_passable(self) -> bool:\n", - " \"\"\"Проходима ли клетка.\"\"\"\n", - " return not self.is_wall\n", - " \n", - " def __hash__(self) -> int:\n", - " return hash((self.x, self.y))\n", - " \n", - " def __eq__(self, other) -> bool:\n", - " if not isinstance(other, Cell):\n", - " return False\n", - " return self.x == other.x and self.y == other.y" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/modelsMaze.ipynb b/MininaVD/docs2/data2/modelsMaze.ipynb new file mode 100644 index 0000000..f90c4a4 --- /dev/null +++ b/MininaVD/docs2/data2/modelsMaze.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "bc164689-22b6-4efd-bdd7-6a9913be9303", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Optional, Tuple\n", + "from modelsCell import Cell\n", + "\n", + "class Maze:\n", + " \"\"\"Модель лабиринта.\"\"\"\n", + " \n", + " def __init__(self, width: int = 0, height: int = 0):\n", + " self.width = width\n", + " self.height = height\n", + " self._cells: List[List[Optional[Cell]]] = [\n", + " [None for _ in range(width)] for _ in range(height)\n", + " ]\n", + " self.start_cell: Optional[Cell] = None\n", + " self.exit_cell: Optional[Cell] = None\n", + " \n", + " def set_cell(self, x: int, y: int, cell: Cell) -> None:\n", + " \"\"\"Установить клетку.\"\"\"\n", + " if 0 <= x < self.width and 0 <= y < self.height:\n", + " self._cells[y][x] = cell\n", + " \n", + " def get_cell(self, x: int, y: int) -> Optional[Cell]:\n", + " \"\"\"Получить клетку по координатам.\"\"\"\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: Cell) -> List[Cell]:\n", + " \"\"\"Получить проходимых соседей клетки (вверх, вниз, влево, вправо).\"\"\"\n", + " neighbors = []\n", + " directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо\n", + " \n", + " for dx, dy in directions:\n", + " nx, ny = cell.x + dx, cell.y + dy\n", + " neighbor = self.get_cell(nx, ny)\n", + " if neighbor and neighbor.is_passable():\n", + " neighbors.append(neighbor)\n", + " \n", + " return neighbors\n", + " \n", + " def get_all_cells(self) -> List[Cell]:\n", + " \"\"\"Получить все клетки лабиринта.\"\"\"\n", + " cells = []\n", + " for y in range(self.height):\n", + " for x in range(self.width):\n", + " cell = self.get_cell(x, y)\n", + " if cell:\n", + " cells.append(cell)\n", + " return cells" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 615c256eb0fd3bc770402657d5a3dde42f35c57c Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 13:50:26 +0300 Subject: [PATCH 04/19] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/buildersMaze_builder.ipynb | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 MininaVD/docs2/data2/buildersMaze_builder.ipynb diff --git a/MininaVD/docs2/data2/buildersMaze_builder.ipynb b/MininaVD/docs2/data2/buildersMaze_builder.ipynb new file mode 100644 index 0000000..e59e19c --- /dev/null +++ b/MininaVD/docs2/data2/buildersMaze_builder.ipynb @@ -0,0 +1,23 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "761fe720-5ebb-456c-96dc-fa3288bc6280", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "name": "" + }, + "language_info": { + "name": "" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d6286add4a699519aae41c897fe46ece4a158733 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:05:15 +0300 Subject: [PATCH 05/19] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=20=D0=BB=D0=B0=D0=B1=D0=B8=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data2/buildersText_maze_builder.ipynb | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 MininaVD/docs2/data2/buildersText_maze_builder.ipynb diff --git a/MininaVD/docs2/data2/buildersText_maze_builder.ipynb b/MininaVD/docs2/data2/buildersText_maze_builder.ipynb new file mode 100644 index 0000000..fb62e28 --- /dev/null +++ b/MininaVD/docs2/data2/buildersText_maze_builder.ipynb @@ -0,0 +1,23 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "6c5d97cf-bcbf-428a-b12c-1b86e9a02b96", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "name": "" + }, + "language_info": { + "name": "" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 055a73325b9b0abc64c8c9c7c0af513467855f32 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:23:43 +0300 Subject: [PATCH 06/19] =?UTF-8?q?=D0=A1=D1=82=D1=80=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategiesPathfinding_strategy.ipynb | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb diff --git a/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb b/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb new file mode 100644 index 0000000..6c641ac --- /dev/null +++ b/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb @@ -0,0 +1,67 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3f8f5923-00bf-4fc2-88e9-6a51d6183c5b", + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "from typing import List, Optional\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class PathFindingStrategy(ABC):\n", + " \"\"\"Интерфейс стратегии поиска пути (паттерн Strategy).\"\"\"\n", + " \n", + " @abstractmethod\n", + " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", + " \"\"\"\n", + " Найти путь от start до exit_cell.\n", + " Возвращает список клеток пути (включая start и exit) или пустой список.\n", + " \"\"\"\n", + " pass\n", + " \n", + " @property\n", + " @abstractmethod\n", + " def name(self) -> str:\n", + " \"\"\"Имя стратегии для отчётов.\"\"\"\n", + " pass\n", + " \n", + " def _reconstruct_path(self, came_from: dict, start: Cell, current: Cell) -> List[Cell]:\n", + " \"\"\"Восстановить путь из словаря предков.\"\"\"\n", + " path = []\n", + " while current != start:\n", + " path.append(current)\n", + " current = came_from.get(current)\n", + " if current is None:\n", + " return []\n", + " path.append(start)\n", + " path.reverse()\n", + " return path" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5647ac84f73370bc4fdd4da7ff4f585cadc9ae72 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:35:43 +0300 Subject: [PATCH 07/19] =?UTF-8?q?=D0=9F=D0=BE=D0=B8=D1=81=D0=BA=20=D0=B2?= =?UTF-8?q?=20=D1=88=D0=B8=D1=80=D0=B8=D0=BD=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/strategiesBfs_strategy.ipynb | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 MininaVD/docs2/data2/strategiesBfs_strategy.ipynb diff --git a/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb b/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb new file mode 100644 index 0000000..82be5f6 --- /dev/null +++ b/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "263fab53-6f76-4853-b167-a51b1198ddee", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import deque\n", + "from typing import List, Dict, Optional\n", + "from strategiesPathfinding_strategy import PathFindingStrategy\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class BFSStrategy(PathFindingStrategy):\n", + " \"\"\"Поиск в ширину - гарантирует кратчайший путь.\"\"\"\n", + " \n", + " @property\n", + " def name(self) -> str:\n", + " return \"BFS\"\n", + " \n", + " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", + " if start == exit_cell:\n", + " return [start]\n", + " \n", + " queue = deque([start])\n", + " came_from: Dict[Cell, Optional[Cell]] = {start: None}\n", + " visited_count = 0 # Для статистики\n", + " \n", + " while queue:\n", + " current = queue.popleft()\n", + " visited_count += 1\n", + " \n", + " if current == exit_cell:\n", + " # Сохраняем количество посещённых клеток для статистики\n", + " self._last_visited_count = visited_count\n", + " return self._reconstruct_path(came_from, start, current)\n", + " \n", + " for neighbor in maze.get_neighbors(current):\n", + " if neighbor not in came_from:\n", + " came_from[neighbor] = current\n", + " queue.append(neighbor)\n", + " \n", + " self._last_visited_count = visited_count\n", + " return []\n", + " \n", + " @property\n", + " def last_visited_count(self) -> int:\n", + " return getattr(self, '_last_visited_count', 0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From ccb1fca2a93e1e5dc4ca6fdfc39fd4ac438f4008 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:38:41 +0300 Subject: [PATCH 08/19] =?UTF-8?q?=D0=9F=D0=BE=D0=B8=D1=81=D0=BA=20=D0=B2?= =?UTF-8?q?=20=D0=B3=D0=BB=D1=83=D0=B1=D0=B8=D0=BD=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/strategiesDfs_strategy.ipynb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 MininaVD/docs2/data2/strategiesDfs_strategy.ipynb diff --git a/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb b/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb new file mode 100644 index 0000000..5ce0f5e --- /dev/null +++ b/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "4d8d9af1-63da-4f25-9140-a0b3c58cb96d", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Dict, Optional\n", + "from strategiesPathfinding_strategy import PathFindingStrategy\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class DFSStrategy(PathFindingStrategy):\n", + " \"\"\"Поиск в глубину - быстрый, но не обязательно кратчайший.\"\"\"\n", + " \n", + " @property\n", + " def name(self) -> str:\n", + " return \"DFS\"\n", + " \n", + " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", + " if start == exit_cell:\n", + " return [start]\n", + " \n", + " stack = [start]\n", + " came_from: Dict[Cell, Optional[Cell]] = {start: None}\n", + " visited_count = 0\n", + " \n", + " while stack:\n", + " current = stack.pop()\n", + " visited_count += 1\n", + " \n", + " if current == exit_cell:\n", + " self._last_visited_count = visited_count\n", + " return self._reconstruct_path(came_from, start, current)\n", + " \n", + " for neighbor in maze.get_neighbors(current):\n", + " if neighbor not in came_from:\n", + " came_from[neighbor] = current\n", + " stack.append(neighbor)\n", + " \n", + " self._last_visited_count = visited_count\n", + " return []\n", + " \n", + " @property\n", + " def last_visited_count(self) -> int:\n", + " return getattr(self, '_last_visited_count', 0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d8eebad0a5a73a2f263cd6c46e762707292b8552 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:48:02 +0300 Subject: [PATCH 09/19] =?UTF-8?q?=D0=9C=D0=B0=D0=BD=D1=85=D1=8D=D1=82?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D1=81=D0=BA=D0=B0=D1=8F=20=D1=8D=D0=B2=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data2/strategiesA_star_strategy.ipynb | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 MininaVD/docs2/data2/strategiesA_star_strategy.ipynb diff --git a/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb b/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb new file mode 100644 index 0000000..0f3b90b --- /dev/null +++ b/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f8e6c2ad-712c-44a0-8ebc-ed0d67234c05", + "metadata": {}, + "outputs": [], + "source": [ + "import heapq\n", + "from typing import List, Dict, Optional, Tuple\n", + "from strategiesPathfinding_strategy import PathFindingStrategy\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class AStarStrategy(PathFindingStrategy):\n", + " \"\"\"Алгоритм A* с манхэттенской эвристикой.\"\"\"\n", + " \n", + " @property\n", + " def name(self) -> str:\n", + " return \"A*\"\n", + " \n", + " def _heuristic(self, a: Cell, b: Cell) -> int:\n", + " \"\"\"Манхэттенское расстояние.\"\"\"\n", + " return abs(a.x - b.x) + abs(a.y - b.y)\n", + " \n", + " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", + " if start == exit_cell:\n", + " return [start]\n", + " \n", + " # Приоритетная очередь: (f_score, counter, cell)\n", + " open_set = [(0, 0, start)]\n", + " counter = 1\n", + " \n", + " came_from: Dict[Cell, Optional[Cell]] = {}\n", + " \n", + " g_score: Dict[Cell, float] = {start: 0}\n", + " f_score: Dict[Cell, float] = {start: self._heuristic(start, exit_cell)}\n", + " \n", + " visited_count = 0\n", + " \n", + " while open_set:\n", + " current_f, _, current = heapq.heappop(open_set)\n", + " visited_count += 1\n", + " \n", + " if current == exit_cell:\n", + " self._last_visited_count = visited_count\n", + " return self._reconstruct_path(came_from, start, current)\n", + " \n", + " for neighbor in maze.get_neighbors(current):\n", + " tentative_g_score = g_score.get(current, float('inf')) + 1\n", + " \n", + " if tentative_g_score < g_score.get(neighbor, float('inf')):\n", + " came_from[neighbor] = current\n", + " g_score[neighbor] = tentative_g_score\n", + " f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, exit_cell)\n", + " heapq.heappush(open_set, (f_score[neighbor], counter, neighbor))\n", + " counter += 1\n", + " \n", + " self._last_visited_count = visited_count\n", + " return []\n", + " \n", + " @property\n", + " def last_visited_count(self) -> int:\n", + " return getattr(self, '_last_visited_count', 0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From af4dae63f5425788fcc59e00667b7237f36cc188 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 14:56:59 +0300 Subject: [PATCH 10/19] solverMaze_solver --- MininaVD/docs2/data2/solverMaze_solver.ipynb | 129 +++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 MininaVD/docs2/data2/solverMaze_solver.ipynb diff --git a/MininaVD/docs2/data2/solverMaze_solver.ipynb b/MininaVD/docs2/data2/solverMaze_solver.ipynb new file mode 100644 index 0000000..4cc5c0d --- /dev/null +++ b/MininaVD/docs2/data2/solverMaze_solver.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1ef30a86-b41f-49eb-84c3-5d425614cbdd", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from typing import List, Optional\n", + "from dataclasses import dataclass, field\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "from strategiesPathfinding_strategy import PathFindingStrategy\n", + "from visualizationObserver import Observer\n", + "\n", + "@dataclass\n", + "class SearchStats:\n", + " \"\"\"Статистика поиска.\"\"\"\n", + " algorithm_name: str\n", + " time_ms: float\n", + " visited_cells: int\n", + " path_length: int\n", + " path_found: bool = True\n", + "\n", + "class MazeSolver:\n", + " \"\"\"\n", + " Оркестратор для решения лабиринта.\n", + " Использует паттерн Strategy для алгоритмов поиска.\n", + " Поддерживает Observer для уведомлений.\n", + " \"\"\"\n", + " \n", + " def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):\n", + " self.maze = maze\n", + " self._strategy = strategy\n", + " self._observers: List[Observer] = []\n", + " self._last_path: List[Cell] = []\n", + " self._last_stats: Optional[SearchStats] = None\n", + " \n", + " def set_strategy(self, strategy: PathFindingStrategy) -> None:\n", + " \"\"\"Динамическая смена стратегии.\"\"\"\n", + " self._strategy = strategy\n", + " self._notify(f\"Стратегия изменена на {strategy.name}\")\n", + " \n", + " def attach(self, observer: Observer) -> None:\n", + " \"\"\"Подписать наблюдателя.\"\"\"\n", + " self._observers.append(observer)\n", + " \n", + " def detach(self, observer: Observer) -> None:\n", + " \"\"\"Отписать наблюдателя.\"\"\"\n", + " if observer in self._observers:\n", + " self._observers.remove(observer)\n", + " \n", + " def _notify(self, event: str) -> None:\n", + " \"\"\"Уведомить всех наблюдателей.\"\"\"\n", + " for observer in self._observers:\n", + " observer.update(event)\n", + " \n", + " def solve(self) -> List[Cell]:\n", + " \"\"\"\n", + " Выполнить поиск пути с текущей стратегией.\n", + " Возвращает путь (список клеток).\n", + " \"\"\"\n", + " if self._strategy is None:\n", + " raise ValueError(\"Стратегия не установлена\")\n", + " \n", + " if not self.maze.start_cell or not self.maze.exit_cell:\n", + " raise ValueError(\"Лабиринт не имеет старта или выхода\")\n", + " \n", + " self._notify(f\"Начинаем поиск пути с использованием {self._strategy.name}...\")\n", + " \n", + " start_time = time.perf_counter()\n", + " path = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)\n", + " end_time = time.perf_counter()\n", + " \n", + " time_ms = (end_time - start_time) * 1000\n", + " \n", + " # Получаем количество посещённых клеток из стратегии\n", + " visited_cells = getattr(self._strategy, 'last_visited_count', 0)\n", + " \n", + " self._last_path = path\n", + " self._last_stats = SearchStats(\n", + " algorithm_name=self._strategy.name,\n", + " time_ms=time_ms,\n", + " visited_cells=visited_cells,\n", + " path_length=len(path),\n", + " path_found=len(path) > 0\n", + " )\n", + " \n", + " if path:\n", + " self._notify(f\"Путь найден! Длина: {len(path)}, время: {time_ms:.2f} мс, посещено: {visited_cells}\")\n", + " else:\n", + " self._notify(f\"Путь не найден! Время: {time_ms:.2f} мс, посещено: {visited_cells}\")\n", + " \n", + " return path\n", + " \n", + " @property\n", + " def last_path(self) -> List[Cell]:\n", + " return self._last_path\n", + " \n", + " @property\n", + " def last_stats(self) -> Optional[SearchStats]:\n", + " return self._last_stats" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 99daffa95910ad227a0e8a54052dbb856710ae45 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 16:41:28 +0300 Subject: [PATCH 11/19] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D1=80=D1=84?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=20=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/visualizationObserver.ipynb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 MininaVD/docs2/data2/visualizationObserver.ipynb diff --git a/MininaVD/docs2/data2/visualizationObserver.ipynb b/MininaVD/docs2/data2/visualizationObserver.ipynb new file mode 100644 index 0000000..41ff7bc --- /dev/null +++ b/MininaVD/docs2/data2/visualizationObserver.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "8998420f-2c73-4f8a-a102-cc403777f3e9", + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "\n", + "class Observer(ABC):\n", + " \"\"\"Интерфейс наблюдателя (паттерн Observer).\"\"\"\n", + " \n", + " @abstractmethod\n", + " def update(self, event: str) -> None:\n", + " \"\"\"Обработчик события.\"\"\"\n", + " pass" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From aaa3d93224ff74d2e0078f3c21dbe691acf28345 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 16:45:01 +0300 Subject: [PATCH 12/19] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=81=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BB=D0=B0=D0=B1=D0=B8?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data2/visualizationConsole_view.ipynb | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 MininaVD/docs2/data2/visualizationConsole_view.ipynb diff --git a/MininaVD/docs2/data2/visualizationConsole_view.ipynb b/MininaVD/docs2/data2/visualizationConsole_view.ipynb new file mode 100644 index 0000000..c3e817b --- /dev/null +++ b/MininaVD/docs2/data2/visualizationConsole_view.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "9d15670f-4ae1-48f5-801c-7a1a84cafba3", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from typing import List, Optional, Set\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "from visualizationObserver import Observer\n", + "\n", + "class ConsoleView(Observer):\n", + " \"\"\"Консольная визуализация лабиринта.\"\"\"\n", + " \n", + " # Символы для отображения\n", + " SYMBOLS = {\n", + " 'wall': '█',\n", + " 'path': '·',\n", + " 'start': 'S',\n", + " 'exit': 'E',\n", + " 'player': 'P',\n", + " 'solution': '★'\n", + " }\n", + " \n", + " def __init__(self, maze: Maze):\n", + " self.maze = maze\n", + " self.player_pos: Optional[Cell] = None\n", + " self.solution_path: Set[Cell] = set()\n", + " self.messages: List[str] = []\n", + " \n", + " def update(self, event: str) -> None:\n", + " \"\"\"Обработка событий от MazeSolver.\"\"\"\n", + " self.messages.append(f\"[СОБЫТИЕ] {event}\")\n", + " self.render()\n", + " \n", + " def set_solution_path(self, path: List[Cell]) -> None:\n", + " \"\"\"Установить найденный путь для отображения.\"\"\"\n", + " self.solution_path = set(path)\n", + " \n", + " def set_player_position(self, cell: Cell) -> None:\n", + " \"\"\"Установить позицию игрока.\"\"\"\n", + " self.player_pos = cell\n", + " \n", + " def render(self) -> None:\n", + " \"\"\"Отрисовать лабиринт в консоли.\"\"\"\n", + " # Очистка консоли (опционально)\n", + " # os.system('cls' if os.name == 'nt' else 'clear')\n", + " \n", + " print(\"\\n\" + \"=\" * (self.maze.width * 2 + 4))\n", + " print(f\"Лабиринт {self.maze.width}×{self.maze.height}\")\n", + " print(\"=\" * (self.maze.width * 2 + 4))\n", + " \n", + " for y in range(self.maze.height):\n", + " row = \"\"\n", + " for x in range(self.maze.width):\n", + " cell = self.maze.get_cell(x, y)\n", + " if not cell:\n", + " row += \" \"\n", + " continue\n", + " \n", + " if self.player_pos and cell == self.player_pos:\n", + " row += self.SYMBOLS['player'] + \" \"\n", + " elif cell.is_start:\n", + " row += self.SYMBOLS['start'] + \" \"\n", + " elif cell.is_exit:\n", + " row += self.SYMBOLS['exit'] + \" \"\n", + " elif cell in self.solution_path:\n", + " row += self.SYMBOLS['solution'] + \" \"\n", + " elif cell.is_wall:\n", + " row += self.SYMBOLS['wall'] * 2\n", + " else:\n", + " row += self.SYMBOLS['path'] * 2\n", + " print(row)\n", + " \n", + " print(\"-\" * (self.maze.width * 2 + 4))\n", + " \n", + " # Показать последние сообщения\n", + " if self.messages:\n", + " print(\"Последние события:\")\n", + " for msg in self.messages[-3:]:\n", + " print(f\" {msg}\")\n", + " \n", + " print()\n", + " \n", + " def clear_messages(self) -> None:\n", + " \"\"\"Очистить сообщения.\"\"\"\n", + " self.messages.clear()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3334a1de979cdaca2902bdbe8239af2361523205 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 16:47:41 +0300 Subject: [PATCH 13/19] Commands --- MininaVD/docs2/data2/commandsCommand.ipynb | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 MininaVD/docs2/data2/commandsCommand.ipynb diff --git a/MininaVD/docs2/data2/commandsCommand.ipynb b/MininaVD/docs2/data2/commandsCommand.ipynb new file mode 100644 index 0000000..f4f4961 --- /dev/null +++ b/MininaVD/docs2/data2/commandsCommand.ipynb @@ -0,0 +1,48 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "29468b18-8654-4914-a68e-76c1a7f01c49", + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "\n", + "class Command(ABC):\n", + " \"\"\"Интерфейс команды (паттерн Command).\"\"\"\n", + " \n", + " @abstractmethod\n", + " def execute(self) -> None:\n", + " \"\"\"Выполнить команду.\"\"\"\n", + " pass\n", + " \n", + " @abstractmethod\n", + " def undo(self) -> None:\n", + " \"\"\"Отменить команду.\"\"\"\n", + " pass" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From e3e6e176a471b86f550a839f7909671033758f2b Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 17:16:36 +0300 Subject: [PATCH 14/19] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MininaVD/docs2/data2/commandsPlayer.ipynb | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 MininaVD/docs2/data2/commandsPlayer.ipynb diff --git a/MininaVD/docs2/data2/commandsPlayer.ipynb b/MininaVD/docs2/data2/commandsPlayer.ipynb new file mode 100644 index 0000000..2329588 --- /dev/null +++ b/MininaVD/docs2/data2/commandsPlayer.ipynb @@ -0,0 +1,65 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c2a5ffd4-9644-453e-83de-bdac76215a37", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Optional\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class Player:\n", + " \"\"\"Игрок, перемещающийся по лабиринту.\"\"\"\n", + " \n", + " def __init__(self, maze: Maze, start_cell: Cell):\n", + " self.maze = maze\n", + " self.current_cell = start_cell\n", + " self._previous_cell: Optional[Cell] = None\n", + " \n", + " def move_to(self, cell: Cell) -> bool:\n", + " \"\"\"Переместить игрока в указанную клетку (если она проходима).\"\"\"\n", + " if cell and cell.is_passable():\n", + " self._previous_cell = self.current_cell\n", + " self.current_cell = cell\n", + " return True\n", + " return False\n", + " \n", + " def undo_move(self) -> bool:\n", + " \"\"\"Отменить последнее перемещение.\"\"\"\n", + " if self._previous_cell:\n", + " self.current_cell = self._previous_cell\n", + " self._previous_cell = None\n", + " return True\n", + " return False\n", + " \n", + " @property\n", + " def position(self) -> Cell:\n", + " return self.current_cell" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d677c5c794bff8e6d8dd6ffbc8b08cde55dc804a Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 17:17:41 +0300 Subject: [PATCH 15/19] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/commandsMove_command.ipynb | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 MininaVD/docs2/data2/commandsMove_command.ipynb diff --git a/MininaVD/docs2/data2/commandsMove_command.ipynb b/MininaVD/docs2/data2/commandsMove_command.ipynb new file mode 100644 index 0000000..8bcd6d7 --- /dev/null +++ b/MininaVD/docs2/data2/commandsMove_command.ipynb @@ -0,0 +1,84 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "492dd9b1-922e-4c95-9112-4b2df086eae6", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Optional\n", + "from commandsCommand import Command\n", + "from commandsPlayer import Player\n", + "from modelsMaze import Maze\n", + "from modelsCell import Cell\n", + "\n", + "class MoveCommand(Command):\n", + " \"\"\"Команда перемещения игрока.\"\"\"\n", + " \n", + " # Направления\n", + " DIRECTIONS = {\n", + " 'w': (0, -1), # вверх\n", + " 's': (0, 1), # вниз\n", + " 'a': (-1, 0), # влево\n", + " 'd': (1, 0), # вправо\n", + " }\n", + " \n", + " def __init__(self, player: Player, maze: Maze, direction: str):\n", + " self.player = player\n", + " self.maze = maze\n", + " self.direction = direction.lower()\n", + " self._target_cell: Optional[Cell] = None\n", + " self._executed = False\n", + " \n", + " def execute(self) -> bool:\n", + " \"\"\"Выполнить перемещение.\"\"\"\n", + " if self.direction not in self.DIRECTIONS:\n", + " return False\n", + " \n", + " dx, dy = self.DIRECTIONS[self.direction]\n", + " x = self.player.current_cell.x + dx\n", + " y = self.player.current_cell.y + dy\n", + " \n", + " self._target_cell = self.maze.get_cell(x, y)\n", + " \n", + " if self._target_cell and self._target_cell.is_passable():\n", + " self.player.move_to(self._target_cell)\n", + " self._executed = True\n", + " return True\n", + " \n", + " return False\n", + " \n", + " def undo(self) -> bool:\n", + " \"\"\"Отменить перемещение.\"\"\"\n", + " if self._executed:\n", + " success = self.player.undo_move()\n", + " if success:\n", + " self._executed = False\n", + " return True\n", + " return False" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 0d72312f50a0d1863cffac96f3937463939285ae Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 17:18:52 +0300 Subject: [PATCH 16/19] experimentsBenchmark --- .../docs2/data2/experimentsBenchmark.ipynb | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 MininaVD/docs2/data2/experimentsBenchmark.ipynb diff --git a/MininaVD/docs2/data2/experimentsBenchmark.ipynb b/MininaVD/docs2/data2/experimentsBenchmark.ipynb new file mode 100644 index 0000000..a07df9d --- /dev/null +++ b/MininaVD/docs2/data2/experimentsBenchmark.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c7d4d33a-ead6-4906-b307-e357ba0995e2", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "import time\n", + "from typing import List, Dict, Any\n", + "from modelsMaze import Maze\n", + "from strategiesBfs_strategy import BFSStrategy\n", + "from strategiesDfs_strategy import DFSStrategy\n", + "from strategiesA_star_strategy import AStarStrategy\n", + "from solverMaze_solver import MazeSolver\n", + "\n", + "class Benchmark:\n", + " \"\"\"Экспериментальное сравнение алгоритмов.\"\"\"\n", + " \n", + " def __init__(self):\n", + " self.strategies = [\n", + " BFSStrategy(),\n", + " DFSStrategy(),\n", + " AStarStrategy(),\n", + " ]\n", + " self.results: List[Dict[str, Any]] = []\n", + " \n", + " def run_on_maze(self, maze: Maze, maze_name: str, iterations: int = 5) -> List[Dict]:\n", + " \"\"\"Запустить все стратегии на одном лабиринте.\"\"\"\n", + " results = []\n", + " \n", + " for strategy in self.strategies:\n", + " solver = MazeSolver(maze, strategy)\n", + " \n", + " times = []\n", + " visited_counts = []\n", + " path_lengths = []\n", + " path_found = False\n", + " \n", + " for i in range(iterations):\n", + " # Сбрасываем состояние стратегии для честного замера\n", + " # (кэш посещённых клеток не должен влиять)\n", + " start_time = time.perf_counter()\n", + " path = strategy.find_path(maze, maze.start_cell, maze.exit_cell)\n", + " end_time = time.perf_counter()\n", + " \n", + " times.append((end_time - start_time) * 1000)\n", + " visited_counts.append(getattr(strategy, 'last_visited_count', 0))\n", + " path_lengths.append(len(path))\n", + " path_found = len(path) > 0\n", + " \n", + " result = {\n", + " 'maze': maze_name,\n", + " 'algorithm': strategy.name,\n", + " 'avg_time_ms': sum(times) / len(times),\n", + " 'min_time_ms': min(times),\n", + " 'max_time_ms': max(times),\n", + " 'avg_visited': sum(visited_counts) / len(visited_counts),\n", + " 'avg_path_length': sum(path_lengths) / len(path_lengths),\n", + " 'path_found': path_found,\n", + " 'iterations': iterations\n", + " }\n", + " results.append(result)\n", + " self.results.append(result)\n", + " \n", + " return results\n", + " \n", + " def save_to_csv(self, filename: str = \"benchmark_results.csv\") -> None:\n", + " \"\"\"Сохранить результаты в CSV.\"\"\"\n", + " if not self.results:\n", + " print(\"Нет результатов для сохранения\")\n", + " return\n", + " \n", + " fieldnames = ['maze', 'algorithm', 'avg_time_ms', 'min_time_ms', \n", + " 'max_time_ms', 'avg_visited', 'avg_path_length', \n", + " 'path_found', 'iterations']\n", + " \n", + " with open(filename, 'w', newline='', encoding='utf-8') as f:\n", + " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", + " writer.writeheader()\n", + " writer.writerows(self.results)\n", + " \n", + " print(f\"Результаты сохранены в {filename}\")\n", + " \n", + " def print_summary(self) -> None:\n", + " \"\"\"Вывести сводку результатов.\"\"\"\n", + " print(\"РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ\")\n", + " \n", + " current_maze = None\n", + " for r in self.results:\n", + " if r['maze'] != current_maze:\n", + " current_maze = r['maze']\n", + " print(f\"\\n--- Лабиринт: {current_maze} ---\")\n", + " \n", + " status = \" НАЙДЕН\" if r['path_found'] else \" НЕ НАЙДЕН\"\n", + " print(f\" {r['algorithm']:6} | Время: {r['avg_time_ms']:8.2f} мс | \"\n", + " f\"Посещено: {r['avg_visited']:8.1f} | \"\n", + " f\"Путь: {r['avg_path_length']:6.1f} | {status}\")\n", + " \n", + " " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5207bb8682964bce3a37f0674385ba4d7831011a Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 17:20:04 +0300 Subject: [PATCH 17/19] main --- MininaVD/docs2/data2/main.ipynb | 256 ++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 MininaVD/docs2/data2/main.ipynb diff --git a/MininaVD/docs2/data2/main.ipynb b/MininaVD/docs2/data2/main.ipynb new file mode 100644 index 0000000..1d1c73f --- /dev/null +++ b/MininaVD/docs2/data2/main.ipynb @@ -0,0 +1,256 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "id": "ac02445d-6e74-4f6e-bb96-2c28ccd82d83", + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'modelsMaze'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m 5\u001b[39m sys.path.insert(\u001b[32m0\u001b[39m, os.getcwd())\n\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# Импорты с вашими именами файлов\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mmodelsMaze\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Maze, Cell\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mbuildersText_maze_builder\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m TextFieldMazeBuilder\n\u001b[32m 10\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mstrategiesBFS_strategy\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m BFSStrategy\n", + "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'modelsMaze'" + ] + } + ], + "source": [ + "import sys\n", + "import os\n", + "\n", + "# Добавляем текущую папку в путь\n", + "sys.path.insert(0, os.getcwd())\n", + "\n", + "# Импорты с вашими именами файлов\n", + "from modelsMaze import Maze, Cell\n", + "from buildersText_maze_builder import TextFieldMazeBuilder\n", + "from strategiesBFS_strategy import BFSStrategy\n", + "from strategiesDFS_strategy import DFSStrategy\n", + "from strategiesA_star_strategy import AStarStrategy\n", + "from solverMaze_solver import MazeSolver\n", + "from visualizationConsole_view import ConsoleView\n", + "from commandsPlayer import Player\n", + "from commandsMove_command import MoveCommand\n", + "from experimentsBenchmark import Benchmark\n", + "\n", + "def create_test_mazes():\n", + " \"\"\"Создать тестовые лабиринты в папке mazes/.\"\"\"\n", + " mazes_dir = \"mazes\"\n", + " os.makedirs(mazes_dir, exist_ok=True)\n", + " \n", + " # Маленький лабиринт 10×10\n", + " small = [\n", + " \"##########\",\n", + " \"#S #\",\n", + " \"# ##### #\",\n", + " \"# # # #\",\n", + " \"# # # # #\",\n", + " \"# # # #\",\n", + " \"##### # #\",\n", + " \"# #\",\n", + " \"# E#\",\n", + " \"##########\",\n", + " ]\n", + " \n", + " # Пустой лабиринт\n", + " empty = [\"S\" + \" \" * 48 + \"E\"] + [\" \" * 50 for _ in range(48)]\n", + " \n", + " # Лабиринт без выхода\n", + " no_exit = [\n", + " \"##########\",\n", + " \"#S #\",\n", + " \"# ##### #\",\n", + " \"# # # #\",\n", + " \"# # # # #\",\n", + " \"# # # #\",\n", + " \"##### # #\",\n", + " \"# #\",\n", + " \"##########\",\n", + " \"##########\",\n", + " ]\n", + " \n", + " mazes = {\n", + " \"small.txt\": small,\n", + " \"empty.txt\": empty,\n", + " \"no_exit.txt\": no_exit,\n", + " }\n", + " \n", + " for name, content in mazes.items():\n", + " path = os.path.join(mazes_dir, name)\n", + " with open(path, 'w', encoding='utf-8') as f:\n", + " f.write('\\n'.join(content))\n", + " print(f\"Создан тестовый лабиринт: {path}\")\n", + " \n", + " print()\n", + "\n", + "def demo_builder_and_strategy():\n", + " \"\"\"Демонстрация паттернов Builder и Strategy.\"\"\"\n", + " print(\"\\n\" + \"=\" * 60)\n", + " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНОВ BUILDER И STRATEGY\")\n", + " print(\"=\" * 60)\n", + " \n", + " builder = TextFieldMazeBuilder()\n", + " maze = builder.build_from_file(\"mazes/small.txt\")\n", + " \n", + " strategies = [\n", + " BFSStrategy(),\n", + " DFSStrategy(),\n", + " AStarStrategy(),\n", + " ]\n", + " \n", + " for strategy in strategies:\n", + " print(f\"\\n--- Используем стратегию: {strategy.name} ---\")\n", + " solver = MazeSolver(maze, strategy)\n", + " path = solver.solve()\n", + " \n", + " if path:\n", + " print(f\" Путь найден! Длина: {len(path)}\")\n", + " print(f\" Время: {solver.last_stats.time_ms:.2f} мс\")\n", + " print(f\" Посещено клеток: {solver.last_stats.visited_cells}\")\n", + " else:\n", + " print(\" Путь не найден!\")\n", + " \n", + " return maze\n", + "\n", + "def demo_observer(maze: Maze):\n", + " \"\"\"Демонстрация паттерна Observer.\"\"\"\n", + " print(\"\\n\" + \"=\" * 60)\n", + " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНА OBSERVER\")\n", + " print(\"=\" * 60)\n", + " \n", + " view = ConsoleView(maze)\n", + " solver = MazeSolver(maze, BFSStrategy())\n", + " solver.attach(view)\n", + " \n", + " print(\"Запускаем поиск с наблюдателем...\")\n", + " path = solver.solve()\n", + " \n", + " view.set_solution_path(path)\n", + " view.render()\n", + " \n", + " return view\n", + "\n", + "def demo_command(maze: Maze, view: ConsoleView):\n", + " \"\"\"Демонстрация паттерна Command.\"\"\"\n", + " print(\"\\n\" + \"=\" * 60)\n", + " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНА COMMAND\")\n", + " print(\"=\" * 60)\n", + " \n", + " player = Player(maze, maze.start_cell)\n", + " view.set_player_position(player.position)\n", + " \n", + " print(\"Управление игроком:\")\n", + " print(\" W/A/S/D - движение, Z - отмена, Q - выход\")\n", + " \n", + " history = []\n", + " \n", + " while True:\n", + " view.render()\n", + " \n", + " cmd = input(\"Ваш ход: \").strip().lower()\n", + " \n", + " if cmd == 'q':\n", + " break\n", + " elif cmd == 'z':\n", + " if history:\n", + " last_cmd = history.pop()\n", + " last_cmd.undo()\n", + " view.set_player_position(player.position)\n", + " print(\"Последний ход отменён\")\n", + " else:\n", + " print(\"Нечего отменять\")\n", + " elif cmd in MoveCommand.DIRECTIONS:\n", + " move_cmd = MoveCommand(player, maze, cmd)\n", + " if move_cmd.execute():\n", + " history.append(move_cmd)\n", + " view.set_player_position(player.position)\n", + " \n", + " if player.position == maze.exit_cell:\n", + " print(\"\\n🎉 ПОБЕДА! ВЫ НАШЛИ ВЫХОД! 🎉\")\n", + " view.render()\n", + " break\n", + " else:\n", + " print(\"Туда нельзя пройти\")\n", + " else:\n", + " print(\"Неизвестная команда\")\n", + " \n", + " print(\"Игра завершена\")\n", + "\n", + "def run_experiments():\n", + " \"\"\"Запуск экспериментального сравнения.\"\"\"\n", + " print(\"\\n\" + \"=\" * 60)\n", + " print(\"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ\")\n", + " print(\"=\" * 60)\n", + " \n", + " builder = TextFieldMazeBuilder()\n", + " benchmark = Benchmark()\n", + " \n", + " maze_files = [\"small.txt\", \"empty.txt\", \"no_exit.txt\"]\n", + " \n", + " for maze_file in maze_files:\n", + " try:\n", + " maze = builder.build_from_file(f\"mazes/{maze_file}\")\n", + " print(f\"\\nТестируем: {maze_file} ({maze.width}×{maze.height})\")\n", + " benchmark.run_on_maze(maze, maze_file, iterations=5)\n", + " except FileNotFoundError:\n", + " print(f\"Файл {maze_file} не найден\")\n", + " except ValueError as e:\n", + " print(f\"Ошибка: {e}\")\n", + " \n", + " benchmark.print_summary()\n", + " benchmark.save_to_csv()\n", + "\n", + "def main():\n", + " \"\"\"Главная функция.\"\"\"\n", + " print(\"=\" * 60)\n", + " print(\"ПРОГРАММА ПОИСКА ВЫХОДА ИЗ ЛАБИРИНТА\")\n", + " print(\"Паттерны: Builder, Strategy, Observer, Command\")\n", + " print(\"=\" * 60)\n", + " \n", + " create_test_mazes()\n", + " maze = demo_builder_and_strategy()\n", + " view = demo_observer(maze)\n", + " demo_command(maze, view)\n", + " run_experiments()\n", + " \n", + " print(\"\\nПрограмма завершена!\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0972138-e9f0-4612-a87c-741d9d0bea13", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d8ab579a4660ecee4a21674ec98fe06e8eaf37e0 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Sun, 17 May 2026 17:56:08 +0300 Subject: [PATCH 18/19] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=BC=20=D0=B2=20=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D0=B5=20py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs2/data2/buildersMaze_builder.ipynb | 23 -- MininaVD/docs2/data2/buildersMaze_builder.py | 10 + .../data2/buildersText_maze_builder.ipynb | 23 -- .../docs2/data2/buildersText_maze_builder.py | 60 ++++ MininaVD/docs2/data2/commandsCommand.ipynb | 48 ---- MininaVD/docs2/data2/commandsCommand.py | 21 ++ .../docs2/data2/commandsMove_command.ipynb | 84 ------ MininaVD/docs2/data2/commandsMove_command.py | 57 ++++ MininaVD/docs2/data2/commandsPlayer.ipynb | 65 ----- MininaVD/docs2/data2/commandsPlayer.py | 38 +++ .../docs2/data2/experimentsBenchmark.ipynb | 127 --------- MininaVD/docs2/data2/experimentsBenchmark.py | 100 +++++++ MininaVD/docs2/data2/main.ipynb | 256 ------------------ MininaVD/docs2/data2/main.py | 215 +++++++++++++++ MininaVD/docs2/data2/modelsCell.ipynb | 58 ---- MininaVD/docs2/data2/modelsCell.py | 31 +++ MininaVD/docs2/data2/modelsMaze.ipynb | 82 ------ MininaVD/docs2/data2/modelsMaze.py | 55 ++++ MininaVD/docs2/data2/solverMaze_solver.ipynb | 129 --------- MininaVD/docs2/data2/solverMaze_solver.py | 102 +++++++ .../data2/strategiesA_star_strategy.ipynb | 90 ------ .../docs2/data2/strategiesA_star_strategy.py | 63 +++++ .../docs2/data2/strategiesBfs_strategy.ipynb | 75 ----- .../docs2/data2/strategiesBfs_strategy.py | 48 ++++ .../docs2/data2/strategiesDfs_strategy.ipynb | 73 ----- .../docs2/data2/strategiesDfs_strategy.py | 46 ++++ .../strategiesPathfinding_strategy.ipynb | 67 ----- .../data2/strategiesPathfinding_strategy.py | 40 +++ .../data2/visualizationConsole_view.ipynb | 116 -------- .../docs2/data2/visualizationConsole_view.py | 89 ++++++ .../docs2/data2/visualizationObserver.ipynb | 43 --- MininaVD/docs2/data2/visualizationObserver.py | 16 ++ 32 files changed, 991 insertions(+), 1359 deletions(-) delete mode 100644 MininaVD/docs2/data2/buildersMaze_builder.ipynb create mode 100644 MininaVD/docs2/data2/buildersMaze_builder.py delete mode 100644 MininaVD/docs2/data2/buildersText_maze_builder.ipynb create mode 100644 MininaVD/docs2/data2/buildersText_maze_builder.py delete mode 100644 MininaVD/docs2/data2/commandsCommand.ipynb create mode 100644 MininaVD/docs2/data2/commandsCommand.py delete mode 100644 MininaVD/docs2/data2/commandsMove_command.ipynb create mode 100644 MininaVD/docs2/data2/commandsMove_command.py delete mode 100644 MininaVD/docs2/data2/commandsPlayer.ipynb create mode 100644 MininaVD/docs2/data2/commandsPlayer.py delete mode 100644 MininaVD/docs2/data2/experimentsBenchmark.ipynb create mode 100644 MininaVD/docs2/data2/experimentsBenchmark.py delete mode 100644 MininaVD/docs2/data2/main.ipynb create mode 100644 MininaVD/docs2/data2/main.py delete mode 100644 MininaVD/docs2/data2/modelsCell.ipynb create mode 100644 MininaVD/docs2/data2/modelsCell.py delete mode 100644 MininaVD/docs2/data2/modelsMaze.ipynb create mode 100644 MininaVD/docs2/data2/modelsMaze.py delete mode 100644 MininaVD/docs2/data2/solverMaze_solver.ipynb create mode 100644 MininaVD/docs2/data2/solverMaze_solver.py delete mode 100644 MininaVD/docs2/data2/strategiesA_star_strategy.ipynb create mode 100644 MininaVD/docs2/data2/strategiesA_star_strategy.py delete mode 100644 MininaVD/docs2/data2/strategiesBfs_strategy.ipynb create mode 100644 MininaVD/docs2/data2/strategiesBfs_strategy.py delete mode 100644 MininaVD/docs2/data2/strategiesDfs_strategy.ipynb create mode 100644 MininaVD/docs2/data2/strategiesDfs_strategy.py delete mode 100644 MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb create mode 100644 MininaVD/docs2/data2/strategiesPathfinding_strategy.py delete mode 100644 MininaVD/docs2/data2/visualizationConsole_view.ipynb create mode 100644 MininaVD/docs2/data2/visualizationConsole_view.py delete mode 100644 MininaVD/docs2/data2/visualizationObserver.ipynb create mode 100644 MininaVD/docs2/data2/visualizationObserver.py diff --git a/MininaVD/docs2/data2/buildersMaze_builder.ipynb b/MininaVD/docs2/data2/buildersMaze_builder.ipynb deleted file mode 100644 index e59e19c..0000000 --- a/MininaVD/docs2/data2/buildersMaze_builder.ipynb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "761fe720-5ebb-456c-96dc-fa3288bc6280", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "", - "name": "" - }, - "language_info": { - "name": "" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/buildersMaze_builder.py b/MininaVD/docs2/data2/buildersMaze_builder.py new file mode 100644 index 0000000..e7c588a --- /dev/null +++ b/MininaVD/docs2/data2/buildersMaze_builder.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod +from modelsMaze import Maze + +class MazeBuilder(ABC): + """Интерфейс строителя лабиринта (паттерн Builder).""" + + @abstractmethod + def build_from_file(self, filename: str) -> Maze: + """Загрузить лабиринт из файла.""" + pass diff --git a/MininaVD/docs2/data2/buildersText_maze_builder.ipynb b/MininaVD/docs2/data2/buildersText_maze_builder.ipynb deleted file mode 100644 index fb62e28..0000000 --- a/MininaVD/docs2/data2/buildersText_maze_builder.ipynb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "6c5d97cf-bcbf-428a-b12c-1b86e9a02b96", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "", - "name": "" - }, - "language_info": { - "name": "" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/buildersText_maze_builder.py b/MininaVD/docs2/data2/buildersText_maze_builder.py new file mode 100644 index 0000000..7780891 --- /dev/null +++ b/MininaVD/docs2/data2/buildersText_maze_builder.py @@ -0,0 +1,60 @@ +from typing import List, Tuple +from buildersMaze_builder import MazeBuilder +from modelsMaze import Maze +from modelsCell import Cell + +class TextFieldMazeBuilder(MazeBuilder): + """Загрузчик лабиринта из текстового файла.""" + + # Символы в файле + WALL_CHAR = '#' + PASS_CHAR = ' ' + START_CHAR = 'S' + EXIT_CHAR = 'E' + + def build_from_file(self, filename: str) -> Maze: + """Загрузить лабиринт из текстового файла.""" + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + + if not lines: + raise ValueError("Файл пуст") + + height = len(lines) + width = max(len(line) for line in lines) + + maze = Maze(width, height) + start_cell = None + exit_cell = None + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if x >= width: + continue + + is_wall = (ch == self.WALL_CHAR) + is_start = (ch == self.START_CHAR) + is_exit = (ch == self.EXIT_CHAR) + + # Пробел или буква - проходимая клетка + if ch == self.PASS_CHAR or is_start or is_exit: + is_wall = False + + cell = Cell(x=x, y=y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) + maze.set_cell(x, y, cell) + + if is_start: + start_cell = cell + if is_exit: + exit_cell = cell + + # Валидация + if start_cell is None: + raise ValueError("В лабиринте нет стартовой клетки (S)") + if exit_cell is None: + raise ValueError("В лабиринте нет выходной клетки (E)") + + maze.start_cell = start_cell + maze.exit_cell = exit_cell + + return maze diff --git a/MininaVD/docs2/data2/commandsCommand.ipynb b/MininaVD/docs2/data2/commandsCommand.ipynb deleted file mode 100644 index f4f4961..0000000 --- a/MininaVD/docs2/data2/commandsCommand.ipynb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "29468b18-8654-4914-a68e-76c1a7f01c49", - "metadata": {}, - "outputs": [], - "source": [ - "from abc import ABC, abstractmethod\n", - "\n", - "class Command(ABC):\n", - " \"\"\"Интерфейс команды (паттерн Command).\"\"\"\n", - " \n", - " @abstractmethod\n", - " def execute(self) -> None:\n", - " \"\"\"Выполнить команду.\"\"\"\n", - " pass\n", - " \n", - " @abstractmethod\n", - " def undo(self) -> None:\n", - " \"\"\"Отменить команду.\"\"\"\n", - " pass" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/commandsCommand.py b/MininaVD/docs2/data2/commandsCommand.py new file mode 100644 index 0000000..c7d1fac --- /dev/null +++ b/MininaVD/docs2/data2/commandsCommand.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from abc import ABC, abstractmethod + +class Command(ABC): + """Интерфейс команды (паттерн Command).""" + + @abstractmethod + def execute(self) -> None: + """Выполнить команду.""" + pass + + @abstractmethod + def undo(self) -> None: + """Отменить команду.""" + pass + diff --git a/MininaVD/docs2/data2/commandsMove_command.ipynb b/MininaVD/docs2/data2/commandsMove_command.ipynb deleted file mode 100644 index 8bcd6d7..0000000 --- a/MininaVD/docs2/data2/commandsMove_command.ipynb +++ /dev/null @@ -1,84 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "492dd9b1-922e-4c95-9112-4b2df086eae6", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Optional\n", - "from commandsCommand import Command\n", - "from commandsPlayer import Player\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class MoveCommand(Command):\n", - " \"\"\"Команда перемещения игрока.\"\"\"\n", - " \n", - " # Направления\n", - " DIRECTIONS = {\n", - " 'w': (0, -1), # вверх\n", - " 's': (0, 1), # вниз\n", - " 'a': (-1, 0), # влево\n", - " 'd': (1, 0), # вправо\n", - " }\n", - " \n", - " def __init__(self, player: Player, maze: Maze, direction: str):\n", - " self.player = player\n", - " self.maze = maze\n", - " self.direction = direction.lower()\n", - " self._target_cell: Optional[Cell] = None\n", - " self._executed = False\n", - " \n", - " def execute(self) -> bool:\n", - " \"\"\"Выполнить перемещение.\"\"\"\n", - " if self.direction not in self.DIRECTIONS:\n", - " return False\n", - " \n", - " dx, dy = self.DIRECTIONS[self.direction]\n", - " x = self.player.current_cell.x + dx\n", - " y = self.player.current_cell.y + dy\n", - " \n", - " self._target_cell = self.maze.get_cell(x, y)\n", - " \n", - " if self._target_cell and self._target_cell.is_passable():\n", - " self.player.move_to(self._target_cell)\n", - " self._executed = True\n", - " return True\n", - " \n", - " return False\n", - " \n", - " def undo(self) -> bool:\n", - " \"\"\"Отменить перемещение.\"\"\"\n", - " if self._executed:\n", - " success = self.player.undo_move()\n", - " if success:\n", - " self._executed = False\n", - " return True\n", - " return False" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/commandsMove_command.py b/MininaVD/docs2/data2/commandsMove_command.py new file mode 100644 index 0000000..b916987 --- /dev/null +++ b/MininaVD/docs2/data2/commandsMove_command.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from typing import Optional +from commandsCommand import Command +from commandsPlayer import Player +from modelsMaze import Maze +from modelsCell import Cell + +class MoveCommand(Command): + """Команда перемещения игрока.""" + + # Направления + DIRECTIONS = { + 'w': (0, -1), # вверх + 's': (0, 1), # вниз + 'a': (-1, 0), # влево + 'd': (1, 0), # вправо + } + + def __init__(self, player: Player, maze: Maze, direction: str): + self.player = player + self.maze = maze + self.direction = direction.lower() + self._target_cell: Optional[Cell] = None + self._executed = False + + def execute(self) -> bool: + """Выполнить перемещение.""" + if self.direction not in self.DIRECTIONS: + return False + + dx, dy = self.DIRECTIONS[self.direction] + x = self.player.current_cell.x + dx + y = self.player.current_cell.y + dy + + self._target_cell = self.maze.get_cell(x, y) + + if self._target_cell and self._target_cell.is_passable(): + self.player.move_to(self._target_cell) + self._executed = True + return True + + return False + + def undo(self) -> bool: + """Отменить перемещение.""" + if self._executed: + success = self.player.undo_move() + if success: + self._executed = False + return True + return False + diff --git a/MininaVD/docs2/data2/commandsPlayer.ipynb b/MininaVD/docs2/data2/commandsPlayer.ipynb deleted file mode 100644 index 2329588..0000000 --- a/MininaVD/docs2/data2/commandsPlayer.ipynb +++ /dev/null @@ -1,65 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c2a5ffd4-9644-453e-83de-bdac76215a37", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Optional\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class Player:\n", - " \"\"\"Игрок, перемещающийся по лабиринту.\"\"\"\n", - " \n", - " def __init__(self, maze: Maze, start_cell: Cell):\n", - " self.maze = maze\n", - " self.current_cell = start_cell\n", - " self._previous_cell: Optional[Cell] = None\n", - " \n", - " def move_to(self, cell: Cell) -> bool:\n", - " \"\"\"Переместить игрока в указанную клетку (если она проходима).\"\"\"\n", - " if cell and cell.is_passable():\n", - " self._previous_cell = self.current_cell\n", - " self.current_cell = cell\n", - " return True\n", - " return False\n", - " \n", - " def undo_move(self) -> bool:\n", - " \"\"\"Отменить последнее перемещение.\"\"\"\n", - " if self._previous_cell:\n", - " self.current_cell = self._previous_cell\n", - " self._previous_cell = None\n", - " return True\n", - " return False\n", - " \n", - " @property\n", - " def position(self) -> Cell:\n", - " return self.current_cell" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/commandsPlayer.py b/MininaVD/docs2/data2/commandsPlayer.py new file mode 100644 index 0000000..f78a9a7 --- /dev/null +++ b/MininaVD/docs2/data2/commandsPlayer.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from typing import Optional +from modelsMaze import Maze +from modelsCell import Cell + +class Player: + """Игрок, перемещающийся по лабиринту.""" + + def __init__(self, maze: Maze, start_cell: Cell): + self.maze = maze + self.current_cell = start_cell + self._previous_cell: Optional[Cell] = None + + def move_to(self, cell: Cell) -> bool: + """Переместить игрока в указанную клетку (если она проходима).""" + if cell and cell.is_passable(): + self._previous_cell = self.current_cell + self.current_cell = cell + return True + return False + + def undo_move(self) -> bool: + """Отменить последнее перемещение.""" + if self._previous_cell: + self.current_cell = self._previous_cell + self._previous_cell = None + return True + return False + + @property + def position(self) -> Cell: + return self.current_cell + diff --git a/MininaVD/docs2/data2/experimentsBenchmark.ipynb b/MininaVD/docs2/data2/experimentsBenchmark.ipynb deleted file mode 100644 index a07df9d..0000000 --- a/MininaVD/docs2/data2/experimentsBenchmark.ipynb +++ /dev/null @@ -1,127 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c7d4d33a-ead6-4906-b307-e357ba0995e2", - "metadata": {}, - "outputs": [], - "source": [ - "import csv\n", - "import time\n", - "from typing import List, Dict, Any\n", - "from modelsMaze import Maze\n", - "from strategiesBfs_strategy import BFSStrategy\n", - "from strategiesDfs_strategy import DFSStrategy\n", - "from strategiesA_star_strategy import AStarStrategy\n", - "from solverMaze_solver import MazeSolver\n", - "\n", - "class Benchmark:\n", - " \"\"\"Экспериментальное сравнение алгоритмов.\"\"\"\n", - " \n", - " def __init__(self):\n", - " self.strategies = [\n", - " BFSStrategy(),\n", - " DFSStrategy(),\n", - " AStarStrategy(),\n", - " ]\n", - " self.results: List[Dict[str, Any]] = []\n", - " \n", - " def run_on_maze(self, maze: Maze, maze_name: str, iterations: int = 5) -> List[Dict]:\n", - " \"\"\"Запустить все стратегии на одном лабиринте.\"\"\"\n", - " results = []\n", - " \n", - " for strategy in self.strategies:\n", - " solver = MazeSolver(maze, strategy)\n", - " \n", - " times = []\n", - " visited_counts = []\n", - " path_lengths = []\n", - " path_found = False\n", - " \n", - " for i in range(iterations):\n", - " # Сбрасываем состояние стратегии для честного замера\n", - " # (кэш посещённых клеток не должен влиять)\n", - " start_time = time.perf_counter()\n", - " path = strategy.find_path(maze, maze.start_cell, maze.exit_cell)\n", - " end_time = time.perf_counter()\n", - " \n", - " times.append((end_time - start_time) * 1000)\n", - " visited_counts.append(getattr(strategy, 'last_visited_count', 0))\n", - " path_lengths.append(len(path))\n", - " path_found = len(path) > 0\n", - " \n", - " result = {\n", - " 'maze': maze_name,\n", - " 'algorithm': strategy.name,\n", - " 'avg_time_ms': sum(times) / len(times),\n", - " 'min_time_ms': min(times),\n", - " 'max_time_ms': max(times),\n", - " 'avg_visited': sum(visited_counts) / len(visited_counts),\n", - " 'avg_path_length': sum(path_lengths) / len(path_lengths),\n", - " 'path_found': path_found,\n", - " 'iterations': iterations\n", - " }\n", - " results.append(result)\n", - " self.results.append(result)\n", - " \n", - " return results\n", - " \n", - " def save_to_csv(self, filename: str = \"benchmark_results.csv\") -> None:\n", - " \"\"\"Сохранить результаты в CSV.\"\"\"\n", - " if not self.results:\n", - " print(\"Нет результатов для сохранения\")\n", - " return\n", - " \n", - " fieldnames = ['maze', 'algorithm', 'avg_time_ms', 'min_time_ms', \n", - " 'max_time_ms', 'avg_visited', 'avg_path_length', \n", - " 'path_found', 'iterations']\n", - " \n", - " with open(filename, 'w', newline='', encoding='utf-8') as f:\n", - " writer = csv.DictWriter(f, fieldnames=fieldnames)\n", - " writer.writeheader()\n", - " writer.writerows(self.results)\n", - " \n", - " print(f\"Результаты сохранены в {filename}\")\n", - " \n", - " def print_summary(self) -> None:\n", - " \"\"\"Вывести сводку результатов.\"\"\"\n", - " print(\"РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ\")\n", - " \n", - " current_maze = None\n", - " for r in self.results:\n", - " if r['maze'] != current_maze:\n", - " current_maze = r['maze']\n", - " print(f\"\\n--- Лабиринт: {current_maze} ---\")\n", - " \n", - " status = \" НАЙДЕН\" if r['path_found'] else \" НЕ НАЙДЕН\"\n", - " print(f\" {r['algorithm']:6} | Время: {r['avg_time_ms']:8.2f} мс | \"\n", - " f\"Посещено: {r['avg_visited']:8.1f} | \"\n", - " f\"Путь: {r['avg_path_length']:6.1f} | {status}\")\n", - " \n", - " " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/experimentsBenchmark.py b/MininaVD/docs2/data2/experimentsBenchmark.py new file mode 100644 index 0000000..7f88121 --- /dev/null +++ b/MininaVD/docs2/data2/experimentsBenchmark.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +import csv +import time +from typing import List, Dict, Any +from modelsMaze import Maze +from strategiesBfs_strategy import BFSStrategy +from strategiesDfs_strategy import DFSStrategy +from strategiesA_star_strategy import AStarStrategy +from solverMaze_solver import MazeSolver + +class Benchmark: + """Экспериментальное сравнение алгоритмов.""" + + def __init__(self): + self.strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + ] + self.results: List[Dict[str, Any]] = [] + + def run_on_maze(self, maze: Maze, maze_name: str, iterations: int = 5) -> List[Dict]: + """Запустить все стратегии на одном лабиринте.""" + results = [] + + for strategy in self.strategies: + solver = MazeSolver(maze, strategy) + + times = [] + visited_counts = [] + path_lengths = [] + path_found = False + + for i in range(iterations): + # Сбрасываем состояние стратегии для честного замера + # (кэш посещённых клеток не должен влиять) + start_time = time.perf_counter() + path = strategy.find_path(maze, maze.start_cell, maze.exit_cell) + end_time = time.perf_counter() + + times.append((end_time - start_time) * 1000) + visited_counts.append(getattr(strategy, 'last_visited_count', 0)) + path_lengths.append(len(path)) + path_found = len(path) > 0 + + result = { + 'maze': maze_name, + 'algorithm': strategy.name, + 'avg_time_ms': sum(times) / len(times), + 'min_time_ms': min(times), + 'max_time_ms': max(times), + 'avg_visited': sum(visited_counts) / len(visited_counts), + 'avg_path_length': sum(path_lengths) / len(path_lengths), + 'path_found': path_found, + 'iterations': iterations + } + results.append(result) + self.results.append(result) + + return results + + def save_to_csv(self, filename: str = "benchmark_results.csv") -> None: + """Сохранить результаты в CSV.""" + if not self.results: + print("Нет результатов для сохранения") + return + + fieldnames = ['maze', 'algorithm', 'avg_time_ms', 'min_time_ms', + 'max_time_ms', 'avg_visited', 'avg_path_length', + 'path_found', 'iterations'] + + with open(filename, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(self.results) + + print(f"Результаты сохранены в {filename}") + + def print_summary(self) -> None: + """Вывести сводку результатов.""" + print("РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ") + + current_maze = None + for r in self.results: + if r['maze'] != current_maze: + current_maze = r['maze'] + print(f"\n--- Лабиринт: {current_maze} ---") + + status = " НАЙДЕН" if r['path_found'] else " НЕ НАЙДЕН" + print(f" {r['algorithm']:6} | Время: {r['avg_time_ms']:8.2f} мс | " + f"Посещено: {r['avg_visited']:8.1f} | " + f"Путь: {r['avg_path_length']:6.1f} | {status}") + + + diff --git a/MininaVD/docs2/data2/main.ipynb b/MininaVD/docs2/data2/main.ipynb deleted file mode 100644 index 1d1c73f..0000000 --- a/MininaVD/docs2/data2/main.ipynb +++ /dev/null @@ -1,256 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "id": "ac02445d-6e74-4f6e-bb96-2c28ccd82d83", - "metadata": {}, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'modelsMaze'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m 5\u001b[39m sys.path.insert(\u001b[32m0\u001b[39m, os.getcwd())\n\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# Импорты с вашими именами файлов\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mmodelsMaze\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m Maze, Cell\n\u001b[32m 9\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mbuildersText_maze_builder\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m TextFieldMazeBuilder\n\u001b[32m 10\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mstrategiesBFS_strategy\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m BFSStrategy\n", - "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'modelsMaze'" - ] - } - ], - "source": [ - "import sys\n", - "import os\n", - "\n", - "# Добавляем текущую папку в путь\n", - "sys.path.insert(0, os.getcwd())\n", - "\n", - "# Импорты с вашими именами файлов\n", - "from modelsMaze import Maze, Cell\n", - "from buildersText_maze_builder import TextFieldMazeBuilder\n", - "from strategiesBFS_strategy import BFSStrategy\n", - "from strategiesDFS_strategy import DFSStrategy\n", - "from strategiesA_star_strategy import AStarStrategy\n", - "from solverMaze_solver import MazeSolver\n", - "from visualizationConsole_view import ConsoleView\n", - "from commandsPlayer import Player\n", - "from commandsMove_command import MoveCommand\n", - "from experimentsBenchmark import Benchmark\n", - "\n", - "def create_test_mazes():\n", - " \"\"\"Создать тестовые лабиринты в папке mazes/.\"\"\"\n", - " mazes_dir = \"mazes\"\n", - " os.makedirs(mazes_dir, exist_ok=True)\n", - " \n", - " # Маленький лабиринт 10×10\n", - " small = [\n", - " \"##########\",\n", - " \"#S #\",\n", - " \"# ##### #\",\n", - " \"# # # #\",\n", - " \"# # # # #\",\n", - " \"# # # #\",\n", - " \"##### # #\",\n", - " \"# #\",\n", - " \"# E#\",\n", - " \"##########\",\n", - " ]\n", - " \n", - " # Пустой лабиринт\n", - " empty = [\"S\" + \" \" * 48 + \"E\"] + [\" \" * 50 for _ in range(48)]\n", - " \n", - " # Лабиринт без выхода\n", - " no_exit = [\n", - " \"##########\",\n", - " \"#S #\",\n", - " \"# ##### #\",\n", - " \"# # # #\",\n", - " \"# # # # #\",\n", - " \"# # # #\",\n", - " \"##### # #\",\n", - " \"# #\",\n", - " \"##########\",\n", - " \"##########\",\n", - " ]\n", - " \n", - " mazes = {\n", - " \"small.txt\": small,\n", - " \"empty.txt\": empty,\n", - " \"no_exit.txt\": no_exit,\n", - " }\n", - " \n", - " for name, content in mazes.items():\n", - " path = os.path.join(mazes_dir, name)\n", - " with open(path, 'w', encoding='utf-8') as f:\n", - " f.write('\\n'.join(content))\n", - " print(f\"Создан тестовый лабиринт: {path}\")\n", - " \n", - " print()\n", - "\n", - "def demo_builder_and_strategy():\n", - " \"\"\"Демонстрация паттернов Builder и Strategy.\"\"\"\n", - " print(\"\\n\" + \"=\" * 60)\n", - " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНОВ BUILDER И STRATEGY\")\n", - " print(\"=\" * 60)\n", - " \n", - " builder = TextFieldMazeBuilder()\n", - " maze = builder.build_from_file(\"mazes/small.txt\")\n", - " \n", - " strategies = [\n", - " BFSStrategy(),\n", - " DFSStrategy(),\n", - " AStarStrategy(),\n", - " ]\n", - " \n", - " for strategy in strategies:\n", - " print(f\"\\n--- Используем стратегию: {strategy.name} ---\")\n", - " solver = MazeSolver(maze, strategy)\n", - " path = solver.solve()\n", - " \n", - " if path:\n", - " print(f\" Путь найден! Длина: {len(path)}\")\n", - " print(f\" Время: {solver.last_stats.time_ms:.2f} мс\")\n", - " print(f\" Посещено клеток: {solver.last_stats.visited_cells}\")\n", - " else:\n", - " print(\" Путь не найден!\")\n", - " \n", - " return maze\n", - "\n", - "def demo_observer(maze: Maze):\n", - " \"\"\"Демонстрация паттерна Observer.\"\"\"\n", - " print(\"\\n\" + \"=\" * 60)\n", - " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНА OBSERVER\")\n", - " print(\"=\" * 60)\n", - " \n", - " view = ConsoleView(maze)\n", - " solver = MazeSolver(maze, BFSStrategy())\n", - " solver.attach(view)\n", - " \n", - " print(\"Запускаем поиск с наблюдателем...\")\n", - " path = solver.solve()\n", - " \n", - " view.set_solution_path(path)\n", - " view.render()\n", - " \n", - " return view\n", - "\n", - "def demo_command(maze: Maze, view: ConsoleView):\n", - " \"\"\"Демонстрация паттерна Command.\"\"\"\n", - " print(\"\\n\" + \"=\" * 60)\n", - " print(\"ДЕМОНСТРАЦИЯ ПАТТЕРНА COMMAND\")\n", - " print(\"=\" * 60)\n", - " \n", - " player = Player(maze, maze.start_cell)\n", - " view.set_player_position(player.position)\n", - " \n", - " print(\"Управление игроком:\")\n", - " print(\" W/A/S/D - движение, Z - отмена, Q - выход\")\n", - " \n", - " history = []\n", - " \n", - " while True:\n", - " view.render()\n", - " \n", - " cmd = input(\"Ваш ход: \").strip().lower()\n", - " \n", - " if cmd == 'q':\n", - " break\n", - " elif cmd == 'z':\n", - " if history:\n", - " last_cmd = history.pop()\n", - " last_cmd.undo()\n", - " view.set_player_position(player.position)\n", - " print(\"Последний ход отменён\")\n", - " else:\n", - " print(\"Нечего отменять\")\n", - " elif cmd in MoveCommand.DIRECTIONS:\n", - " move_cmd = MoveCommand(player, maze, cmd)\n", - " if move_cmd.execute():\n", - " history.append(move_cmd)\n", - " view.set_player_position(player.position)\n", - " \n", - " if player.position == maze.exit_cell:\n", - " print(\"\\n🎉 ПОБЕДА! ВЫ НАШЛИ ВЫХОД! 🎉\")\n", - " view.render()\n", - " break\n", - " else:\n", - " print(\"Туда нельзя пройти\")\n", - " else:\n", - " print(\"Неизвестная команда\")\n", - " \n", - " print(\"Игра завершена\")\n", - "\n", - "def run_experiments():\n", - " \"\"\"Запуск экспериментального сравнения.\"\"\"\n", - " print(\"\\n\" + \"=\" * 60)\n", - " print(\"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ\")\n", - " print(\"=\" * 60)\n", - " \n", - " builder = TextFieldMazeBuilder()\n", - " benchmark = Benchmark()\n", - " \n", - " maze_files = [\"small.txt\", \"empty.txt\", \"no_exit.txt\"]\n", - " \n", - " for maze_file in maze_files:\n", - " try:\n", - " maze = builder.build_from_file(f\"mazes/{maze_file}\")\n", - " print(f\"\\nТестируем: {maze_file} ({maze.width}×{maze.height})\")\n", - " benchmark.run_on_maze(maze, maze_file, iterations=5)\n", - " except FileNotFoundError:\n", - " print(f\"Файл {maze_file} не найден\")\n", - " except ValueError as e:\n", - " print(f\"Ошибка: {e}\")\n", - " \n", - " benchmark.print_summary()\n", - " benchmark.save_to_csv()\n", - "\n", - "def main():\n", - " \"\"\"Главная функция.\"\"\"\n", - " print(\"=\" * 60)\n", - " print(\"ПРОГРАММА ПОИСКА ВЫХОДА ИЗ ЛАБИРИНТА\")\n", - " print(\"Паттерны: Builder, Strategy, Observer, Command\")\n", - " print(\"=\" * 60)\n", - " \n", - " create_test_mazes()\n", - " maze = demo_builder_and_strategy()\n", - " view = demo_observer(maze)\n", - " demo_command(maze, view)\n", - " run_experiments()\n", - " \n", - " print(\"\\nПрограмма завершена!\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " main()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0972138-e9f0-4612-a87c-741d9d0bea13", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/main.py b/MininaVD/docs2/data2/main.py new file mode 100644 index 0000000..cb8f41b --- /dev/null +++ b/MininaVD/docs2/data2/main.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[7]: + + +import sys +import os + +# Добавляем текущую папку в путь +sys.path.insert(0, os.getcwd()) + +# Импорты с вашими именами файлов +from modelsMaze import Maze, Cell +from buildersText_maze_builder import TextFieldMazeBuilder +from strategiesBfs_strategy import BFSStrategy +from strategiesDfs_strategy import DFSStrategy +from strategiesA_star_strategy import AStarStrategy +from solverMaze_solver import MazeSolver +from visualizationConsole_view import ConsoleView +from commandsPlayer import Player +from commandsMove_command import MoveCommand +from experimentsBenchmark import Benchmark + +def create_test_mazes(): + """Создать тестовые лабиринты в папке mazes/.""" + mazes_dir = "mazes" + os.makedirs(mazes_dir, exist_ok=True) + + # Маленький лабиринт 10×10 + small = [ + "##########", + "#S #", + "# ##### #", + "# # # #", + "# # # # #", + "# # # #", + "##### # #", + "# #", + "# E#", + "##########", + ] + + # Пустой лабиринт + empty = ["S" + " " * 48 + "E"] + [" " * 50 for _ in range(48)] + + # Лабиринт без выхода + no_exit = [ + "##########", + "#S #", + "# ##### #", + "# # # #", + "# # # # #", + "# # # #", + "##### # #", + "# #", + "##########", + "##########", + ] + + mazes = { + "small.txt": small, + "empty.txt": empty, + "no_exit.txt": no_exit, + } + + for name, content in mazes.items(): + path = os.path.join(mazes_dir, name) + with open(path, 'w', encoding='utf-8') as f: + f.write('\n'.join(content)) + print(f"Создан тестовый лабиринт: {path}") + + print() + +def demo_builder_and_strategy(): + """Демонстрация паттернов Builder и Strategy.""" + print("\n" + "=" * 60) + print("ДЕМОНСТРАЦИЯ ПАТТЕРНОВ BUILDER И STRATEGY") + print("=" * 60) + + builder = TextFieldMazeBuilder() + maze = builder.build_from_file("mazes/small.txt") + + strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + ] + + for strategy in strategies: + print(f"\n--- Используем стратегию: {strategy.name} ---") + solver = MazeSolver(maze, strategy) + path = solver.solve() + + if path: + print(f" Путь найден! Длина: {len(path)}") + print(f" Время: {solver.last_stats.time_ms:.2f} мс") + print(f" Посещено клеток: {solver.last_stats.visited_cells}") + else: + print(" Путь не найден!") + + return maze + +def demo_observer(maze: Maze): + """Демонстрация паттерна Observer.""" + print("\n" + "=" * 60) + print("ДЕМОНСТРАЦИЯ ПАТТЕРНА OBSERVER") + print("=" * 60) + + view = ConsoleView(maze) + solver = MazeSolver(maze, BFSStrategy()) + solver.attach(view) + + print("Запускаем поиск с наблюдателем...") + path = solver.solve() + + view.set_solution_path(path) + view.render() + + return view + +def demo_command(maze: Maze, view: ConsoleView): + """Демонстрация паттерна Command.""" + print("\n" + "=" * 60) + print("ДЕМОНСТРАЦИЯ ПАТТЕРНА COMMAND") + print("=" * 60) + + player = Player(maze, maze.start_cell) + view.set_player_position(player.position) + + print("Управление игроком:") + print(" W/A/S/D - движение, Z - отмена, Q - выход") + + history = [] + + while True: + view.render() + + cmd = input("Ваш ход: ").strip().lower() + + if cmd == 'q': + break + elif cmd == 'z': + if history: + last_cmd = history.pop() + last_cmd.undo() + view.set_player_position(player.position) + print("Последний ход отменён") + else: + print("Нечего отменять") + elif cmd in MoveCommand.DIRECTIONS: + move_cmd = MoveCommand(player, maze, cmd) + if move_cmd.execute(): + history.append(move_cmd) + view.set_player_position(player.position) + + if player.position == maze.exit_cell: + print("\n🎉 ПОБЕДА! ВЫ НАШЛИ ВЫХОД! 🎉") + view.render() + break + else: + print("Туда нельзя пройти") + else: + print("Неизвестная команда") + + print("Игра завершена") + +def run_experiments(): + """Запуск экспериментального сравнения.""" + print("\n" + "=" * 60) + print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ") + print("=" * 60) + + builder = TextFieldMazeBuilder() + benchmark = Benchmark() + + maze_files = ["small.txt", "empty.txt", "no_exit.txt"] + + for maze_file in maze_files: + try: + maze = builder.build_from_file(f"mazes/{maze_file}") + print(f"\nТестируем: {maze_file} ({maze.width}×{maze.height})") + benchmark.run_on_maze(maze, maze_file, iterations=5) + except FileNotFoundError: + print(f"Файл {maze_file} не найден") + except ValueError as e: + print(f"Ошибка: {e}") + + benchmark.print_summary() + benchmark.save_to_csv() + +def main(): + """Главная функция.""" + print("=" * 60) + print("ПРОГРАММА ПОИСКА ВЫХОДА ИЗ ЛАБИРИНТА") + print("Паттерны: Builder, Strategy, Observer, Command") + print("=" * 60) + + create_test_mazes() + maze = demo_builder_and_strategy() + view = demo_observer(maze) + demo_command(maze, view) + run_experiments() + + print("\nПрограмма завершена!") + +if __name__ == "__main__": + main() + + +# In[ ]: + + + + diff --git a/MininaVD/docs2/data2/modelsCell.ipynb b/MininaVD/docs2/data2/modelsCell.ipynb deleted file mode 100644 index b6b5547..0000000 --- a/MininaVD/docs2/data2/modelsCell.ipynb +++ /dev/null @@ -1,58 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3a6bb811-80ad-4ef5-95b6-0b7cffc6545e", - "metadata": {}, - "outputs": [], - "source": [ - "from dataclasses import dataclass\n", - "from typing import Optional\n", - "\n", - "@dataclass\n", - "class Cell:\n", - " \"\"\"Клетка лабиринта.\"\"\"\n", - " x: int\n", - " y: int\n", - " is_wall: bool = False\n", - " is_start: bool = False\n", - " is_exit: bool = False\n", - " weight: int = 1 # Для взвешенных лабиринтов (доп. задание)\n", - " \n", - " def is_passable(self) -> bool:\n", - " \"\"\"Проходима ли клетка.\"\"\"\n", - " return not self.is_wall\n", - " \n", - " def __hash__(self) -> int:\n", - " return hash((self.x, self.y))\n", - " \n", - " def __eq__(self, other) -> bool:\n", - " if not isinstance(other, Cell):\n", - " return False\n", - " return self.x == other.x and self.y == other.y" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/modelsCell.py b/MininaVD/docs2/data2/modelsCell.py new file mode 100644 index 0000000..e8c6b64 --- /dev/null +++ b/MininaVD/docs2/data2/modelsCell.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from dataclasses import dataclass +from typing import Optional + +@dataclass +class Cell: + """Клетка лабиринта.""" + x: int + y: int + is_wall: bool = False + is_start: bool = False + is_exit: bool = False + weight: int = 1 # Для взвешенных лабиринтов (доп. задание) + + def is_passable(self) -> bool: + """Проходима ли клетка.""" + return not self.is_wall + + def __hash__(self) -> int: + return hash((self.x, self.y)) + + def __eq__(self, other) -> bool: + if not isinstance(other, Cell): + return False + return self.x == other.x and self.y == other.y + diff --git a/MininaVD/docs2/data2/modelsMaze.ipynb b/MininaVD/docs2/data2/modelsMaze.ipynb deleted file mode 100644 index f90c4a4..0000000 --- a/MininaVD/docs2/data2/modelsMaze.ipynb +++ /dev/null @@ -1,82 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "bc164689-22b6-4efd-bdd7-6a9913be9303", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List, Optional, Tuple\n", - "from modelsCell import Cell\n", - "\n", - "class Maze:\n", - " \"\"\"Модель лабиринта.\"\"\"\n", - " \n", - " def __init__(self, width: int = 0, height: int = 0):\n", - " self.width = width\n", - " self.height = height\n", - " self._cells: List[List[Optional[Cell]]] = [\n", - " [None for _ in range(width)] for _ in range(height)\n", - " ]\n", - " self.start_cell: Optional[Cell] = None\n", - " self.exit_cell: Optional[Cell] = None\n", - " \n", - " def set_cell(self, x: int, y: int, cell: Cell) -> None:\n", - " \"\"\"Установить клетку.\"\"\"\n", - " if 0 <= x < self.width and 0 <= y < self.height:\n", - " self._cells[y][x] = cell\n", - " \n", - " def get_cell(self, x: int, y: int) -> Optional[Cell]:\n", - " \"\"\"Получить клетку по координатам.\"\"\"\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: Cell) -> List[Cell]:\n", - " \"\"\"Получить проходимых соседей клетки (вверх, вниз, влево, вправо).\"\"\"\n", - " neighbors = []\n", - " directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо\n", - " \n", - " for dx, dy in directions:\n", - " nx, ny = cell.x + dx, cell.y + dy\n", - " neighbor = self.get_cell(nx, ny)\n", - " if neighbor and neighbor.is_passable():\n", - " neighbors.append(neighbor)\n", - " \n", - " return neighbors\n", - " \n", - " def get_all_cells(self) -> List[Cell]:\n", - " \"\"\"Получить все клетки лабиринта.\"\"\"\n", - " cells = []\n", - " for y in range(self.height):\n", - " for x in range(self.width):\n", - " cell = self.get_cell(x, y)\n", - " if cell:\n", - " cells.append(cell)\n", - " return cells" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/modelsMaze.py b/MininaVD/docs2/data2/modelsMaze.py new file mode 100644 index 0000000..43ae196 --- /dev/null +++ b/MininaVD/docs2/data2/modelsMaze.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from typing import List, Optional, Tuple +from modelsCell import Cell + +class Maze: + """Модель лабиринта.""" + + def __init__(self, width: int = 0, height: int = 0): + self.width = width + self.height = height + self._cells: List[List[Optional[Cell]]] = [ + [None for _ in range(width)] for _ in range(height) + ] + self.start_cell: Optional[Cell] = None + self.exit_cell: Optional[Cell] = None + + def set_cell(self, x: int, y: int, cell: Cell) -> None: + """Установить клетку.""" + if 0 <= x < self.width and 0 <= y < self.height: + self._cells[y][x] = cell + + def get_cell(self, x: int, y: int) -> Optional[Cell]: + """Получить клетку по координатам.""" + if 0 <= x < self.width and 0 <= y < self.height: + return self._cells[y][x] + return None + + def get_neighbors(self, cell: Cell) -> List[Cell]: + """Получить проходимых соседей клетки (вверх, вниз, влево, вправо).""" + neighbors = [] + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо + + for dx, dy in directions: + 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 + + def get_all_cells(self) -> List[Cell]: + """Получить все клетки лабиринта.""" + cells = [] + for y in range(self.height): + for x in range(self.width): + cell = self.get_cell(x, y) + if cell: + cells.append(cell) + return cells + diff --git a/MininaVD/docs2/data2/solverMaze_solver.ipynb b/MininaVD/docs2/data2/solverMaze_solver.ipynb deleted file mode 100644 index 4cc5c0d..0000000 --- a/MininaVD/docs2/data2/solverMaze_solver.ipynb +++ /dev/null @@ -1,129 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "1ef30a86-b41f-49eb-84c3-5d425614cbdd", - "metadata": {}, - "outputs": [], - "source": [ - "import time\n", - "from typing import List, Optional\n", - "from dataclasses import dataclass, field\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "from strategiesPathfinding_strategy import PathFindingStrategy\n", - "from visualizationObserver import Observer\n", - "\n", - "@dataclass\n", - "class SearchStats:\n", - " \"\"\"Статистика поиска.\"\"\"\n", - " algorithm_name: str\n", - " time_ms: float\n", - " visited_cells: int\n", - " path_length: int\n", - " path_found: bool = True\n", - "\n", - "class MazeSolver:\n", - " \"\"\"\n", - " Оркестратор для решения лабиринта.\n", - " Использует паттерн Strategy для алгоритмов поиска.\n", - " Поддерживает Observer для уведомлений.\n", - " \"\"\"\n", - " \n", - " def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):\n", - " self.maze = maze\n", - " self._strategy = strategy\n", - " self._observers: List[Observer] = []\n", - " self._last_path: List[Cell] = []\n", - " self._last_stats: Optional[SearchStats] = None\n", - " \n", - " def set_strategy(self, strategy: PathFindingStrategy) -> None:\n", - " \"\"\"Динамическая смена стратегии.\"\"\"\n", - " self._strategy = strategy\n", - " self._notify(f\"Стратегия изменена на {strategy.name}\")\n", - " \n", - " def attach(self, observer: Observer) -> None:\n", - " \"\"\"Подписать наблюдателя.\"\"\"\n", - " self._observers.append(observer)\n", - " \n", - " def detach(self, observer: Observer) -> None:\n", - " \"\"\"Отписать наблюдателя.\"\"\"\n", - " if observer in self._observers:\n", - " self._observers.remove(observer)\n", - " \n", - " def _notify(self, event: str) -> None:\n", - " \"\"\"Уведомить всех наблюдателей.\"\"\"\n", - " for observer in self._observers:\n", - " observer.update(event)\n", - " \n", - " def solve(self) -> List[Cell]:\n", - " \"\"\"\n", - " Выполнить поиск пути с текущей стратегией.\n", - " Возвращает путь (список клеток).\n", - " \"\"\"\n", - " if self._strategy is None:\n", - " raise ValueError(\"Стратегия не установлена\")\n", - " \n", - " if not self.maze.start_cell or not self.maze.exit_cell:\n", - " raise ValueError(\"Лабиринт не имеет старта или выхода\")\n", - " \n", - " self._notify(f\"Начинаем поиск пути с использованием {self._strategy.name}...\")\n", - " \n", - " start_time = time.perf_counter()\n", - " path = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)\n", - " end_time = time.perf_counter()\n", - " \n", - " time_ms = (end_time - start_time) * 1000\n", - " \n", - " # Получаем количество посещённых клеток из стратегии\n", - " visited_cells = getattr(self._strategy, 'last_visited_count', 0)\n", - " \n", - " self._last_path = path\n", - " self._last_stats = SearchStats(\n", - " algorithm_name=self._strategy.name,\n", - " time_ms=time_ms,\n", - " visited_cells=visited_cells,\n", - " path_length=len(path),\n", - " path_found=len(path) > 0\n", - " )\n", - " \n", - " if path:\n", - " self._notify(f\"Путь найден! Длина: {len(path)}, время: {time_ms:.2f} мс, посещено: {visited_cells}\")\n", - " else:\n", - " self._notify(f\"Путь не найден! Время: {time_ms:.2f} мс, посещено: {visited_cells}\")\n", - " \n", - " return path\n", - " \n", - " @property\n", - " def last_path(self) -> List[Cell]:\n", - " return self._last_path\n", - " \n", - " @property\n", - " def last_stats(self) -> Optional[SearchStats]:\n", - " return self._last_stats" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/solverMaze_solver.py b/MininaVD/docs2/data2/solverMaze_solver.py new file mode 100644 index 0000000..b95e911 --- /dev/null +++ b/MininaVD/docs2/data2/solverMaze_solver.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +import time +from typing import List, Optional +from dataclasses import dataclass, field +from modelsMaze import Maze +from modelsCell import Cell +from strategiesPathfinding_strategy import PathFindingStrategy +from visualizationObserver import Observer + +@dataclass +class SearchStats: + """Статистика поиска.""" + algorithm_name: str + time_ms: float + visited_cells: int + path_length: int + path_found: bool = True + +class MazeSolver: + """ + Оркестратор для решения лабиринта. + Использует паттерн Strategy для алгоритмов поиска. + Поддерживает Observer для уведомлений. + """ + + def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None): + self.maze = maze + self._strategy = strategy + self._observers: List[Observer] = [] + self._last_path: List[Cell] = [] + self._last_stats: Optional[SearchStats] = None + + def set_strategy(self, strategy: PathFindingStrategy) -> None: + """Динамическая смена стратегии.""" + self._strategy = strategy + self._notify(f"Стратегия изменена на {strategy.name}") + + def attach(self, observer: Observer) -> None: + """Подписать наблюдателя.""" + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + """Отписать наблюдателя.""" + if observer in self._observers: + self._observers.remove(observer) + + def _notify(self, event: str) -> None: + """Уведомить всех наблюдателей.""" + for observer in self._observers: + observer.update(event) + + def solve(self) -> List[Cell]: + """ + Выполнить поиск пути с текущей стратегией. + Возвращает путь (список клеток). + """ + if self._strategy is None: + raise ValueError("Стратегия не установлена") + + if not self.maze.start_cell or not self.maze.exit_cell: + raise ValueError("Лабиринт не имеет старта или выхода") + + self._notify(f"Начинаем поиск пути с использованием {self._strategy.name}...") + + start_time = time.perf_counter() + path = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell) + end_time = time.perf_counter() + + time_ms = (end_time - start_time) * 1000 + + # Получаем количество посещённых клеток из стратегии + visited_cells = getattr(self._strategy, 'last_visited_count', 0) + + self._last_path = path + self._last_stats = SearchStats( + algorithm_name=self._strategy.name, + time_ms=time_ms, + visited_cells=visited_cells, + path_length=len(path), + path_found=len(path) > 0 + ) + + if path: + self._notify(f"Путь найден! Длина: {len(path)}, время: {time_ms:.2f} мс, посещено: {visited_cells}") + else: + self._notify(f"Путь не найден! Время: {time_ms:.2f} мс, посещено: {visited_cells}") + + return path + + @property + def last_path(self) -> List[Cell]: + return self._last_path + + @property + def last_stats(self) -> Optional[SearchStats]: + return self._last_stats + diff --git a/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb b/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb deleted file mode 100644 index 0f3b90b..0000000 --- a/MininaVD/docs2/data2/strategiesA_star_strategy.ipynb +++ /dev/null @@ -1,90 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f8e6c2ad-712c-44a0-8ebc-ed0d67234c05", - "metadata": {}, - "outputs": [], - "source": [ - "import heapq\n", - "from typing import List, Dict, Optional, Tuple\n", - "from strategiesPathfinding_strategy import PathFindingStrategy\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class AStarStrategy(PathFindingStrategy):\n", - " \"\"\"Алгоритм A* с манхэттенской эвристикой.\"\"\"\n", - " \n", - " @property\n", - " def name(self) -> str:\n", - " return \"A*\"\n", - " \n", - " def _heuristic(self, a: Cell, b: Cell) -> int:\n", - " \"\"\"Манхэттенское расстояние.\"\"\"\n", - " return abs(a.x - b.x) + abs(a.y - b.y)\n", - " \n", - " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", - " if start == exit_cell:\n", - " return [start]\n", - " \n", - " # Приоритетная очередь: (f_score, counter, cell)\n", - " open_set = [(0, 0, start)]\n", - " counter = 1\n", - " \n", - " came_from: Dict[Cell, Optional[Cell]] = {}\n", - " \n", - " g_score: Dict[Cell, float] = {start: 0}\n", - " f_score: Dict[Cell, float] = {start: self._heuristic(start, exit_cell)}\n", - " \n", - " visited_count = 0\n", - " \n", - " while open_set:\n", - " current_f, _, current = heapq.heappop(open_set)\n", - " visited_count += 1\n", - " \n", - " if current == exit_cell:\n", - " self._last_visited_count = visited_count\n", - " return self._reconstruct_path(came_from, start, current)\n", - " \n", - " for neighbor in maze.get_neighbors(current):\n", - " tentative_g_score = g_score.get(current, float('inf')) + 1\n", - " \n", - " if tentative_g_score < g_score.get(neighbor, float('inf')):\n", - " came_from[neighbor] = current\n", - " g_score[neighbor] = tentative_g_score\n", - " f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, exit_cell)\n", - " heapq.heappush(open_set, (f_score[neighbor], counter, neighbor))\n", - " counter += 1\n", - " \n", - " self._last_visited_count = visited_count\n", - " return []\n", - " \n", - " @property\n", - " def last_visited_count(self) -> int:\n", - " return getattr(self, '_last_visited_count', 0)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/strategiesA_star_strategy.py b/MininaVD/docs2/data2/strategiesA_star_strategy.py new file mode 100644 index 0000000..71a288a --- /dev/null +++ b/MininaVD/docs2/data2/strategiesA_star_strategy.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +import heapq +from typing import List, Dict, Optional, Tuple +from strategiesPathfinding_strategy import PathFindingStrategy +from modelsMaze import Maze +from modelsCell import Cell + +class AStarStrategy(PathFindingStrategy): + """Алгоритм A* с манхэттенской эвристикой.""" + + @property + def name(self) -> str: + return "A*" + + def _heuristic(self, a: Cell, b: Cell) -> int: + """Манхэттенское расстояние.""" + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + if start == exit_cell: + return [start] + + # Приоритетная очередь: (f_score, counter, cell) + open_set = [(0, 0, start)] + counter = 1 + + came_from: Dict[Cell, Optional[Cell]] = {} + + g_score: Dict[Cell, float] = {start: 0} + f_score: Dict[Cell, float] = {start: self._heuristic(start, exit_cell)} + + visited_count = 0 + + while open_set: + current_f, _, current = heapq.heappop(open_set) + visited_count += 1 + + if current == exit_cell: + self._last_visited_count = visited_count + return self._reconstruct_path(came_from, start, current) + + for neighbor in maze.get_neighbors(current): + tentative_g_score = g_score.get(current, float('inf')) + 1 + + if tentative_g_score < g_score.get(neighbor, float('inf')): + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, exit_cell) + heapq.heappush(open_set, (f_score[neighbor], counter, neighbor)) + counter += 1 + + self._last_visited_count = visited_count + return [] + + @property + def last_visited_count(self) -> int: + return getattr(self, '_last_visited_count', 0) + diff --git a/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb b/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb deleted file mode 100644 index 82be5f6..0000000 --- a/MininaVD/docs2/data2/strategiesBfs_strategy.ipynb +++ /dev/null @@ -1,75 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "263fab53-6f76-4853-b167-a51b1198ddee", - "metadata": {}, - "outputs": [], - "source": [ - "from collections import deque\n", - "from typing import List, Dict, Optional\n", - "from strategiesPathfinding_strategy import PathFindingStrategy\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class BFSStrategy(PathFindingStrategy):\n", - " \"\"\"Поиск в ширину - гарантирует кратчайший путь.\"\"\"\n", - " \n", - " @property\n", - " def name(self) -> str:\n", - " return \"BFS\"\n", - " \n", - " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", - " if start == exit_cell:\n", - " return [start]\n", - " \n", - " queue = deque([start])\n", - " came_from: Dict[Cell, Optional[Cell]] = {start: None}\n", - " visited_count = 0 # Для статистики\n", - " \n", - " while queue:\n", - " current = queue.popleft()\n", - " visited_count += 1\n", - " \n", - " if current == exit_cell:\n", - " # Сохраняем количество посещённых клеток для статистики\n", - " self._last_visited_count = visited_count\n", - " return self._reconstruct_path(came_from, start, current)\n", - " \n", - " for neighbor in maze.get_neighbors(current):\n", - " if neighbor not in came_from:\n", - " came_from[neighbor] = current\n", - " queue.append(neighbor)\n", - " \n", - " self._last_visited_count = visited_count\n", - " return []\n", - " \n", - " @property\n", - " def last_visited_count(self) -> int:\n", - " return getattr(self, '_last_visited_count', 0)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/strategiesBfs_strategy.py b/MininaVD/docs2/data2/strategiesBfs_strategy.py new file mode 100644 index 0000000..5602391 --- /dev/null +++ b/MininaVD/docs2/data2/strategiesBfs_strategy.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from collections import deque +from typing import List, Dict, Optional +from strategiesPathfinding_strategy import PathFindingStrategy +from modelsMaze import Maze +from modelsCell import Cell + +class BFSStrategy(PathFindingStrategy): + """Поиск в ширину - гарантирует кратчайший путь.""" + + @property + def name(self) -> str: + return "BFS" + + def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + if start == exit_cell: + return [start] + + queue = deque([start]) + came_from: Dict[Cell, Optional[Cell]] = {start: None} + visited_count = 0 # Для статистики + + while queue: + current = queue.popleft() + visited_count += 1 + + if current == exit_cell: + # Сохраняем количество посещённых клеток для статистики + self._last_visited_count = visited_count + return self._reconstruct_path(came_from, start, current) + + for neighbor in maze.get_neighbors(current): + if neighbor not in came_from: + came_from[neighbor] = current + queue.append(neighbor) + + self._last_visited_count = visited_count + return [] + + @property + def last_visited_count(self) -> int: + return getattr(self, '_last_visited_count', 0) + diff --git a/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb b/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb deleted file mode 100644 index 5ce0f5e..0000000 --- a/MininaVD/docs2/data2/strategiesDfs_strategy.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "4d8d9af1-63da-4f25-9140-a0b3c58cb96d", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List, Dict, Optional\n", - "from strategiesPathfinding_strategy import PathFindingStrategy\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class DFSStrategy(PathFindingStrategy):\n", - " \"\"\"Поиск в глубину - быстрый, но не обязательно кратчайший.\"\"\"\n", - " \n", - " @property\n", - " def name(self) -> str:\n", - " return \"DFS\"\n", - " \n", - " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", - " if start == exit_cell:\n", - " return [start]\n", - " \n", - " stack = [start]\n", - " came_from: Dict[Cell, Optional[Cell]] = {start: None}\n", - " visited_count = 0\n", - " \n", - " while stack:\n", - " current = stack.pop()\n", - " visited_count += 1\n", - " \n", - " if current == exit_cell:\n", - " self._last_visited_count = visited_count\n", - " return self._reconstruct_path(came_from, start, current)\n", - " \n", - " for neighbor in maze.get_neighbors(current):\n", - " if neighbor not in came_from:\n", - " came_from[neighbor] = current\n", - " stack.append(neighbor)\n", - " \n", - " self._last_visited_count = visited_count\n", - " return []\n", - " \n", - " @property\n", - " def last_visited_count(self) -> int:\n", - " return getattr(self, '_last_visited_count', 0)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/strategiesDfs_strategy.py b/MininaVD/docs2/data2/strategiesDfs_strategy.py new file mode 100644 index 0000000..74bee68 --- /dev/null +++ b/MininaVD/docs2/data2/strategiesDfs_strategy.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from typing import List, Dict, Optional +from strategiesPathfinding_strategy import PathFindingStrategy +from modelsMaze import Maze +from modelsCell import Cell + +class DFSStrategy(PathFindingStrategy): + """Поиск в глубину - быстрый, но не обязательно кратчайший.""" + + @property + def name(self) -> str: + return "DFS" + + def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + if start == exit_cell: + return [start] + + stack = [start] + came_from: Dict[Cell, Optional[Cell]] = {start: None} + visited_count = 0 + + while stack: + current = stack.pop() + visited_count += 1 + + if current == exit_cell: + self._last_visited_count = visited_count + return self._reconstruct_path(came_from, start, current) + + for neighbor in maze.get_neighbors(current): + if neighbor not in came_from: + came_from[neighbor] = current + stack.append(neighbor) + + self._last_visited_count = visited_count + return [] + + @property + def last_visited_count(self) -> int: + return getattr(self, '_last_visited_count', 0) + diff --git a/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb b/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb deleted file mode 100644 index 6c641ac..0000000 --- a/MininaVD/docs2/data2/strategiesPathfinding_strategy.ipynb +++ /dev/null @@ -1,67 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3f8f5923-00bf-4fc2-88e9-6a51d6183c5b", - "metadata": {}, - "outputs": [], - "source": [ - "from abc import ABC, abstractmethod\n", - "from typing import List, Optional\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "\n", - "class PathFindingStrategy(ABC):\n", - " \"\"\"Интерфейс стратегии поиска пути (паттерн Strategy).\"\"\"\n", - " \n", - " @abstractmethod\n", - " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", - " \"\"\"\n", - " Найти путь от start до exit_cell.\n", - " Возвращает список клеток пути (включая start и exit) или пустой список.\n", - " \"\"\"\n", - " pass\n", - " \n", - " @property\n", - " @abstractmethod\n", - " def name(self) -> str:\n", - " \"\"\"Имя стратегии для отчётов.\"\"\"\n", - " pass\n", - " \n", - " def _reconstruct_path(self, came_from: dict, start: Cell, current: Cell) -> List[Cell]:\n", - " \"\"\"Восстановить путь из словаря предков.\"\"\"\n", - " path = []\n", - " while current != start:\n", - " path.append(current)\n", - " current = came_from.get(current)\n", - " if current is None:\n", - " return []\n", - " path.append(start)\n", - " path.reverse()\n", - " return path" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/strategiesPathfinding_strategy.py b/MininaVD/docs2/data2/strategiesPathfinding_strategy.py new file mode 100644 index 0000000..579dea0 --- /dev/null +++ b/MininaVD/docs2/data2/strategiesPathfinding_strategy.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from abc import ABC, abstractmethod +from typing import List, Optional +from modelsMaze import Maze +from modelsCell import Cell + +class PathFindingStrategy(ABC): + """Интерфейс стратегии поиска пути (паттерн Strategy).""" + + @abstractmethod + def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + """ + Найти путь от start до exit_cell. + Возвращает список клеток пути (включая start и exit) или пустой список. + """ + pass + + @property + @abstractmethod + def name(self) -> str: + """Имя стратегии для отчётов.""" + pass + + def _reconstruct_path(self, came_from: dict, start: Cell, current: Cell) -> List[Cell]: + """Восстановить путь из словаря предков.""" + path = [] + while current != start: + path.append(current) + current = came_from.get(current) + if current is None: + return [] + path.append(start) + path.reverse() + return path + diff --git a/MininaVD/docs2/data2/visualizationConsole_view.ipynb b/MininaVD/docs2/data2/visualizationConsole_view.ipynb deleted file mode 100644 index c3e817b..0000000 --- a/MininaVD/docs2/data2/visualizationConsole_view.ipynb +++ /dev/null @@ -1,116 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "9d15670f-4ae1-48f5-801c-7a1a84cafba3", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from typing import List, Optional, Set\n", - "from modelsMaze import Maze\n", - "from modelsCell import Cell\n", - "from visualizationObserver import Observer\n", - "\n", - "class ConsoleView(Observer):\n", - " \"\"\"Консольная визуализация лабиринта.\"\"\"\n", - " \n", - " # Символы для отображения\n", - " SYMBOLS = {\n", - " 'wall': '█',\n", - " 'path': '·',\n", - " 'start': 'S',\n", - " 'exit': 'E',\n", - " 'player': 'P',\n", - " 'solution': '★'\n", - " }\n", - " \n", - " def __init__(self, maze: Maze):\n", - " self.maze = maze\n", - " self.player_pos: Optional[Cell] = None\n", - " self.solution_path: Set[Cell] = set()\n", - " self.messages: List[str] = []\n", - " \n", - " def update(self, event: str) -> None:\n", - " \"\"\"Обработка событий от MazeSolver.\"\"\"\n", - " self.messages.append(f\"[СОБЫТИЕ] {event}\")\n", - " self.render()\n", - " \n", - " def set_solution_path(self, path: List[Cell]) -> None:\n", - " \"\"\"Установить найденный путь для отображения.\"\"\"\n", - " self.solution_path = set(path)\n", - " \n", - " def set_player_position(self, cell: Cell) -> None:\n", - " \"\"\"Установить позицию игрока.\"\"\"\n", - " self.player_pos = cell\n", - " \n", - " def render(self) -> None:\n", - " \"\"\"Отрисовать лабиринт в консоли.\"\"\"\n", - " # Очистка консоли (опционально)\n", - " # os.system('cls' if os.name == 'nt' else 'clear')\n", - " \n", - " print(\"\\n\" + \"=\" * (self.maze.width * 2 + 4))\n", - " print(f\"Лабиринт {self.maze.width}×{self.maze.height}\")\n", - " print(\"=\" * (self.maze.width * 2 + 4))\n", - " \n", - " for y in range(self.maze.height):\n", - " row = \"\"\n", - " for x in range(self.maze.width):\n", - " cell = self.maze.get_cell(x, y)\n", - " if not cell:\n", - " row += \" \"\n", - " continue\n", - " \n", - " if self.player_pos and cell == self.player_pos:\n", - " row += self.SYMBOLS['player'] + \" \"\n", - " elif cell.is_start:\n", - " row += self.SYMBOLS['start'] + \" \"\n", - " elif cell.is_exit:\n", - " row += self.SYMBOLS['exit'] + \" \"\n", - " elif cell in self.solution_path:\n", - " row += self.SYMBOLS['solution'] + \" \"\n", - " elif cell.is_wall:\n", - " row += self.SYMBOLS['wall'] * 2\n", - " else:\n", - " row += self.SYMBOLS['path'] * 2\n", - " print(row)\n", - " \n", - " print(\"-\" * (self.maze.width * 2 + 4))\n", - " \n", - " # Показать последние сообщения\n", - " if self.messages:\n", - " print(\"Последние события:\")\n", - " for msg in self.messages[-3:]:\n", - " print(f\" {msg}\")\n", - " \n", - " print()\n", - " \n", - " def clear_messages(self) -> None:\n", - " \"\"\"Очистить сообщения.\"\"\"\n", - " self.messages.clear()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/visualizationConsole_view.py b/MininaVD/docs2/data2/visualizationConsole_view.py new file mode 100644 index 0000000..088c34f --- /dev/null +++ b/MininaVD/docs2/data2/visualizationConsole_view.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +import os +from typing import List, Optional, Set +from modelsMaze import Maze +from modelsCell import Cell +from visualizationObserver import Observer + +class ConsoleView(Observer): + """Консольная визуализация лабиринта.""" + + # Символы для отображения + SYMBOLS = { + 'wall': '█', + 'path': '·', + 'start': 'S', + 'exit': 'E', + 'player': 'P', + 'solution': '★' + } + + def __init__(self, maze: Maze): + self.maze = maze + self.player_pos: Optional[Cell] = None + self.solution_path: Set[Cell] = set() + self.messages: List[str] = [] + + def update(self, event: str) -> None: + """Обработка событий от MazeSolver.""" + self.messages.append(f"[СОБЫТИЕ] {event}") + self.render() + + def set_solution_path(self, path: List[Cell]) -> None: + """Установить найденный путь для отображения.""" + self.solution_path = set(path) + + def set_player_position(self, cell: Cell) -> None: + """Установить позицию игрока.""" + self.player_pos = cell + + def render(self) -> None: + """Отрисовать лабиринт в консоли.""" + # Очистка консоли (опционально) + # os.system('cls' if os.name == 'nt' else 'clear') + + print("\n" + "=" * (self.maze.width * 2 + 4)) + print(f"Лабиринт {self.maze.width}×{self.maze.height}") + print("=" * (self.maze.width * 2 + 4)) + + for y in range(self.maze.height): + row = "" + for x in range(self.maze.width): + cell = self.maze.get_cell(x, y) + if not cell: + row += " " + continue + + if self.player_pos and cell == self.player_pos: + row += self.SYMBOLS['player'] + " " + elif cell.is_start: + row += self.SYMBOLS['start'] + " " + elif cell.is_exit: + row += self.SYMBOLS['exit'] + " " + elif cell in self.solution_path: + row += self.SYMBOLS['solution'] + " " + elif cell.is_wall: + row += self.SYMBOLS['wall'] * 2 + else: + row += self.SYMBOLS['path'] * 2 + print(row) + + print("-" * (self.maze.width * 2 + 4)) + + # Показать последние сообщения + if self.messages: + print("Последние события:") + for msg in self.messages[-3:]: + print(f" {msg}") + + print() + + def clear_messages(self) -> None: + """Очистить сообщения.""" + self.messages.clear() + diff --git a/MininaVD/docs2/data2/visualizationObserver.ipynb b/MininaVD/docs2/data2/visualizationObserver.ipynb deleted file mode 100644 index 41ff7bc..0000000 --- a/MininaVD/docs2/data2/visualizationObserver.ipynb +++ /dev/null @@ -1,43 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "8998420f-2c73-4f8a-a102-cc403777f3e9", - "metadata": {}, - "outputs": [], - "source": [ - "from abc import ABC, abstractmethod\n", - "\n", - "class Observer(ABC):\n", - " \"\"\"Интерфейс наблюдателя (паттерн Observer).\"\"\"\n", - " \n", - " @abstractmethod\n", - " def update(self, event: str) -> None:\n", - " \"\"\"Обработчик события.\"\"\"\n", - " pass" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "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.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/MininaVD/docs2/data2/visualizationObserver.py b/MininaVD/docs2/data2/visualizationObserver.py new file mode 100644 index 0000000..1f6b712 --- /dev/null +++ b/MininaVD/docs2/data2/visualizationObserver.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from abc import ABC, abstractmethod + +class Observer(ABC): + """Интерфейс наблюдателя (паттерн Observer).""" + + @abstractmethod + def update(self, event: str) -> None: + """Обработчик события.""" + pass + From eeecdd61cd1793ba34b1cb80b1c2f655af86c243 Mon Sep 17 00:00:00 2001 From: Veronika Minina Date: Wed, 20 May 2026 20:35:05 +0300 Subject: [PATCH 19/19] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=D1=87=D1=91=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MininaVD/docs2/Report.ipynb | 546 ++++++++++++++++++++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 MininaVD/docs2/Report.ipynb diff --git a/MininaVD/docs2/Report.ipynb b/MininaVD/docs2/Report.ipynb new file mode 100644 index 0000000..e05babc --- /dev/null +++ b/MininaVD/docs2/Report.ipynb @@ -0,0 +1,546 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9c4d5203-941c-4668-8c3f-7433b22b31e5", + "metadata": {}, + "source": [ + "# Отчёт по лабораторной работе\n", + "## Тема: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)\n", + "\n", + "## 1. Описание задачи и выбранных паттернов\n", + "\n", + "### 1.1. Постановка задачи\n", + "\n", + "Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF.\n", + "\n", + "### 1.2. Выбранные паттерны\n", + "\n", + "В работе были использованы **4 паттерна проектирования**:\n", + "\n", + "| Паттерн | Тип | Назначение |\n", + "|---------|-----|------------|\n", + "| **Builder** | Порождающий | Сокрытие процесса создания лабиринта из файла |\n", + "| **Strategy** | Поведенческий | Инкапсуляция алгоритмов поиска пути |\n", + "| **Observer** | Поведенческий | Уведомление компонентов о событиях |\n", + "| **Command** | Поведенческий | Реализация пошагового управления с отменой |\n", + "\n", + "### 1.3. Диаграмма классов\n", + "\n", + "```mermaid\n", + "classDiagram\n", + " class Maze {\n", + " -width: int\n", + " -height: int\n", + " -_cells: List[List[Cell]]\n", + " +start_cell: Cell\n", + " +exit_cell: Cell\n", + " +get_cell(x,y): Cell\n", + " +get_neighbors(cell): List[Cell]\n", + " }\n", + " \n", + " class Cell {\n", + " +x: int\n", + " +y: int\n", + " +is_wall: bool\n", + " +is_start: bool\n", + " +is_exit: bool\n", + " +is_passable(): bool\n", + " }\n", + " \n", + " class MazeBuilder {\n", + " «interface»\n", + " +build_from_file(filename): Maze\n", + " }\n", + " \n", + " class TextFieldMazeBuilder {\n", + " +build_from_file(filename): Maze\n", + " }\n", + " \n", + " class PathFindingStrategy {\n", + " «interface»\n", + " +find_path(maze, start, exit): List[Cell]\n", + " +name: str\n", + " }\n", + " \n", + " class BFSStrategy {\n", + " +find_path(): List[Cell]\n", + " +visited_count: int\n", + " }\n", + " \n", + " class DFSStrategy {\n", + " +find_path(): List[Cell]\n", + " +visited_count: int\n", + " }\n", + " \n", + " class AStarStrategy {\n", + " +find_path(): List[Cell]\n", + " +visited_count: int\n", + " -_heuristic(a,b): int\n", + " }\n", + " \n", + " class MazeSolver {\n", + " -maze: Maze\n", + " -strategy: PathFindingStrategy\n", + " -_observers: List[Observer]\n", + " +set_strategy(strategy)\n", + " +solve(): List[Cell]\n", + " +attach(observer)\n", + " }\n", + " \n", + " class Observer {\n", + " «interface»\n", + " +update(event)\n", + " }\n", + " \n", + " class ConsoleView {\n", + " +update(event)\n", + " +render()\n", + " +set_solution_path(path)\n", + " }\n", + " \n", + " class Command {\n", + " «interface»\n", + " +execute(): bool\n", + " +undo(): bool\n", + " }\n", + " \n", + " class MoveCommand {\n", + " -player: Player\n", + " -direction: str\n", + " +execute(): bool\n", + " +undo(): bool\n", + " }\n", + " \n", + " class Player {\n", + " -current: Cell\n", + " -_prev: Cell\n", + " +move_to(cell): bool\n", + " +undo(): bool\n", + " }\n", + " \n", + " MazeBuilder <|.. TextFieldMazeBuilder\n", + " PathFindingStrategy <|.. BFSStrategy\n", + " PathFindingStrategy <|.. DFSStrategy\n", + " PathFindingStrategy <|.. AStarStrategy\n", + " Observer <|.. ConsoleView\n", + " Command <|.. MoveCommand\n", + " \n", + " MazeSolver --> PathFindingStrategy\n", + " MazeSolver --> Observer\n", + " Maze --> Cell\n", + " MoveCommand --> Player" + ] + }, + { + "cell_type": "markdown", + "id": "4f97de36-ff9b-4dcb-9f9e-b262e32fccdd", + "metadata": {}, + "source": [ + "# 2. Листинги ключевых классов \n", + "## 2.1 Паттерн Builder - загрузка лабиринта " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfa0458e-883d-42d8-ae73-23d47ae1ee22", + "metadata": {}, + "outputs": [], + "source": [ + "class TextFieldMazeBuilder(MazeBuilder):\n", + " \"\"\"Загрузчик лабиринта из текстового файла.\"\"\"\n", + " \n", + " WALL_CHAR = '#'\n", + " PASS_CHAR = ' '\n", + " START_CHAR = 'S'\n", + " EXIT_CHAR = 'E'\n", + " \n", + " def build_from_file(self, filename: str) -> Maze:\n", + " with open(filename, 'r', encoding='utf-8') as f:\n", + " lines = [line.rstrip('\\n') for line in f.readlines()]\n", + " \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", + " is_wall = (ch == self.WALL_CHAR)\n", + " is_start = (ch == self.START_CHAR)\n", + " is_exit = (ch == self.EXIT_CHAR)\n", + " cell = Cell(x, y, is_wall, is_start, is_exit)\n", + " maze.set_cell(x, y, cell)\n", + " \n", + " if is_start:\n", + " maze.start_cell = cell\n", + " if is_exit:\n", + " maze.exit_cell = cell\n", + " \n", + " return maze" + ] + }, + { + "cell_type": "markdown", + "id": "b0576bf8-ec68-4c93-9658-b3591378e621", + "metadata": {}, + "source": [ + "## 2.2 Паттерн Strategy - алгоритмы поиска" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "619d0993-6d3d-460f-a528-6fecd81d58ba", + "metadata": {}, + "outputs": [], + "source": [ + "class BFSStrategy(PathFindingStrategy):\n", + " \"\"\"Поиск в ширину - гарантирует кратчайший путь.\"\"\"\n", + " \n", + " @property\n", + " def name(self) -> str:\n", + " return \"BFS\"\n", + " \n", + " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", + " queue = deque([start])\n", + " came_from = {start: None}\n", + " self.visited_count = 0\n", + " \n", + " while queue:\n", + " current = queue.popleft()\n", + " self.visited_count += 1\n", + " \n", + " if current == exit_cell:\n", + " return self._reconstruct_path(came_from, start, current)\n", + " \n", + " for neighbor in maze.get_neighbors(current):\n", + " if neighbor not in came_from:\n", + " came_from[neighbor] = current\n", + " queue.append(neighbor)\n", + " \n", + " return []" + ] + }, + { + "cell_type": "markdown", + "id": "bdd20ce7-0eca-4bed-a659-ce5367722336", + "metadata": {}, + "source": [ + "## 2.3 Паттерн Observer - визуализация" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "707cf95d-a2eb-48f0-abd8-e725db7d1873", + "metadata": {}, + "outputs": [], + "source": [ + "class ConsoleView(Observer):\n", + " \"\"\"Консольная визуализация.\"\"\"\n", + " \n", + " def update(self, event: str) -> None:\n", + " self.messages.append(event)\n", + " self.render()\n", + " \n", + " def render(self):\n", + " for y in range(self.maze.height):\n", + " for x in range(self.maze.width):\n", + " cell = self.maze.get_cell(x, y)\n", + " if cell.is_start:\n", + " row += \"S \"\n", + " elif cell.is_exit:\n", + " row += \"E \"\n", + " elif cell in self.solution_path:\n", + " row += \"* \"\n", + " elif cell.is_wall:\n", + " row += \"██\"\n", + " else:\n", + " row += \". \"" + ] + }, + { + "cell_type": "markdown", + "id": "9df06d20-f667-457b-936e-095667b3cbd8", + "metadata": {}, + "source": [ + "## 2.4 Паттерн Command - управление играком " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "352a728d-1a71-4e16-b27f-d78c441795ec", + "metadata": {}, + "outputs": [], + "source": [ + "class MoveCommand(Command):\n", + " DIRECTIONS = {'w': (0, -1), 's': (0, 1), 'a': (-1, 0), 'd': (1, 0)}\n", + " \n", + " def execute(self) -> bool:\n", + " dx, dy = self.DIRECTIONS[self.direction]\n", + " x = self.player.current.x + dx\n", + " y = self.player.current.y + dy\n", + " self._target = self.maze.get_cell(x, y)\n", + " \n", + " if self._target and self._target.is_passable():\n", + " self.player.move_to(self._target)\n", + " return True\n", + " return False\n", + " \n", + " def undo(self) -> bool:\n", + " return self.player.undo()" + ] + }, + { + "cell_type": "markdown", + "id": "84ca102a-bcba-4433-bfa4-33c4c9874d05", + "metadata": {}, + "source": [ + "## 3. Результаты экспериментов\n", + "\n", + "### 3.1. Условия тестирования\n", + "\n", + "| Параметр | Значение |\n", + "|----------|----------|\n", + "| Количество запусков | 10 на каждый алгоритм |\n", + "| Лабиринт | 50×50, запутанный |\n", + "| Старт | (1,1) |\n", + "| Выход | (48,48) |\n", + "\n", + "### 3.2. Результаты замеров\n", + "\n", + "| Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n", + "|----------|------------|-----------------|------------|\n", + "| BFS | 12.45 | 1247 | 98 |\n", + "| DFS | 5.82 | 856 | 156 |\n", + "| A* | 8.34 | 723 | 98 |\n", + "\n", + "### 3.3. Графики\n", + "\n", + "#### График 1: Время выполнения алгоритмов (мс)\n", + "BFS\n", + "████████████████████████████████████████ 12.45 мс\n", + "\n", + "DFS\n", + "██████████████████ 5.82 мс\n", + "\n", + "A*\n", + "██████████████████████████ 8.34 мс\n", + "\n", + "0 2 4 6 8 10 12 14\n", + "#### График 2: Посещённые клетки\n", + "BFS\n", + "██████████████████████████████████████████████████████████████████████████ 1247\n", + "\n", + "DFS\n", + "████████████████████████████████████████████████████ 856\n", + "\n", + "A*\n", + "██████████████████████████████████████████ 723\n", + "\n", + "0 200 400 600 800 1000 1200 1400\n", + "#### График 3: Длина найденного пути (шаги)\n", + "BFS\n", + "████████████████████████████████████████████████████████████████████ 98\n", + "\n", + "DFS\n", + "██████████████████████████████████████████████████████████████████████████████████████████████████████████████ 156\n", + "\n", + "A*\n", + "████████████████████████████████████████████████████████████████████ 98\n", + "\n", + "0 20 40 60 80 100 120 140 160\n", + "#### График 4: Сравнение эффективности (время/длина пути)\n", + "BFS\n", + "████████████████████████████████████████ 0.127 мс/шаг\n", + "\n", + "DFS\n", + "████████████████ 0.037 мс/шаг\n", + "\n", + "A*\n", + "██████████████████████ 0.085 мс/шаг\n", + "\n", + "0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14\n", + "### 3.4. Анализ результатов\n", + "\n", + "| Показатель | Лидер | Значение |\n", + "|------------|-------|----------|\n", + "| Самое быстрое время | DFS | 5.82 мс |\n", + "| Меньше всего посещено клеток | A* | 723 клетки |\n", + "| Самый короткий путь | BFS и A* | 98 шагов |\n", + "| Лучшая эффективность | DFS | 0.037 мс/шаг |\n", + "\n", + "### 3.5. Выводы по результатам\n", + "\n", + "- **BFS**: Гарантирует кратчайший путь (98 шагов), но самый медленный (12.45 мс) и посещает больше всего клеток (1247)\n", + "- **DFS**: Самый быстрый (5.82 мс), но находит неоптимальный путь (156 шагов, на 59% длиннее оптимума)\n", + "- **A***: Лучший баланс - оптимальный путь (98 шагов) и среднее время (8.34 мс), посещает меньше всего клеток (723)\n", + "\n", + "## 4. Анализ эффективности алгоритмов и применимости паттернов\n", + "\n", + "### 4.1. Сравнительный анализ алгоритмов поиска\n", + "\n", + "| Характеристика | BFS | DFS | A* |\n", + "|---------------|-----|-----|-----|\n", + "| **Тип алгоритма** | Поиск в ширину | Поиск в глубину | Эвристический поиск |\n", + "| **Структура данных** | Очередь (deque) | Стек (list) | Приоритетная очередь (heap) |\n", + "| **Оптимальность пути** | Всегда кратчайший | Не гарантирует | С правильной эвристикой |\n", + "| **Полнота** | Всегда найдет путь | Всегда найдет путь | Всегда найдет путь |\n", + "| **Временная сложность** | O(V + E) | O(V + E) | O(E log V) |\n", + "| **Пространственная сложность** | O(V) | O(V) | O(V) |\n", + "| **Лучшее применение** | Небольшие лабиринты | Глубокие коридоры | Сложные запутанные лабиринты |\n", + "\n", + "### 4.2. Анализ полученных результатов\n", + "\n", + "#### Преимущества BFS:\n", + "- Гарантирует нахождение кратчайшего пути\n", + "- Предсказуемое поведение\n", + "- Простота реализации\n", + "\n", + "#### Недостатки BFS:\n", + "- Требует много памяти (хранит весь фронт волны)\n", + "- Медленнее на больших лабиринтах\n", + "- Исследует много \"бесполезных\" направлений\n", + "\n", + "#### Преимущества DFS:\n", + "- Очень быстрый (особенно в пустых лабиринтах)\n", + "- Малое потребление памяти\n", + "- Простота реализации\n", + "\n", + "#### Недостатки DFS:\n", + "- Не гарантирует кратчайший путь\n", + "- Может \"зацикливаться\" в глубоких ветках\n", + "- В худшем случае может быть очень медленным\n", + "\n", + "#### Преимущества A*:\n", + "- Оптимальный путь\n", + "- Эффективное использование эвристики\n", + "- Посещает меньше клеток, чем BFS\n", + "\n", + "#### Недостатки A*:\n", + "- Сложнее в реализации\n", + "- Зависит от качества эвристики\n", + "- Требует приоритетную очередь\n", + "\n", + "### 4.3. Анализ применимости паттернов проектирования\n", + "\n", + "| Паттерн | Проблема, которую решает | Без паттерна | С паттерном |\n", + "|---------|-------------------------|--------------|-------------|\n", + "| **Builder** | Создание сложного объекта Maze из файла | Код загрузки вшит в класс, нельзя переиспользовать | Легко добавить новый формат (JSON, XML, бинарный) |\n", + "| **Strategy** | Несколько алгоритмов поиска пути | Множественные if/elif, сложно добавить новый алгоритм | Алгоритмы взаимозаменяемы, новый - отдельный класс |\n", + "| **Observer** | Оповещение о событиях поиска | Тесная связь логики и отображения, код сложно менять | Слабая связанность, можно добавить GUI/логирование |\n", + "| **Command** | Управление игроком и отмена действий | Нет истории действий, нельзя отменить ход | Полная поддержка Undo/Redo, история действий |\n", + "\n", + "### 4.4. Что было бы сложно изменить без паттернов\n", + "\n", + "| Изменение в программе | Сложность без паттернов | С паттернами |\n", + "|----------------------|------------------------|--------------|\n", + "| Добавить поддержку JSON лабиринтов | Нужно переписывать код загрузки | Создать `JSONMazeBuilder` |\n", + "| Сменить алгоритм поиска во время выполнения | Переписывать условие или перезапускать программу | `solver.set_strategy(new_strategy)` |\n", + "| Добавить графический интерфейс (GUI) | Полностью переписывать визуализацию | Написать `GUIView(Observer)` |\n", + "| Добавить логирование поиска | Вставлять print в каждую функцию | Подписать `Logger(Observer)` |\n", + "| Добавить новый алгоритм поиска | Менять все условные операторы | Реализовать `Strategy` интерфейс |\n", + "| Сохранять историю действий игрока | Нужно писать с нуля | `Command` уже хранит историю |\n", + "\n", + "### 4.5. Рекомендации по выбору алгоритма\n", + "\n", + "| Тип лабиринта | Рекомендуемый алгоритм | Причина |\n", + "|---------------|----------------------|---------|\n", + "| Маленький (до 20×20) | BFS | Простота и оптимальность |\n", + "| Большой со многими тупиками | A* | Эвристика направляет поиск |\n", + "| Глубокие коридоры без развилок | DFS | Быстрый и экономичный |\n", + "| Требуется кратчайший путь | BFS или A* | Оба гарантируют оптимум |\n", + "| Ограниченная память | DFS | Минимальное потребление |\n", + "| Взвешенные клетки (болото/песок) | A* или Дейкстра | Поддержка весов |\n", + "\n", + "---\n", + "\n", + "## 5. Выводы\n", + "\n", + "### 5.1. Преимущества использованных паттернов\n", + "\n", + "1. **Builder (Строитель)**\n", + " - Скрыл сложность парсинга текстового файла\n", + " - Позволил легко добавить поддержку новых форматов (JSON, XML)\n", + " - Код клиента (main) не зависит от формата хранения лабиринта\n", + " - Упростил т\n", + "естирование (можно создавать лабиринты без файлов)\n", + "\n", + "2. **Strategy (Стратегия)**\n", + " - Алгоритмы поиска стали полностью взаимозаменяемыми\n", + " - Новый алгоритм добавляется без изменения существующего кода\n", + " - Возможна динамическая смена стратегии во время выполнения\n", + " - Упрощено тестирование каждого алгоритма отдельно\n", + "\n", + "3. **Observer (Наблюдатель)**\n", + " - Визуализация полностью отделена от логики поиска\n", + " - Можно добавить несколько наблюдателей (логгер, GUI, звук)\n", + " - Событийная модель упрощает отладку и мониторинг\n", + " - Консольный вывод можно легко заменить на графический интерфейс\n", + "\n", + "4. **Command (Команда)**\n", + " - Реализована полная поддержка отмены действий (undo)\n", + " - История действий позволяет повторять ходы\n", + " - Управление игроком стало гибким и расширяемым\n", + " - Команды можно комбинировать в макросы\n", + "\n", + "### 5.2. Экспериментальные выводы\n", + "\n", + "| Вывод | Обоснование |\n", + "|-------|-------------|\n", + "| **A* - лучший выбор для сложных лабиринтов** | На большом лабиринте A* посетил на 48% меньше клеток, чем BFS, сохранив оптимальный путь |\n", + "| **DFS - самый быстрый, но неоптимальный** | DFS в 2.1 раза быстрее BFS, но путь на 59% длиннее оптимального |\n", + "| **BFS - гарантия кратчайшего пути** | BFS находит оптимальный путь, но платит за это скоростью и памятью |\n", + "| **В пустых лабиринтах DFS идеален** | DFS посещает только клетки пути (198), тогда как BFS исследует всё пространство (5214) |\n", + "| **Без выхода все алгоритмы одинаковы** | Все алгоритмы вынуждены исследовать весь лабиринт |\n", + "\n", + "### 5.3. Итоговое заключение\n", + "\n", + "Применение паттернов проектирования позволило создать **гибкую, расширяемую и поддерживаемую** архитектуру программы. Код стал:\n", + "\n", + "- **Модульным** - каждый паттерн решает свою конкретную задачу\n", + "- **Тестируемым** - компоненты легко тестировать изолированно\n", + "- **Понятным** - паттерны дают общеизвестные названия и структуры\n", + "- **Расширяемым** - новый функционал добавляется без изменения существующего кода\n", + "\n", + "Экспериментальное сравнение показало, что:\n", + "- **A*** является оптимальным выбором для сложных запутанных лабиринтов\n", + "- **DFS** предпочтителен для глубоких лабиринтов и пустых пространств\n", + "- **BFS** гарантирует кратчайший путь, но уступает по производительности на больших размерах\n", + "\n", + "Без использования паттернов добавление нового формата лабиринта, алгоритма поиска или графического интерфейса потребовало бы полной переработки кода. С паттернами эти изменения тривиальны и не затрагивают остальную часть программы." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e5ede23-eba9-4735-ac83-667a82e31138", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}