{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "204f9862-9a51-47be-bb5b-fcb099f9f707",
   "metadata": {},
   "source": [
    "# Отчёт по лабораторной работе: Поиск выхода из лабиринта (ООП + паттерны)\n",
    "\n",
    "## 1. Описание задачи\n",
    "\n",
    "Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. Требовалось применить минимум 3 паттерна проектирования GoF.\n",
    "\n",
    "**Исходные данные:**  \n",
    "- Лабиринты разной сложности (маленький, средний, большой, пустой, без выхода).  \n",
    "- Формат файла: `#` – стена, ` ` – проход, `S` – старт, `E` – выход.  \n",
    "- Алгоритмы поиска: BFS, DFS, A* (с манхэттенской эвристикой).\n",
    "\n",
    "**Цель эксперимента:** сравнить эффективность алгоритмов по времени, количеству посещённых клеток и длине найденного пути.\n",
    "\n",
    "---\n",
    "\n",
    "## 2. Выбранные паттерны проектирования\n",
    "\n",
    "### 2.1. Builder (строитель) – для загрузки лабиринта\n",
    "**Проблема:** создание лабиринта из файла требует нескольких шагов (чтение, парсинг, установка флагов).  \n",
    "**Решение:** интерфейс `MazeBuilder` и конкретная реализация `TextFileMazeBuilder` скрывают детали построения.  \n",
    "**Преимущество:** легко добавить новый формат (JSON, XML) без изменения остального кода.\n",
    "\n",
    "### 2.2. Strategy (стратегия) – для алгоритмов поиска\n",
    "**Проблема:** алгоритмы поиска (BFS, DFS, A*) взаимозаменяемы, но их реализация отличается.  \n",
    "**Решение:** интерфейс `PathFindingStrategy` и три класса-стратегии.  \n",
    "**Преимущество:** можно динамически менять алгоритм во время выполнения (например, через `MazeSolver.setStrategy()`).\n",
    "\n",
    "### 2.3. Observer (наблюдатель) – для логирования (опционально, но реализован)\n",
    "**Проблема:** нужно оповещать внешние компоненты о событиях поиска (начало, найден путь, ошибка).  \n",
    "**Решение:** интерфейс `Observer` и класс `ConsoleLogger`.  \n",
    "**Преимущество:** слабая связность – легко добавить другие наблюдатели (GUI, файл лога).\n",
    "\n",
    "### 2.4. Command (команда) – для пошагового движения (опционально, в демо)\n",
    "**Проблема:** требуется поддержка отмены ходов (undo).  \n",
    "**Решение:** интерфейс `Command`, класс `MoveCommand`, класс `Player`.  \n",
    "**Преимущество:** инкапсуляция запроса, возможность отмены, ведения истории.\n",
    "\n",
    "---\n",
    "\n",
    "## 3. Диаграмма классов (Mermaid)\n",
    "\n",
    "```mermaid\n",
    "classDiagram\n",
    "    class Cell {\n",
    "        -int x\n",
    "        -int y\n",
    "        -bool isWall\n",
    "        -bool isStart\n",
    "        -bool isExit\n",
    "        +isPassable()\n",
    "    }\n",
    "    class Maze {\n",
    "        -int width\n",
    "        -int height\n",
    "        -List[List[Cell]] cells\n",
    "        -Cell start\n",
    "        -Cell exit\n",
    "        +getCell(x,y)\n",
    "        +getNeighbors(cell)\n",
    "    }\n",
    "    class MazeBuilder {\n",
    "        <<interface>>\n",
    "        +buildFromFile(filename)\n",
    "    }\n",
    "    class TextFileMazeBuilder {\n",
    "        +buildFromFile(filename)\n",
    "    }\n",
    "    class PathFindingStrategy {\n",
    "        <<interface>>\n",
    "        +findPath(maze, start, exit)\n",
    "    }\n",
    "    class BFSStrategy\n",
    "    class DFSStrategy\n",
    "    class AStarStrategy\n",
    "    class SearchStats {\n",
    "        +float time_ms\n",
    "        +int visited_cells\n",
    "        +int path_length\n",
    "        +string algorithm\n",
    "    }\n",
    "    class MazeSolver {\n",
    "        -Maze maze\n",
    "        -PathFindingStrategy strategy\n",
    "        +setStrategy(strategy)\n",
    "        +solve() (path, stats)\n",
    "    }\n",
    "    class Observer {\n",
    "        <<interface>>\n",
    "        +update(event_type, data)\n",
    "    }\n",
    "    class ConsoleLogger {\n",
    "        +update(event_type, data)\n",
    "    }\n",
    "    class Command {\n",
    "        <<interface>>\n",
    "        +execute()\n",
    "        +undo()\n",
    "    }\n",
    "    class MoveCommand {\n",
    "        -Player player\n",
    "        -tuple direction\n",
    "        -Maze maze\n",
    "        -Cell prev_pos\n",
    "        +execute()\n",
    "        +undo()\n",
    "    }\n",
    "    class Player {\n",
    "        -Cell current_cell\n",
    "    }\n",
    "\n",
    "    MazeBuilder <|.. TextFileMazeBuilder\n",
    "    PathFindingStrategy <|.. BFSStrategy\n",
    "    PathFindingStrategy <|.. DFSStrategy\n",
    "    PathFindingStrategy <|.. AStarStrategy\n",
    "    MazeSolver --> PathFindingStrategy\n",
    "    MazeSolver --> Maze\n",
    "    MazeSolver --> SearchStats\n",
    "    Observer <|.. ConsoleLogger\n",
    "    MazeSolver --> Observer\n",
    "    Command <|.. MoveCommand\n",
    "    MoveCommand --> Player\n",
    "    MoveCommand --> Maze"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e671083-627f-4940-970f-80f8668388fb",
   "metadata": {},
   "source": [
    "## 4.1 Builder (загрузка лабиринта)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "bb983238-274f-4f19-b784-73be954a3aae",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'ABC' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mMazeBuilder\u001b[39;00m(ABC):\n\u001b[0;32m      2\u001b[0m     \u001b[38;5;129m@abstractmethod\u001b[39m\n\u001b[0;32m      3\u001b[0m     \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mbuild_from_file\u001b[39m(\u001b[38;5;28mself\u001b[39m, filename):\n\u001b[0;32m      4\u001b[0m         \u001b[38;5;28;01mpass\u001b[39;00m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'ABC' is not defined"
     ]
    }
   ],
   "source": [
    "class MazeBuilder(ABC):\n",
    "    @abstractmethod\n",
    "    def build_from_file(self, filename):\n",
    "        pass\n",
    "\n",
    "class TextFileMazeBuilder(MazeBuilder):\n",
    "    def build_from_file(self, filename):\n",
    "        with open(filename, 'r') as f:\n",
    "            lines = [line.rstrip('\\n') for line in f]\n",
    "        height = len(lines)\n",
    "        width = max(len(line) for line in lines)\n",
    "        maze = Maze(width, height)\n",
    "        for y, line in enumerate(lines):\n",
    "            for x, ch in enumerate(line):\n",
    "                cell = maze.get_cell(x, y)\n",
    "                if ch == '#': cell.is_wall = True\n",
    "                elif ch == 'S': cell.is_start = True; maze.start = cell\n",
    "                elif ch == 'E': cell.is_exit = True; maze.exit = cell\n",
    "                else: cell.is_wall = False\n",
    "        return maze"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f20c327f-b8f7-40f4-a71f-eede64d5dedf",
   "metadata": {},
   "source": [
    "## 4.2. Стратегии поиска (BFS, DFS, A*)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5fb0715-9749-404d-870d-0a83c0025eac",
   "metadata": {},
   "outputs": [],
   "source": [
    "class BFSStrategy(PathFindingStrategy):\n",
    "    def find_path(self, maze, start, exit):\n",
    "        queue = deque([start])\n",
    "        visited = {start}\n",
    "        parent = {start: None}\n",
    "        while queue:\n",
    "            cur = queue.popleft()\n",
    "            if cur == exit:\n",
    "                path = []\n",
    "                while cur:\n",
    "                    path.append(cur)\n",
    "                    cur = parent[cur]\n",
    "                return path[::-1], len(visited)\n",
    "            for nb in maze.get_neighbors(cur):\n",
    "                if nb not in visited:\n",
    "                    visited.add(nb)\n",
    "                    parent[nb] = cur\n",
    "                    queue.append(nb)\n",
    "        return [], len(visited)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bae1222c-07f6-487d-ac05-7d09d7086e67",
   "metadata": {},
   "source": [
    "## 4.3. Оркестратор MazeSolver и статистика"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "247f19cf-4811-4dd8-8e35-03af6550d202",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MazeSolver:\n",
    "    def solve(self):\n",
    "        start_time = time.perf_counter()\n",
    "        path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)\n",
    "        end_time = time.perf_counter()\n",
    "        stats = SearchStats(\n",
    "            time_ms=(end_time - start_time)*1000,\n",
    "            visited_cells=visited,\n",
    "            path_length=len(path),\n",
    "            algorithm=self.strategy.__class__.__name__\n",
    "        )\n",
    "        return path, stats"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a7167d5-efd8-4b3c-a14e-5b6c1cba83ef",
   "metadata": {},
   "source": [
    "## 5. Результаты экспериментов\n",
    "\n",
    "**Тестовые лабиринты:**\n",
    "- `tiny.txt` – 10×10, простой путь\n",
    "- `medium.txt` – 50×50, с тупиками\n",
    "- `large.txt` – 100×100, запутанный\n",
    "- `empty.txt` – 100×100, без стен (старт в левом верхнем углу, выход в правом нижнем)\n",
    "- `no_exit.txt` – лабиринт без выхода\n",
    "\n",
    "> **Примечание:** средний лабиринт (`medium.txt`) не был включён в замеры из-за отсутствия корректного файла. Остальные четыре лабиринта соответствуют заданию.\n",
    "\n",
    "**Методика:** каждый алгоритм запущен 5 раз на каждом лабиринте, значения усреднены. Данные получены из `all_results.csv`.\n",
    "\n",
    "### 5.1. Таблица результатов\n",
    "\n",
    "| Лабиринт       | Алгоритм       | Время (мс) | Посещено клеток | Длина пути |\n",
    "|----------------|----------------|------------|-----------------|------------|\n",
    "| tiny.txt       | BFSStrategy    | 0.2854     | 72.0            | 16.0       |\n",
    "| tiny.txt       | DFSStrategy    | 0.2665     | 71.0            | 72.0       |\n",
    "| tiny.txt       | AStarStrategy  | 0.3941     | 72.0            | 16.0       |\n",
    "| large.txt      | BFSStrategy    | 4.9520     | 1275.0          | 50.0       |\n",
    "| large.txt      | DFSStrategy    | 0.2159     | 49.0            | 50.0       |\n",
    "| large.txt      | AStarStrategy  | 0.3549     | 50.0            | 50.0       |\n",
    "| empty.txt      | BFSStrategy    | 24.3337    | 5049.0          | 100.0      |\n",
    "| empty.txt      | DFSStrategy    | 0.5570     | 99.0            | 100.0      |\n",
    "| empty.txt      | AStarStrategy  | 0.7525     | 100.0           | 100.0      |\n",
    "| no_exit.txt    | BFSStrategy    | 1.2649     | 324.0           | 0.0        |\n",
    "| no_exit.txt    | DFSStrategy    | 3.2304     | 324.0           | 0.0        |\n",
    "| no_exit.txt    | AStarStrategy  | 2.2239     | 324.0           | 0.0        |\n",
    "\n",
    "### 5.2. Графики\n",
    "\n",
    "Графики для каждого по отдельности сохранены в файле, тут предоставляю общее сравнение\n",
    "![Сводный график](summary_comparison.png)\n",
    "\n",
    "---\n",
    "\n",
    "## 6. Анализ эффективности алгоритмов\n",
    "\n",
    "### 6.1. Время выполнения\n",
    "- На **tiny.txt** все алгоритмы показали близкое время (~0.2–0.4 мс); DFS незначительно быстрее.\n",
    "- На **large.txt** BFS значительно медленнее (4.95 мс) из-за равномерного обхода, а DFS и A* работают быстро (~0.2–0.35 мс).\n",
    "- На **empty.txt** BFS крайне медленен (24.3 мс), поскольку вынужден обойти почти всё поле; DFS и A* справляются за ~0.5–0.75 мс.\n",
    "- В лабиринте **no_exit.txt** BFS быстрее (1.26 мс), а DFS и A* медленнее (2.2–3.2 мс) из-за разных порядков обхода.\n",
    "\n",
    "### 6.2. Количество посещённых клеток\n",
    "- **BFS** на `empty.txt` посещает 5049 клеток (почти половину поля), тогда как DFS и A* – всего ~100 клеток.\n",
    "- В `large.txt` BFS посещает 1275 клеток, DFS – 49, A* – 50. Это показывает, что A* (как и DFS) находит путь, исследуя лишь узкую область.\n",
    "- В `tiny.txt` все алгоритмы посещают около 70 клеток (различия незначительны).\n",
    "\n",
    "### 6.3. Длина найденного пути\n",
    "- **BFS** и **A*** находят кратчайший путь (16 шагов в tiny.txt, 50 в large.txt, 100 в empty.txt).\n",
    "- **DFS** в tiny.txt даёт очень длинный путь (72 шага вместо 16), в large.txt – 50 (совпал с оптимальным), в empty.txt – 100 (оптимально).\n",
    "- Таким образом, DFS не гарантирует кратчайший путь, хотя в некоторых случаях может его найти.\n",
    "\n",
    "### 6.4. Лабиринт без выхода\n",
    "- Все алгоритмы исследуют всю достижимую область (324 клетки).  \n",
    "- Время различается: BFS – 1.26 мс, DFS – 3.23 мс, A* – 2.22 мс. Это связано с тем, что DFS «закапывается» в глубину, а A* тратит время на поддержание очереди с приоритетами.\n",
    "\n",
    "---\n",
    "\n",
    "## 7. Применимость паттернов и гибкость архитектуры\n",
    "\n",
    "### 7.1. Паттерн Builder\n",
    "- **Без него:** код загрузки был бы прямо в `main` или в конструкторе `Maze`. Пришлось бы переписывать при добавлении нового формата.\n",
    "- **С ним:** легко добавить `JSONMazeBuilder`, заменив всего одну строку в клиентском коде.\n",
    "\n",
    "### 7.2. Паттерн Strategy\n",
    "- **Без него:** пришлось бы использовать громоздкие `if-elif` для выбора алгоритма, дублировать код замера времени.\n",
    "- **С ним:** алгоритмы полностью независимы, можно динамически менять стратегию (например, на основе размера лабиринта).\n",
    "\n",
    "### 7.3. Паттерн Observer (опционально)\n",
    "- **Без него:** логирование и визуализация были бы вплетены в алгоритмы поиска.\n",
    "- **С ним:** наблюдатели подписываются на события, и логирование можно отключить или заменить на другой вывод без изменения `MazeSolver`.\n",
    "\n",
    "### 7.4. Паттерн Command (для пошагового движения)\n",
    "- **Без него:** отмена хода пришлось бы реализовывать вручную, что привело бы к дублированию кода.\n",
    "- **С ним:** команды легко складывать в историю, реализовать `undo` и `redo`.\n",
    "\n",
    "---\n",
    "\n",
    "## 8. Выводы\n",
    "\n",
    "- **ООП и паттерны проектирования** позволили создать гибкую, расширяемую и легко тестируемую программу.\n",
    "- **Builder** упростил добавление новых форматов лабиринтов.\n",
    "- **Strategy** сделал алгоритмы поиска взаимозаменяемыми и позволил проводить честное сравнение.\n",
    "- **Observer** и **Command** добавили возможности логирования и отмены действий без «засорения» основной логики.\n",
    "- Экспериментально подтверждены теоретические свойства алгоритмов: A* – лучший выбор для большинства случаев (оптимальный путь и высокая скорость), BFS – оптимален по длине пути, но медленен на больших картах, DFS – прост, но не даёт гарантий кратчайшего пути.\n",
    "\n",
    "**Итог:** использование паттернов повысило качество кода, уменьшило связанность и облегчило поддержку. Без них любое изменение (добавление нового алгоритма или формата файла) потребовало бы правки многих классов.\n",
    "\n",
    "--- \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e634d95-e0c0-4b8a-a330-02463cd085c8",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
