{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9c9e480a-4ce9-4cfb-b0e0-dfbdc1a2ecc0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "tiny.txt:\n",
      "  BFSStrategy: 0.285 мс, посещено 72.0, путь 16.0\n",
      "  DFSStrategy: 0.267 мс, посещено 71.0, путь 72.0\n",
      "  AStarStrategy: 0.394 мс, посещено 72.0, путь 16.0\n",
      "Ошибка при обработке medium.txt: Лабиринт не имеет старта или выхода\n",
      "\n",
      "large.txt:\n",
      "  BFSStrategy: 4.952 мс, посещено 1275.0, путь 50.0\n",
      "  DFSStrategy: 0.216 мс, посещено 49.0, путь 50.0\n",
      "  AStarStrategy: 0.355 мс, посещено 50.0, путь 50.0\n",
      "\n",
      "empty.txt:\n",
      "  BFSStrategy: 24.334 мс, посещено 5049.0, путь 100.0\n",
      "  DFSStrategy: 0.557 мс, посещено 99.0, путь 100.0\n",
      "  AStarStrategy: 0.752 мс, посещено 100.0, путь 100.0\n",
      "\n",
      "no_exit.txt:\n",
      "  BFSStrategy: 1.265 мс, посещено 324.0, путь 0.0\n",
      "  DFSStrategy: 3.230 мс, посещено 324.0, путь 0.0\n",
      "  AStarStrategy: 2.224 мс, посещено 324.0, путь 0.0\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 70184 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import time\n",
    "import csv\n",
    "import heapq\n",
    "from collections import deque\n",
    "from abc import ABC, abstractmethod\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "from dataclasses import dataclass\n",
    "\n",
    "#  Этап 1: Модель лабиринта \n",
    "class Cell:\n",
    "    def __init__(self, x, y, is_wall=False):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "        self.is_wall = is_wall\n",
    "        self.is_start = False\n",
    "        self.is_exit = False\n",
    "\n",
    "    def is_passable(self):\n",
    "        return not self.is_wall\n",
    "\n",
    "\n",
    "class Maze:\n",
    "    def __init__(self, width, height):\n",
    "        self.width = width\n",
    "        self.height = height\n",
    "        self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]\n",
    "        self.start = None\n",
    "        self.exit = None\n",
    "\n",
    "    def get_cell(self, x, y):\n",
    "        if 0 <= x < self.width and 0 <= y < self.height:\n",
    "            return self.cells[y][x]\n",
    "        return None\n",
    "\n",
    "    def get_neighbors(self, cell):\n",
    "        neighbors = []\n",
    "        for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:\n",
    "            nx, ny = cell.x + dx, cell.y + dy\n",
    "            nb = self.get_cell(nx, ny)\n",
    "            if nb and nb.is_passable():\n",
    "                neighbors.append(nb)\n",
    "        return neighbors\n",
    "\n",
    "\n",
    "#  Этап 2: Загрузка из файла (Builder) \n",
    "class MazeBuilder(ABC):\n",
    "    @abstractmethod\n",
    "    def build_from_file(self, filename):\n",
    "        pass\n",
    "\n",
    "\n",
    "class TextFileMazeBuilder(MazeBuilder):\n",
    "    def build_from_file(self, filename):\n",
    "        with open(filename, 'r', encoding='utf-8') as f:\n",
    "            lines = [line.rstrip('\\n') for line in f.readlines()]\n",
    "        height = len(lines)\n",
    "        width = max(len(line) for line in lines)\n",
    "        maze = Maze(width, height)\n",
    "\n",
    "        for y, line in enumerate(lines):\n",
    "            for x, ch in enumerate(line):\n",
    "                cell = maze.get_cell(x, y)\n",
    "                if ch == '#':\n",
    "                    cell.is_wall = True\n",
    "                elif ch == 'S':\n",
    "                    cell.is_start = True\n",
    "                    maze.start = cell\n",
    "                elif ch == 'E':\n",
    "                    cell.is_exit = True\n",
    "                    maze.exit = cell\n",
    "                else:\n",
    "                    cell.is_wall = False\n",
    "        return maze\n",
    "\n",
    "\n",
    "#  Этап 3: Стратегии поиска (Strategy) \n",
    "class PathFindingStrategy(ABC):\n",
    "    @abstractmethod\n",
    "    def find_path(self, maze, start, exit):\n",
    "        pass\n",
    "\n",
    "\n",
    "class BFSStrategy(PathFindingStrategy):\n",
    "    def find_path(self, maze, start, exit):\n",
    "        visited = set()\n",
    "        if start == exit:\n",
    "            return [start], 1\n",
    "        queue = deque([start])\n",
    "        visited.add(start)\n",
    "        parent = {start: None}\n",
    "        while queue:\n",
    "            current = queue.popleft()\n",
    "            for nb in maze.get_neighbors(current):\n",
    "                if nb not in visited:\n",
    "                    visited.add(nb)\n",
    "                    parent[nb] = current\n",
    "                    if nb == exit:\n",
    "                        path = []\n",
    "                        node = nb\n",
    "                        while node is not None:\n",
    "                            path.append(node)\n",
    "                            node = parent[node]\n",
    "                        path.reverse()\n",
    "                        return path, len(visited)\n",
    "                    queue.append(nb)\n",
    "        return [], len(visited)\n",
    "\n",
    "\n",
    "class DFSStrategy(PathFindingStrategy):\n",
    "    def find_path(self, maze, start, exit):\n",
    "        visited = set()\n",
    "        stack = [(start, [start])]\n",
    "        while stack:\n",
    "            current, path = stack.pop()\n",
    "            if current == exit:\n",
    "                return path, len(visited)\n",
    "            visited.add(current)\n",
    "            for nb in maze.get_neighbors(current):\n",
    "                if nb not in visited:\n",
    "                    stack.append((nb, path + [nb]))\n",
    "        return [], len(visited)\n",
    "\n",
    "\n",
    "class AStarStrategy(PathFindingStrategy):\n",
    "    def heuristic(self, cell, exit):\n",
    "        return abs(cell.x - exit.x) + abs(cell.y - exit.y)\n",
    "\n",
    "    def find_path(self, maze, start, exit):\n",
    "        open_set = []\n",
    "        counter = 0\n",
    "        heapq.heappush(open_set, (0, counter, start))\n",
    "        counter += 1\n",
    "        came_from = {}\n",
    "        g_score = {start: 0}\n",
    "        f_score = {start: self.heuristic(start, exit)}\n",
    "        visited = set()\n",
    "        while open_set:\n",
    "            _, _, current = heapq.heappop(open_set)\n",
    "            visited.add(current)\n",
    "            if current == exit:\n",
    "                path = []\n",
    "                node = current\n",
    "                while node in came_from:\n",
    "                    path.append(node)\n",
    "                    node = came_from[node]\n",
    "                path.append(start)\n",
    "                path.reverse()\n",
    "                return path, len(visited)\n",
    "            for nb in maze.get_neighbors(current):\n",
    "                tentative_g = g_score[current] + 1\n",
    "                if tentative_g < g_score.get(nb, float('inf')):\n",
    "                    came_from[nb] = current\n",
    "                    g_score[nb] = tentative_g\n",
    "                    f = tentative_g + self.heuristic(nb, exit)\n",
    "                    heapq.heappush(open_set, (f, counter, nb))\n",
    "                    counter += 1\n",
    "        return [], len(visited)\n",
    "\n",
    "\n",
    "#  Этап 4: Оркестратор и статистика \n",
    "@dataclass\n",
    "class SearchStats:\n",
    "    time_ms: float\n",
    "    visited_cells: int\n",
    "    path_length: int\n",
    "    algorithm: str\n",
    "\n",
    "\n",
    "class MazeSolver:\n",
    "    def __init__(self, maze, strategy):\n",
    "        self.maze = maze\n",
    "        self.strategy = strategy\n",
    "\n",
    "    def set_strategy(self, strategy):\n",
    "        self.strategy = strategy\n",
    "\n",
    "    def solve(self):\n",
    "        if self.maze.start is None or self.maze.exit is None:\n",
    "            raise ValueError(\"Лабиринт не имеет старта или выхода\")\n",
    "        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\n",
    "\n",
    "\n",
    "#  Этап 6: Экспериментальная часть \n",
    "def test_single_maze(filename, strategies, repeats=5):\n",
    "    builder = TextFileMazeBuilder()\n",
    "    maze = builder.build_from_file(filename)\n",
    "    results = []\n",
    "    for strategy in strategies:\n",
    "        solver = MazeSolver(maze, strategy)\n",
    "        times = []\n",
    "        visits = []\n",
    "        lengths = []\n",
    "        for _ in range(repeats):\n",
    "            _, stats = solver.solve()\n",
    "            times.append(stats.time_ms)\n",
    "            visits.append(stats.visited_cells)\n",
    "            lengths.append(stats.path_length)\n",
    "        results.append({\n",
    "            'algorithm': strategy.__class__.__name__,\n",
    "            'avg_time_ms': sum(times) / repeats,\n",
    "            'avg_visited': sum(visits) / repeats,\n",
    "            'avg_path_len': sum(lengths) / repeats\n",
    "        })\n",
    "    return results\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    maze_files = [\n",
    "        \"tiny.txt\",\n",
    "        \"medium.txt\",\n",
    "        \"large.txt\",\n",
    "        \"empty.txt\",\n",
    "        \"no_exit.txt\"\n",
    "    ]\n",
    "    strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]\n",
    "    all_results = []\n",
    "\n",
    "    for maze_file in maze_files:\n",
    "        try:\n",
    "            results = test_single_maze(maze_file, strategies)\n",
    "            for r in results:\n",
    "                r['maze'] = maze_file\n",
    "                all_results.append(r)\n",
    "            # Краткий вывод результатов\n",
    "            print(f\"\\n{maze_file}:\")\n",
    "            for r in results:\n",
    "                print(f\"  {r['algorithm']}: {r['avg_time_ms']:.3f} мс, \"\n",
    "                      f\"посещено {r['avg_visited']:.1f}, путь {r['avg_path_len']:.1f}\")\n",
    "        except Exception as e:\n",
    "            print(f\"Ошибка при обработке {maze_file}: {e}\")\n",
    "\n",
    "    # Сохранение CSV\n",
    "    if all_results:\n",
    "        with open('all_results.csv', 'w', newline='', encoding='utf-8') as f:\n",
    "            writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited', 'avg_path_len'])\n",
    "            writer.writeheader()\n",
    "            writer.writerows(all_results)\n",
    "\n",
    "        # Построение графиков\n",
    "        df = pd.DataFrame(all_results)\n",
    "        for maze in df['maze'].unique():\n",
    "            subset = df[df['maze'] == maze]\n",
    "            plt.figure()\n",
    "            plt.bar(subset['algorithm'], subset['avg_time_ms'])\n",
    "            plt.title(f'Сравнение алгоритмов на лабиринте {maze}')\n",
    "            plt.ylabel('Среднее время (мс)')\n",
    "            plt.savefig(f'plot_{maze}.png')\n",
    "            plt.close()\n",
    "\n",
    "        plt.figure(figsize=(10, 6))\n",
    "        for alg in df['algorithm'].unique():\n",
    "            subset = df[df['algorithm'] == alg]\n",
    "            plt.plot(subset['maze'], subset['avg_time_ms'], marker='o', label=alg)\n",
    "        plt.xlabel('Лабиринт')\n",
    "        plt.ylabel('Среднее время (мс)')\n",
    "        plt.title('Сравнение эффективности алгоритмов на разных лабиринтах')\n",
    "        plt.legend()\n",
    "        plt.grid(True)\n",
    "        plt.savefig('summary_comparison.png')\n",
    "        plt.show()\n",
    "    else:\n",
    "        print(\"Нет данных для построения графиков. Проверьте файлы лабиринтов.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7d0ed68-a3a6-4db3-aaf5-1eeaa103838f",
   "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
}
