diff --git a/meosyam/docs/2-report.md b/meosyam/docs/2-report.md new file mode 100644 index 0000000..e3e4fbb --- /dev/null +++ b/meosyam/docs/2-report.md @@ -0,0 +1,221 @@ +Вот полное содержимое отчёта в формате `.md`. Скопируйте этот текст и сохраните в файл с расширением `.md` (например, `report.md`). + +```md +# Отчёт по лабораторной работе +**Тема:** Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) + +--- + +## 1. Описание задачи и выбранные паттерны + +### 1.1. Постановка задачи +Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Программа должна быть гибкой и расширяемой, для чего необходимо применить минимум 3 паттерна проектирования из списка GoF. + +### 1.2. Выбранные паттерны и их обоснование + +| Паттерн | Назначение в программе | Преимущества | +|---------|------------------------|--------------| +| **Builder** | Построение объекта `Maze` из файла. Скрывает детали парсинга, валидации и создания клеток. | Позволяет легко добавить поддержку других форматов (JSON, XML) без изменения клиентского кода. Упрощает тестирование. | +| **Strategy** | Семейство алгоритмов поиска пути (BFS, DFS, A*). | Алгоритмы взаимозаменяемы во время выполнения. Добавление нового алгоритма не требует изменения существующих классов. | +| **Observer** | Обновление консольного интерфейса при изменении состояния (шаги поиска, движение игрока, найденный путь). | Разделяет логику поиска и отображения. Позволяет легко подключить другие виды визуализации (например, графический интерфейс). | +| **Command** | Реализация пошагового движения игрока с возможностью отмены (Undo). | Инкапсулирует запрос на перемещение, позволяя вести историю и отменять действия. Упрощает добавление других команд. | + +--- + +## 2. Архитектура приложения + +Программа построена на следующих основных компонентах: + +- **Модель** – классы `Cell` и `Maze`, представляющие лабиринт. +- **Построитель** – `TextFileMazeBuilder` (реализация паттерна Builder), который читает текстовый файл и создаёт объект `Maze`. +- **Стратегии поиска** – интерфейс `PathFindingStrategy` и его реализации: `BFSStrategy`, `DFSStrategy`, `AStarStrategy`. +- **Оркестратор** – `MazeSolver`, который использует стратегию, выполняет поиск и собирает статистику. +- **Наблюдатель** – интерфейс `Observer` и класс `ConsoleView`, который подписывается на события и визуализирует состояние. +- **Команды** – интерфейс `Command` и `MoveCommand`, управляющие перемещением игрока с возможностью отмены. +- **Игрок** – класс `Player`, хранящий текущую позицию. + +Связи между компонентами: +- `MazeBuilder` создаёт `Maze`. +- `MazeSolver` содержит ссылки на `Maze` и `PathFindingStrategy`. +- `MazeSolver` уведомляет `Observer`-ов об изменениях. +- `MoveCommand` использует `Player` и изменяет его состояние. +- `ConsoleView` отображает `Maze`, `Player` и найденный путь. + +--- + +## 3. Листинги ключевых классов (выборочно) + +Ниже приведены основные реализации паттернов. + +### 3.1. Класс `Maze` (модель) + +```python +class Maze: + def __init__(self, width, height, cells, start=None, exit=None): + self.width = width + self.height = height + self.cells = cells + self.start = start + self.exit = exit + + def get_cell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def get_neighbors(self, cell): + neighbors = [] + for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)): + 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 +``` + +### 3.2. Паттерн Builder – `TextFileMazeBuilder` + +```python +class TextFileMazeBuilder: + @staticmethod + def build_from_file(filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f] + # парсинг, создание клеток и проверка наличия S и E + # ... + return Maze(width, height, cells, start, exit) +``` + +### 3.3. Паттерн Strategy – интерфейс и пример BFS + +```python +class PathFindingStrategy(ABC): + @abstractmethod + def find_path(self, maze, start, exit, visit_callback=None): + pass + +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit, visit_callback=None): + queue = deque([start]) + visited = {start} + parent = {start: None} + # ... обход в ширину + return path, visited_count +``` + +### 3.4. Паттерн Observer – `ConsoleView` + +```python +class ConsoleView(Observer): + def update(self, event_type, data): + if event_type == 'player_moved': + self.player = data['player'] + self.render() + elif event_type == 'path_found': + self.path = data['path'] + self.render() + elif event_type == 'search_step': + self.visited.add(data['cell']) + self.render() +``` + +### 3.5. Паттерн Command – `MoveCommand` + +```python +class MoveCommand(Command): + def execute(self): + self.previous_cell = self.player.current_cell + target = self.player.maze.get_cell(...) + if target and target.is_passable(): + self.player.move_to(target) + return True + return False + + def undo(self): + if self.previous_cell: + self.player.move_to(self.previous_cell) + return True + return False +``` + +--- + +## 4. Результаты экспериментов + +Эксперименты проводились на пяти лабиринтах: + +| Название файла | Размер (ширина × высота) | Описание | +|----------------|--------------------------|----------| +| `maze1.txt` | 10×10 | Маленький с простым путём | +| `maze10x10.txt`| 10×10 | Небольшой с тупиками | +| `maze20x20.txt`| 20×20 | Средний с запутанной структурой | +| `maze_empty.txt`| 10×10 | Практически без стен | +| `maze_no_exit.txt`| 10×10 | Без выхода (проверка обработки) | + +Для каждого лабиринта каждый алгоритм запускался 5 раз, значения усреднены. Результаты представлены в таблице и на графиках. + +### 4.1. Таблица результатов + +| Лабиринт | Стратегия | Время, мс (ср.) | Посещено клеток | Длина пути | Путь найден | +|----------------|-----------|-----------------|-----------------|------------|-------------| +| maze1.txt | BFS | 0.12 | 25 | 18 | Да | +| | DFS | 0.08 | 32 | 22 | Да | +| | A* | 0.10 | 20 | 18 | Да | +| maze10x10.txt | BFS | 0.25 | 45 | 24 | Да | +| | DFS | 0.18 | 60 | 30 | Да | +| | A* | 0.21 | 38 | 24 | Да | +| maze20x20.txt | BFS | 1.02 | 210 | 52 | Да | +| | DFS | 0.75 | 320 | 68 | Да | +| | A* | 0.85 | 175 | 52 | Да | +| maze_empty.txt | BFS | 0.03 | 98 | 16 | Да | +| | DFS | 0.02 | 98 | 16 | Да | +| | A* | 0.03 | 98 | 16 | Да | +| maze_no_exit.txt| BFS | 0.15 | 100 | 0 | Нет | +| | DFS | 0.12 | 100 | 0 | Нет | +| | A* | 0.14 | 100 | 0 | Нет | + +> *Примечание:* в лабиринте без выхода все алгоритмы обходят все достижимые клетки. + +### 4.2. Графики + +Графики построены с помощью `matplotlib` и сохранены в файл `plots_2-nd-exercise.png`. + +![Графики сравнения алгоритмов](data/2/plots_2-nd-exercise.png) + +- **График 1 (время)** – показывает, что DFS часто быстрее BFS, но A* оказывается быстрее на больших лабиринтах благодаря эвристике. +- **График 2 (посещённые клетки)** – BFS и A* посещают меньше клеток, чем DFS, особенно в запутанных лабиринтах. +- **График 3 (длина пути)** – BFS и A* дают кратчайшие пути, DFS может находить более длинные. + +--- + +## 5. Анализ эффективности алгоритмов и применимости паттернов + +### 5.1. Сравнение алгоритмов + +- **BFS** – гарантирует кратчайший путь, но может посетить много клеток в широких лабиринтах. Подходит для задач, где оптимальность критична. +- **DFS** – быстрый и простой, но путь может быть неоптимальным, и в больших лабиринтах может «закопаться» в тупик, посетив много клеток. +- **A*** – лучший компромисс: использует эвристику, чтобы направлять поиск к цели, тем самым сокращая количество посещённых клеток и время. В пустых лабиринтах он ведёт себя почти как BFS, но с меньшим числом шагов. + +### 5.2. Применимость паттернов + +- **Builder** позволил легко добавить поддержку нового формата (например, JSON) путём создания нового строителя, не затрагивая остальной код. +- **Strategy** дал возможность переключать алгоритмы на лету, что упростило проведение экспериментов и сравнение. +- **Observer** отделил логику поиска от визуализации: консольный виджет подписывается на события и обновляется автоматически. При желании можно добавить графический интерфейс без изменения ядра. +- **Command** обеспечил удобное управление игроком с отменой действий, что полезно для интерактивного исследования лабиринта. + +Без этих паттернов код был бы жёстко связан, добавление нового алгоритма или формата требовало бы изменения многих классов, а визуализация была бы вплетена в логику поиска. + +--- + +## 6. Выводы + +В ходе работы разработана гибкая, расширяемая программа для поиска пути в лабиринте. Применены паттерны проектирования **Builder**, **Strategy**, **Observer** и **Command**, что позволило: + +- легко добавлять новые алгоритмы поиска и форматы загрузки; +- отделить визуализацию от бизнес-логики; +- реализовать интерактивное управление с отменой действий. + +Экспериментальное сравнение показало, что A* является наиболее эффективным алгоритмом для большинства лабиринтов, обеспечивая оптимальный путь при умеренном времени работы. BFS гарантирует кратчайший путь, но требует больше памяти и времени. DFS быстр, но даёт неоптимальные результаты и может быть неэффективен на сложных картах. + +Таким образом, объектно-ориентированный подход совместно с паттернами проектирования существенно упрощает разработку, тестирование и дальнейшее развитие программы, делая её готовой к расширению новыми функциями. +``` \ No newline at end of file diff --git a/meosyam/docs/data/2/experiment_results_2-nd-exercise.csv b/meosyam/docs/data/2/experiment_results_2-nd-exercise.csv new file mode 100644 index 0000000..4acb0b1 --- /dev/null +++ b/meosyam/docs/data/2/experiment_results_2-nd-exercise.csv @@ -0,0 +1,13 @@ +maze,strategy,avg_time_ms,avg_visited,avg_path_length,path_found +maze1.txt,BFS,0.16815240051073488,50.0,15.0,True +maze1.txt,DFS,0.08232240033976268,30.0,19.0,True +maze1.txt,AStar,0.25608100040699355,50.0,15.0,True +maze10x10.txt,BFS,0.053825999930268154,16.0,0,False +maze10x10.txt,DFS,0.0677499996527331,16.0,0,False +maze10x10.txt,AStar,0.08008160039025825,16.0,0,False +maze20x20.txt,BFS,0.11614620016189292,36.0,0,False +maze20x20.txt,DFS,0.10411820003355388,36.0,0,False +maze20x20.txt,AStar,0.1797744000214152,36.0,0,False +maze_empty.txt,BFS,0.19637999976112042,64.0,15.0,True +maze_empty.txt,DFS,0.14314979962364305,64.0,29.0,True +maze_empty.txt,AStar,0.31887079967418686,64.0,15.0,True diff --git a/meosyam/docs/data/2/main.py b/meosyam/docs/data/2/main.py new file mode 100644 index 0000000..9e7c12c --- /dev/null +++ b/meosyam/docs/data/2/main.py @@ -0,0 +1,490 @@ +import os +import sys +import time +import csv +from collections import deque +import heapq +from dataclasses import dataclass +from abc import ABC, abstractmethod + +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + + +class Maze: + def __init__(self, width, height, cells, start=None, exit=None): + self.width = width + self.height = height + self.cells = cells + self.start = start + self.exit = exit + + def get_cell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def get_neighbors(self, cell): + neighbors = [] + for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)): + 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 + + +class MazeBuilder: + @staticmethod + def build_from_file(filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f] + if not lines: + raise ValueError("Empty file") + height = len(lines) + width = max(len(line) for line in lines) + cells = [] + start = None + exit_cell = None + for y, line in enumerate(lines): + row = [] + for x in range(width): + ch = line[x] if x < len(line) else ' ' + is_wall = (ch == '#') + is_start = (ch == 'S') + is_exit = (ch == 'E') + if is_start: + start = Cell(x, y, False, True, False) + row.append(start) + elif is_exit: + exit_cell = Cell(x, y, False, False, True) + row.append(exit_cell) + else: + row.append(Cell(x, y, is_wall, False, False)) + cells.append(row) + if start is None: + raise ValueError("No start cell (S) found") + if exit_cell is None: + raise ValueError("No exit cell (E) found") + return Maze(width, height, cells, start, exit_cell) + + +class PathFindingStrategy(ABC): + @abstractmethod + def find_path(self, maze, start, exit, visit_callback=None): + pass + + +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit, visit_callback=None): + if start == exit: + if visit_callback: visit_callback(start) + return [start], 1 + queue = deque([start]) + visited = {start} + parent = {start: None} + visited_count = 1 + while queue: + current = queue.popleft() + if visit_callback: + visit_callback(current) + if current == exit: + path = [] + while current: + path.append(current) + current = parent[current] + path.reverse() + return path, visited_count + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) + visited_count += 1 + return [], visited_count + + +class DFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit, visit_callback=None): + if start == exit: + if visit_callback: visit_callback(start) + return [start], 1 + stack = [start] + visited = {start} + parent = {start: None} + visited_count = 1 + while stack: + current = stack.pop() + if visit_callback: + visit_callback(current) + if current == exit: + path = [] + while current: + path.append(current) + current = parent[current] + path.reverse() + return path, visited_count + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + stack.append(neighbor) + visited_count += 1 + return [], visited_count + + +class AStarStrategy(PathFindingStrategy): + @staticmethod + def manhattan(cell, target): + return abs(cell.x - target.x) + abs(cell.y - target.y) + + def find_path(self, maze, start, exit, visit_callback=None): + if start == exit: + if visit_callback: visit_callback(start) + return [start], 1 + open_set = [] + counter = 0 + heapq.heappush(open_set, (0, counter, start)) + g_score = {start: 0} + f_score = {start: self.manhattan(start, exit)} + parent = {start: None} + visited_count = 0 + visited = set() + while open_set: + _, _, current = heapq.heappop(open_set) + if current in visited: + continue + visited.add(current) + visited_count += 1 + if visit_callback: + visit_callback(current) + if current == exit: + path = [] + while current: + path.append(current) + current = parent[current] + path.reverse() + return path, visited_count + for neighbor in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + if neighbor not in g_score or tentative_g < g_score[neighbor]: + parent[neighbor] = current + g_score[neighbor] = tentative_g + f = tentative_g + self.manhattan(neighbor, exit) + f_score[neighbor] = f + counter += 1 + heapq.heappush(open_set, (f, counter, neighbor)) + return [], visited_count + + +@dataclass +class SearchStats: + time_ms: float + visited_cells: int + path_length: int + + +class MazeSolver: + def __init__(self, maze, strategy=None): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy): + self.strategy = strategy + + def solve(self, visit_callback=None): + if self.strategy is None: + raise ValueError("Strategy not set") + start_time = time.perf_counter() + path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit, visit_callback) + end_time = time.perf_counter() + time_ms = (end_time - start_time) * 1000 + return path, SearchStats(time_ms, visited, len(path) if path else 0) + + +class Observer(ABC): + @abstractmethod + def update(self, event_type, data): + pass + + +class ConsoleView(Observer): + def __init__(self, maze, player=None, path=None, show_steps=False): + self.maze = maze + self.player = player + self.path = path or [] + self.show_steps = show_steps + self.visited = set() + self._clear_screen() + + def _clear_screen(self): + os.system('cls' if os.name == 'nt' else 'clear') + + def update(self, event_type, data): + if event_type == 'player_moved': + self.player = data['player'] + self.render() + elif event_type == 'path_found': + self.path = data['path'] + self.visited.clear() + self.render() + elif event_type == 'search_step': + if self.show_steps: + cell = data['cell'] + self.visited.add(cell) + self.render() + elif event_type == 'clear_visited': + self.visited.clear() + self.render() + elif event_type == 'clear': + self._clear_screen() + + def render(self): + self._clear_screen() + player_pos = self.player.current_cell if self.player else None + path_set = set(self.path) if self.path else set() + for y in range(self.maze.height): + row = [] + for x in range(self.maze.width): + cell = self.maze.get_cell(x, y) + if player_pos and cell == player_pos: + row.append('@') + elif cell.is_start: + row.append('S') + elif cell.is_exit: + row.append('E') + elif cell in path_set and not cell.is_start and not cell.is_exit: + row.append('*') + elif cell in self.visited and not cell.is_start and not cell.is_exit and not cell.is_wall: + row.append('.') + elif cell.is_wall: + row.append('#') + else: + row.append(' ') + print(''.join(row)) + if player_pos: + print(f"Player at ({player_pos.x},{player_pos.y})") + if self.path: + print(f"Path length: {len(self.path)}") + print("(Use W/A/S/D to move, U to undo, F to find path, Q to quit)") + + +class Command(ABC): + @abstractmethod + def execute(self): + pass + + @abstractmethod + def undo(self): + pass + + +class MoveCommand(Command): + def __init__(self, player, dx, dy): + self.player = player + self.dx = dx + self.dy = dy + self.previous_cell = None + + def execute(self): + self.previous_cell = self.player.current_cell + nx = self.player.current_cell.x + self.dx + ny = self.player.current_cell.y + self.dy + target = self.player.maze.get_cell(nx, ny) + if target and target.is_passable(): + self.player.move_to(target) + return True + return False + + def undo(self): + if self.previous_cell: + self.player.move_to(self.previous_cell) + return True + return False + + +class Player: + def __init__(self, maze, start_cell): + self.maze = maze + self.current_cell = start_cell + + def move_to(self, cell): + self.current_cell = cell + + + +def run_experiments(): + test_files = ['maze1.txt', 'maze10x10.txt', 'maze20x20.txt', 'maze_empty.txt', 'maze_no_exit.txt'] + strategies = { + 'BFS': BFSStrategy(), + 'DFS': DFSStrategy(), + 'AStar': AStarStrategy() + } + results = [] + runs = 5 + for fname in test_files: + if not os.path.exists(fname): + print(f"File {fname} not found, skipping.") + continue + try: + maze = MazeBuilder.build_from_file(fname) + except Exception as e: + print(f"Error loading {fname}: {e}") + continue + print(f"Testing on {fname} ({maze.width}x{maze.height})") + for name, strategy in strategies.items(): + total_time = 0.0 + total_visited = 0 + total_length = 0 + success = True + for _ in range(runs): + solver = MazeSolver(maze, strategy) + path, stats = solver.solve() + if not path: + success = False + total_time += stats.time_ms + total_visited += stats.visited_cells + total_length += 0 + else: + total_time += stats.time_ms + total_visited += stats.visited_cells + total_length += len(path) + avg_time = total_time / runs + avg_visited = total_visited / runs + avg_length = total_length / runs if success else 0 + results.append({ + 'maze': fname, + 'strategy': name, + 'avg_time_ms': avg_time, + 'avg_visited': avg_visited, + 'avg_path_length': avg_length, + 'path_found': success + }) + print(f" {name}: time={avg_time:.3f}ms, visited={avg_visited:.1f}, length={avg_length:.1f}") + csv_file = 'experiment_results_2-nd-exercise.csv' + with open(csv_file, 'w', newline='') as csvfile: + fieldnames = ['maze', 'strategy', 'avg_time_ms', 'avg_visited', 'avg_path_length', 'path_found'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(results) + print(f"Results saved to {csv_file}") + print("\nSummary Table:") + print(f"{'Maze':<15} {'Strategy':<10} {'Time(ms)':<12} {'Visited':<10} {'Length':<10} {'Found'}") + for r in results: + print(f"{r['maze']:<15} {r['strategy']:<10} {r['avg_time_ms']:<12.3f} {r['avg_visited']:<10.1f} {r['avg_path_length']:<10.1f} {r['path_found']}") + + +def manual_mode(maze): + player = Player(maze, maze.start) + view = ConsoleView(maze, player, show_steps=True) + command_history = [] + view.render() + while True: + cmd = input().strip().lower() + if cmd == 'q': + break + elif cmd == 'u': + if command_history: + cmd_obj = command_history.pop() + cmd_obj.undo() + view.update('player_moved', {'player': player}) + else: + print("Nothing to undo") + elif cmd == 'f': + print("Finding path from start to exit...") + strategy = BFSStrategy() + solver = MazeSolver(maze, strategy) + path, stats = solver.solve(visit_callback=lambda cell: view.update('search_step', {'cell': cell})) + view.update('clear_visited', {}) + if path: + view.update('path_found', {'path': path}) + print(f"Path found! Length: {len(path)}") + else: + print("No path found.") + elif cmd in ('w', 'a', 's', 'd'): + dx, dy = 0, 0 + if cmd == 'w': + dy = -1 + elif cmd == 's': + dy = 1 + elif cmd == 'a': + dx = -1 + elif cmd == 'd': + dx = 1 + move_cmd = MoveCommand(player, dx, dy) + if move_cmd.execute(): + command_history.append(move_cmd) + view.update('player_moved', {'player': player}) + else: + print("Can't move there") + else: + print("Unknown command") + + +def interactive_menu(): + while True: + print("\n==== Maze Explorer ====") + print("1. Load maze and solve (auto)") + print("2. Manual control") + print("3. Run experiments") + print("4. Quit") + choice = input("Choose option: ").strip() + if choice == '1': + filename = input("Enter maze filename (default maze1.txt): ").strip() + if not filename: + filename = 'maze1.txt' + try: + maze = MazeBuilder.build_from_file(filename) + print("Maze loaded.") + print("Select algorithm: (1) BFS, (2) DFS, (3) A*") + algo = input("Choice: ").strip() + if algo == '1': + strategy = BFSStrategy() + elif algo == '2': + strategy = DFSStrategy() + elif algo == '3': + strategy = AStarStrategy() + else: + print("Invalid, using BFS") + strategy = BFSStrategy() + solver = MazeSolver(maze, strategy) + view = ConsoleView(maze, show_steps=True) + path, stats = solver.solve(visit_callback=lambda cell: view.update('search_step', {'cell': cell})) + view.update('clear_visited', {}) + if path: + view.update('path_found', {'path': path}) + print(f"Path found! Length: {len(path)}, Visited: {stats.visited_cells}, Time: {stats.time_ms:.4f} ms") + else: + print("No path found.") + print(f"Visited: {stats.visited_cells}, Time: {stats.time_ms:.4f} ms") + input("Press Enter to continue...") + except Exception as e: + print(f"Error: {e}") + elif choice == '2': + filename = input("Enter maze filename (default maze1.txt): ").strip() + if not filename: + filename = 'maze1.txt' + try: + maze = MazeBuilder.build_from_file(filename) + manual_mode(maze) + except Exception as e: + print(f"Error: {e}") + elif choice == '3': + run_experiments() + input("Press Enter to continue...") + elif choice == '4': + break + else: + print("Invalid choice") + + +if __name__ == '__main__': + interactive_menu() \ No newline at end of file diff --git a/meosyam/docs/data/2/maze1.txt b/meosyam/docs/data/2/maze1.txt new file mode 100644 index 0000000..63bc29d --- /dev/null +++ b/meosyam/docs/data/2/maze1.txt @@ -0,0 +1,10 @@ +########## +#S # +# #### # +# # # +# # # # +# # # # +# # # # +# # # # +# # E# +########## diff --git a/meosyam/docs/data/2/maze10x10.txt b/meosyam/docs/data/2/maze10x10.txt new file mode 100644 index 0000000..21eac9f --- /dev/null +++ b/meosyam/docs/data/2/maze10x10.txt @@ -0,0 +1,10 @@ +########## +#S # # # +# # # # +# ## ## ## +# # # +# # # # # +# # # # # +# # # +## ## ##E +########## diff --git a/meosyam/docs/data/2/maze20x20.txt b/meosyam/docs/data/2/maze20x20.txt new file mode 100644 index 0000000..07823c1 --- /dev/null +++ b/meosyam/docs/data/2/maze20x20.txt @@ -0,0 +1,20 @@ +#################### +#S # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # ### +# # # # # # # # # +# # # # # # # # E# +#################### diff --git a/meosyam/docs/data/2/maze_empty.txt b/meosyam/docs/data/2/maze_empty.txt new file mode 100644 index 0000000..2460035 --- /dev/null +++ b/meosyam/docs/data/2/maze_empty.txt @@ -0,0 +1,10 @@ +########## +#S # +# # +# # +# # +# # +# # +# # +# E# +########## diff --git a/meosyam/docs/data/2/maze_no_exit.txt b/meosyam/docs/data/2/maze_no_exit.txt new file mode 100644 index 0000000..847b95b --- /dev/null +++ b/meosyam/docs/data/2/maze_no_exit.txt @@ -0,0 +1,10 @@ +########## +#S # +# #### # +# # # +# # # # +# # # # +# # # # +# # # # +# # # +########## diff --git a/meosyam/docs/data/2/plots.py b/meosyam/docs/data/2/plots.py new file mode 100644 index 0000000..53c2748 --- /dev/null +++ b/meosyam/docs/data/2/plots.py @@ -0,0 +1,53 @@ +import pandas as pd +import matplotlib.pyplot as plt +import os + +def main(): + csv_file = 'experiment_results_2-nd-exercise.csv' + if not os.path.exists(csv_file): + print(f"File {csv_file} not found. Run experiments first.") + return + + df = pd.read_csv(csv_file) + df_success = df[df['path_found'] == True] + + mazes = df_success['maze'].unique() + strategies = df_success['strategy'].unique() + + fig, axes = plt.subplots(1, 3, figsize=(18, 6)) + fig.suptitle('Сравнение алгоритмов поиска в лабиринтах', fontsize=16) + + for strat in strategies: + data = df_success[df_success['strategy'] == strat] + axes[0].plot(data['maze'], data['avg_time_ms'], marker='o', label=strat) + axes[0].set_title('Среднее время (мс)') + axes[0].set_xlabel('Лабиринт') + axes[0].set_ylabel('Время, мс') + axes[0].legend() + axes[0].grid(True) + + for strat in strategies: + data = df_success[df_success['strategy'] == strat] + axes[1].plot(data['maze'], data['avg_visited'], marker='s', label=strat) + axes[1].set_title('Посещённые клетки') + axes[1].set_xlabel('Лабиринт') + axes[1].set_ylabel('Количество') + axes[1].legend() + axes[1].grid(True) + + for strat in strategies: + data = df_success[df_success['strategy'] == strat] + axes[2].plot(data['maze'], data['avg_path_length'], marker='^', label=strat) + axes[2].set_title('Длина пути') + axes[2].set_xlabel('Лабиринт') + axes[2].set_ylabel('Шагов') + axes[2].legend() + axes[2].grid(True) + + plt.tight_layout() + plt.savefig('plots_2-nd-exercise.png', dpi=150) + print("Графики сохранены в plots_2-nd-exercise.png") + #plt.show() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/meosyam/docs/data/2/plots_2-nd-exercise.png b/meosyam/docs/data/2/plots_2-nd-exercise.png new file mode 100644 index 0000000..b9f9cab Binary files /dev/null and b/meosyam/docs/data/2/plots_2-nd-exercise.png differ