import sys from collections import deque import heapq import time import os 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 def __repr__(self): return f"Cell({self.x}, {self.y})" class Maze: """лабиринт""" def __init__(self, width, height): self.width = width self.height = height self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)] self.start = None self.exit = None 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 = [] directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] for dx, dy in directions: neighbor = self.get_cell(cell.x + dx, cell.y + dy) if neighbor and neighbor.is_passable(): neighbors.append(neighbor) return neighbors def set_start(self, x, y): cell = self.get_cell(x, y) if cell: cell.is_start = True self.start = cell def set_exit(self, x, y): cell = self.get_cell(x, y) if cell: cell.is_exit = True self.exit = cell class MazeBuilder: """интерфейс строителя лабиринта""" def buildFromFile(self, filename): raise NotImplementedError class TextFileMazeBuilder(MazeBuilder): """загрузка лабиринта из текстового файла""" def buildFromFile(self, filename): with open(filename, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n') for line in f.readlines()] height = len(lines) width = max(len(line) for line in lines) if height > 0 else 0 for i in range(height): if len(lines[i]) < width: lines[i] = lines[i] + ' ' * (width - len(lines[i])) maze = Maze(width, height) start_count = 0 exit_count = 0 for y, line in enumerate(lines): for x, ch in enumerate(line): if ch == '#': maze.get_cell(x, y).is_wall = True elif ch == 'S': maze.set_start(x, y) start_count += 1 elif ch == 'E': maze.set_exit(x, y) exit_count += 1 else: maze.get_cell(x, y).is_wall = False if start_count != 1 or exit_count != 1: raise ValueError(f"Ошибка: S={start_count}, E={exit_count} (нужно по одному)") return maze class SearchStats: """статистика поиска""" def __init__(self, time_ms=0, visited_cells=0, path_length=0): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length def __str__(self): return f"Время: {self.time_ms:.2f} мс, Посещено: {self.visited_cells}, Длина пути: {self.path_length}" class PathFindingStrategy: """интерфейс стратегии поиска пути""" def findPath(self, maze, start, exit): raise NotImplementedError def get_name(self): raise NotImplementedError class BFSStrategy(PathFindingStrategy): """BFS - гарантирует кратчайший путь""" def get_name(self): return "BFS (Поиск в ширину)" def findPath(self, maze, start, exit): from collections import deque if not start or not exit: return [], 0 queue = deque([(start, [start])]) visited = {start} while queue: current, path = queue.popleft() if current == exit: return path, len(visited) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) queue.append((neighbor, path + [neighbor])) return [], len(visited) class DFSStrategy(PathFindingStrategy): """DFS - быстрый, но не обязательно кратчайший""" def get_name(self): return "DFS (Поиск в глубину)" def findPath(self, maze, start, exit): if not start or not exit: return [], 0 stack = [(start, [start])] visited = {start} while stack: current, path = stack.pop() if current == exit: return path, len(visited) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) stack.append((neighbor, path + [neighbor])) return [], len(visited) class AStarStrategy(PathFindingStrategy): """алгоритм A Star - оптимальный и быстрый с эвристикой""" def get_name(self): return "A Star" def _heuristic(self, a, b): return abs(a.x - b.x) + abs(a.y - b.y) def findPath(self, maze, start, exit): if not start or not exit: return [], 0 import heapq heap = [] counter = 0 start_f = self._heuristic(start, exit) heapq.heappush(heap, (start_f, counter, start)) came_from = {} g_score = {start: 0} f_score = {start: start_f} visited = set() visited.add(start) while heap: current_f, _, current = heapq.heappop(heap) if current == exit: path = [] while current in came_from: path.append(current) current = came_from[current] path.append(start) path.reverse() return path, len(visited) if current_f > f_score.get(current, float('inf')): continue for neighbor in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = current g_score[neighbor] = tentative_g new_f = tentative_g + self._heuristic(neighbor, exit) f_score[neighbor] = new_f counter += 1 heapq.heappush(heap, (new_f, counter, neighbor)) visited.add(neighbor) return [], len(visited) class DijkstraStrategy(PathFindingStrategy): """алгоритм Дейкстры""" def get_name(self): return "Дейкстра (Dijkstra)" def findPath(self, maze, start, exit): if not start or not exit: return [], 0 import heapq heap = [] counter = 0 heapq.heappush(heap, (0, counter, start)) distances = {start: 0} came_from = {} visited = set() visited.add(start) while heap: current_dist, _, current = heapq.heappop(heap) if current == exit: path = [] while current in came_from: path.append(current) current = came_from[current] path.append(start) path.reverse() return path, len(visited) if current_dist > distances.get(current, float('inf')): continue for neighbor in maze.get_neighbors(current): new_dist = current_dist + 1 if new_dist < distances.get(neighbor, float('inf')): distances[neighbor] = new_dist came_from[neighbor] = current counter += 1 heapq.heappush(heap, (new_dist, counter, neighbor)) visited.add(neighbor) return [], len(visited) class MazeSolver: """решатель лабиринта - оркестратор, использующий стратегию""" def __init__(self, maze): self.maze = maze self._strategy = None def setStrategy(self, strategy): """динамическая смена стратегии поиска""" self._strategy = strategy print(f" Стратегия изменена на: {strategy.get_name()}") def solve(self): """ решение лабиринта с использованием текущей стратегии. возвращает время, посещённые клетки, длина пути """ if self._strategy is None: raise ValueError("Стратегия не установлена. Используйте setStrategy()") start_time = time.perf_counter() path, visited = self._strategy.findPath(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() time_ms = (end_time - start_time) * 1000 stats = SearchStats( time_ms=time_ms, visited_cells=visited, path_length=len(path) if path else 0 ) return path, stats class Observer: """интерфейс наблюдателя""" def update(self, event, data): raise NotImplementedError class ConsoleDisplay(Observer): """консольная визуализация - наблюдатель""" def __init__(self): self._last_path = None self._last_maze = None def update(self, event, data): if event == "maze_loaded": self._draw_maze(data) elif event == "path_found": self._last_path = data self._show_path(data) elif event == "player_moved": self._draw_maze_with_player(data) def _draw_maze(self, maze): """Отрисовка лабиринта""" os.system('cls' if os.name == 'nt' else 'clear') print("=" * (maze.width * 2 + 4)) print("ЛАБИРИНТ") print("=" * (maze.width * 2 + 4)) for y in range(maze.height): line = "" for x in range(maze.width): cell = maze.get_cell(x, y) if cell == maze.start: line += "S " elif cell == maze.exit: line += "E " elif cell.is_wall: line += "# " else: line += ". " print(line) print("=" * (maze.width * 2 + 4)) print("S - старт E - выход # - стена . - проход") def _draw_maze_with_player(self, game_state): """отрисовка лабиринта с игроком""" maze = game_state['maze'] player = game_state['player'] os.system('cls' if os.name == 'nt' else 'clear') print("=" * (maze.width * 2 + 4)) print("ЛАБИРИНТ (P - игрок)") print("=" * (maze.width * 2 + 4)) for y in range(maze.height): line = "" for x in range(maze.width): cell = maze.get_cell(x, y) if player and cell == player.get_position(): line += "P " elif cell == maze.start: line += "S " elif cell == maze.exit: line += "E " elif cell.is_wall: line += "# " else: line += ". " print(line) print("=" * (maze.width * 2 + 4)) if player: pos = player.get_position() print(f"Игрок: ({pos.x}, {pos.y})") print("S - старт E - выход # - стена . - проход P - игрок") def _show_path(self, path): """Показ информации о найденном пути""" if not path: print("\n Путь не найден!") return print(f"\n Путь найден! Длина: {len(path)} клеток") class Command: """Интерфейс команды""" def execute(self): raise NotImplementedError def undo(self): raise NotImplementedError class MoveCommand(Command): """Команда перемещения игрока""" def __init__(self, player, direction, maze): self._player = player self._dx, self._dy = direction self._maze = maze self._executed = False self._prev_position = None def execute(self): """Выполнение перемещения""" if self._executed: return False pos = self._player.get_position() new_x = pos.x + self._dx new_y = pos.y + self._dy target = self._maze.get_cell(new_x, new_y) if target and target.is_passable(): self._prev_position = pos self._player.set_position(target) self._executed = True return True return False def undo(self): """Отмена перемещения""" if not self._executed or self._prev_position is None: return False self._player.set_position(self._prev_position) self._executed = False return True def get_name(self): dir_names = {(-1, 0): "ВЛЕВО", (1, 0): "ВПРАВО", (0, -1): "ВВЕРХ", (0, 1): "ВНИЗ"} return f"Перемещение {dir_names.get((self._dx, self._dy), 'НЕИЗВЕСТНО')}" class CommandInvoker: """Инвокер команд (история для undo/redo)""" def __init__(self): self._history = [] self._redo_stack = [] def execute(self, command): """Выполнение команды с сохранением в истории""" if command.execute(): self._history.append(command) self._redo_stack.clear() return True return False def undo(self): """Отмена последней команды""" if not self._history: return False command = self._history.pop() if command.undo(): self._redo_stack.append(command) return True return False def redo(self): """Повтор отменённой команды""" if not self._redo_stack: return False command = self._redo_stack.pop() if command.execute(): self._history.append(command) return True return False def get_history_size(self): return len(self._history) class Player: """Игрок, перемещающийся по лабиринту""" def __init__(self, start_cell): self._position = start_cell self._start = start_cell def get_position(self): return self._position def set_position(self, cell): self._position = cell def reset(self): self._position = self._start def is_at_exit(self, maze): return self._position == maze.exit def get_steps_count(self, invoker): return invoker.get_history_size() class GameController: """контроллер, объединяющий все компоненты""" def __init__(self, maze): self.maze = maze self.player = Player(maze.start) self.solver = MazeSolver(maze) self.invoker = CommandInvoker() self.view = ConsoleDisplay() def run(self): """запуск интерактивного режима""" self.view.update("maze_loaded", self.maze) print("УПРАВЛЕНИЕ:") print(" H/J/K/Ll - движение") print(" U - отменить ход") print(" R - повторить ход") print(" B - BFS поиск пути") print(" D - DFS поиск пути") print(" A - A* поиск пути") print(" P - показать путь") print(" Q - выход") path = None last_strategy_name = "" while True: cmd = input("\nКоманда > ").lower() if cmd == 'q': print("До встречи!") break elif cmd in ['h', 'j', 'k', 'l']: dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)} command = MoveCommand(self.player, dir_map[cmd], self.maze) if self.invoker.execute(command): self.view.update("player_moved", { 'maze': self.maze, 'player': self.player }) if self.player.is_at_exit(self.maze): print(f"\n *** ПОБЕДА! ВЫХОД ДОСТИГНУТ за {self.player.get_steps_count(self.invoker)} шагов! ***") break else: print(" Стена! Нельзя пройти.") elif cmd == 'u': if self.invoker.undo(): self.view.update("player_moved", { 'maze': self.maze, 'player': self.player }) print(" Отменено") else: print(" Нечего отменять") elif cmd == 'r': if self.invoker.redo(): self.view.update("player_moved", { 'maze': self.maze, 'player': self.player }) print(" Повторено") else: print(" Нечего повторять") elif cmd == 'b': self.solver.setStrategy(BFSStrategy()) start_time = time.perf_counter() path, stats = self.solver.solve() self.view.update("path_found", path) print(f" BFS: {stats}") last_strategy_name = "BFS" elif cmd == 'd': self.solver.setStrategy(DFSStrategy()) path, stats = self.solver.solve() self.view.update("path_found", path) print(f" DFS: {stats}") last_strategy_name = "DFS" elif cmd == 'a': self.solver.setStrategy(AStarStrategy()) path, stats = self.solver.solve() self.view.update("path_found", path) print(f" A*: {stats}") last_strategy_name = "A*" elif cmd == 'p': if path: self._show_path_on_maze(path) else: print(" Сначала найдите путь (B, D или A)") else: print(" Неизвестная команда") def _show_path_on_maze(self, path): """показать путь на лабиринте""" os.system('cls' if os.name == 'nt' else 'clear') print("=" * (self.maze.width * 2 + 4)) print("ЛАБИРИНТ С ПУТЁМ (* - путь)") print("=" * (self.maze.width * 2 + 4)) path_set = set(path) for y in range(self.maze.height): line = "" for x in range(self.maze.width): cell = self.maze.get_cell(x, y) if cell == self.player.get_position(): line += "P " elif cell == self.maze.start: line += "S " elif cell == self.maze.exit: line += "E " elif cell in path_set and cell.is_passable(): line += "* " elif cell.is_wall: line += "# " else: line += ". " print(line) print("=" * (self.maze.width * 2 + 4)) print("S - старт E - выход # - стена . - проход * - путь P - игрок") input("\nНажмите Enter для продолжения...") self.view.update("player_moved", { 'maze': self.maze, 'player': self.player }) class MazeGenerator: """генератор тестовых лабиринтов различной сложности""" @staticmethod def create_empty_maze(width, height): """пустой лабиринт без стен""" maze = Maze(width, height) for y in range(height): for x in range(width): maze.get_cell(x, y).is_wall = False maze.set_start(0, 0) maze.set_exit(width - 1, height - 1) return maze @staticmethod def create_simple_maze(width, height): """простой лабиринт с прямым путём""" maze = Maze(width, height) # Заполняем стенами for y in range(height): for x in range(width): maze.get_cell(x, y).is_wall = True # Создаём прямой путь for i in range(min(width, height)): maze.get_cell(i, i).is_wall = False if i + 1 < width: maze.get_cell(i + 1, i).is_wall = False if i + 1 < height: maze.get_cell(i, i + 1).is_wall = False maze.set_start(0, 0) maze.set_exit(width - 1, height - 1) return maze @staticmethod def generate_dfs_maze(width, height): """генерация запутанного лабиринта алгоритмом DFS""" maze = Maze(width, height) # заполняем стенами for y in range(height): for x in range(width): maze.get_cell(x, y).is_wall = True start_x, start_y = 1, 1 maze.get_cell(start_x, start_y).is_wall = False stack = [(start_x, start_y)] visited = {(start_x, start_y)} directions = [(0, -2), (0, 2), (-2, 0), (2, 0)] while stack: x, y = stack[-1] neighbors = [] for dx, dy in directions: nx, ny = x + dx, y + dy if 0 < nx < width - 1 and 0 < ny < height - 1 and (nx, ny) not in visited: neighbors.append((nx, ny, dx, dy)) if neighbors: import random nx, ny, dx, dy = random.choice(neighbors) maze.get_cell(x + dx // 2, y + dy // 2).is_wall = False maze.get_cell(nx, ny).is_wall = False visited.add((nx, ny)) stack.append((nx, ny)) else: stack.pop() maze.set_start(start_x, start_y) # ищем дальнюю точку для выхода farthest = (start_x, start_y) max_dist = 0 for y in range(height): for x in range(width): cell = maze.get_cell(x, y) if cell and not cell.is_wall: dist = abs(x - start_x) + abs(y - start_y) if dist > max_dist: max_dist = dist farthest = (x, y) # Устанавливаем выход maze.set_exit(farthest[0], farthest[1]) # Дополнительная проверка: если выход всё ещё None - создаём принудительно if maze.exit is None: for y in range(height): for x in range(width): cell = maze.get_cell(x, y) if cell and not cell.is_wall and not cell.is_start: maze.set_exit(x, y) break if maze.exit: break return maze @staticmethod def create_no_exit_maze(width, height): """лабиринт без выхода""" maze = MazeGenerator.generate_dfs_maze(width, height) if maze.exit: maze.exit.is_wall = True maze.exit.is_exit = False maze.exit = None return maze @staticmethod def save_to_file(maze, filename): """сохранение лабиринта в файл""" with open(filename, 'w', encoding='utf-8') as f: for y in range(maze.height): line = "" for x in range(maze.width): cell = maze.get_cell(x, y) if cell.is_start: line += 'S' elif cell.is_exit: line += 'E' elif cell.is_wall: line += '#' else: line += '.' f.write(line + '\n') class ExperimentRunner: """запуск экспериментов и сбор статистики""" def __init__(self, runs_per_experiment=5): self.runs_per_experiment = runs_per_experiment self.results = [] def run_experiment(self, maze, strategy, maze_name): """запуск одного эксперимента""" times = [] visited = [] path_lengths = [] for _ in range(self.runs_per_experiment): start_time = time.perf_counter() path, visited_count = strategy.findPath(maze, maze.start, maze.exit) end_time = time.perf_counter() times.append((end_time - start_time) * 1000) visited.append(visited_count) path_lengths.append(len(path) if path else 0) return { 'maze_name': maze_name, 'strategy': strategy.get_name(), 'avg_time_ms': sum(times) / len(times), 'min_time_ms': min(times), 'max_time_ms': max(times), 'avg_visited_cells': sum(visited) / len(visited), 'min_visited_cells': min(visited), 'max_visited_cells': max(visited), 'avg_path_length': sum(path_lengths) / len(path_lengths), 'min_path_length': min(path_lengths), 'max_path_length': max(path_lengths), 'path_found': any(pl > 0 for pl in path_lengths), 'runs': self.runs_per_experiment } def run_all_experiments(self): """запуск всех экспериментов на всех лабиринтах""" print("ГЕНЕРАЦИЯ ТЕСТОВЫХ ЛАБИРИНТОВ") # Создаём тестовые лабиринты test_mazes = { 'tiny_simple (10x10)': MazeGenerator.create_simple_maze(10, 10), 'small_empty (20x20)': MazeGenerator.create_empty_maze(20, 20), 'medium_dfs (30x30)': MazeGenerator.generate_dfs_maze(30, 30), 'medium_complex (40x40)': MazeGenerator.generate_dfs_maze(40, 40), 'large_dfs (50x50)': MazeGenerator.generate_dfs_maze(50, 50), 'very_large_dfs (100x100)': MazeGenerator.generate_dfs_maze(100, 100), 'no_exit (20x20)': MazeGenerator.create_no_exit_maze(20, 20) } # Сохраняем лабиринты в файлы for name, maze in test_mazes.items(): filename = f"test_{name.replace(' ', '_').replace('(', '').replace(')', '')}.txt" MazeGenerator.save_to_file(maze, filename) print(f" Создан: {filename}") # Стратегии для тестирования strategies = [ BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy() ] print("ЗАПУСК ЭКСПЕРИМЕНТОВ") for maze_name, maze in test_mazes.items(): print(f"\n Лабиринт: {maze_name}") print(f" Размер: {maze.width}x{maze.height}") print(f" Старт: ({maze.start.x}, {maze.start.y})") # Проверяем, есть ли выход if maze.exit: print(f" Выход: ({maze.exit.x}, {maze.exit.y})") else: print(f" Выход: ОТСУТСТВУЕТ") for strategy in strategies: print(f" → {strategy.get_name()}...", end=" ", flush=True) result = self.run_experiment(maze, strategy, maze_name) self.results.append(result) status = "✓" if result['path_found'] else "✗" print(f"{status} {result['avg_time_ms']:.2f}мс, " f"{result['avg_visited_cells']:.0f} клеток, " f"{result['avg_path_length']:.1f} шагов") def save_to_csv(self, filename="experiment_results.csv"): import csv if not self.results: print("Нет результатов для сохранения") return with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile: fieldnames = [ 'maze_name', 'strategy', 'runs', 'avg_time_ms', 'min_time_ms', 'max_time_ms', 'avg_visited_cells', 'min_visited_cells', 'max_visited_cells', 'avg_path_length', 'min_path_length', 'max_path_length', 'path_found' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=';') writer.writeheader() for result in self.results: writer.writerow(result) if os.path.exists(filename): print(f"\n Результаты сохранены в {filename}") print(f" Размер файла: {os.path.getsize(filename)} байт") else: print(f"\n Ошибка: файл {filename} не создан") def print_summary(self): print("СВОДНАЯ СТАТИСТИКА ЭКСПЕРИМЕНТОВ") # Группировка по лабиринтам grouped = {} for result in self.results: name = result['maze_name'] if name not in grouped: grouped[name] = [] grouped[name].append(result) for maze_name, results in grouped.items(): print(f"\n {maze_name}") print(f"{'Стратегия':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден':<8}") for result in sorted(results, key=lambda x: x['avg_time_ms']): status = "✓" if result['path_found'] else "✗" print(f"{result['strategy']:<25} " f"{result['avg_time_ms']:<12.2f} " f"{result['avg_visited_cells']:<12.0f} " f"{result['avg_path_length']:<12.1f} " f"{status:<8}") print("ОБЩАЯ СТАТИСТИКА ПО СТРАТЕГИЯМ") strategy_stats = {} for result in self.results: name = result['strategy'] if name not in strategy_stats: strategy_stats[name] = {'times': [], 'visited': [], 'lengths': []} strategy_stats[name]['times'].append(result['avg_time_ms']) strategy_stats[name]['visited'].append(result['avg_visited_cells']) strategy_stats[name]['lengths'].append(result['avg_path_length']) print(f"\n{'Стратегия':<25} {'Ср.время(мс)':<15} {'Ср.посещено':<15} {'Ср.длина':<12}") for name, stats in strategy_stats.items(): avg_time = sum(stats['times']) / len(stats['times']) avg_visited = sum(stats['visited']) / len(stats['visited']) avg_length = sum(stats['lengths']) / len(stats['lengths']) print(f"{name:<25} {avg_time:<15.2f} {avg_visited:<15.0f} {avg_length:<12.1f}") def print_conclusions(self): print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") # Находим лучшие стратегии bfs_results = [r for r in self.results if r['strategy'] == "BFS (Поиск в ширину)" and r['path_found']] dfs_results = [r for r in self.results if r['strategy'] == "DFS (Поиск в глубину)" and r['path_found']] astar_results = [r for r in self.results if r['strategy'] == "A* (A Star)" and r['path_found']] dijkstra_results = [r for r in self.results if r['strategy'] == "Дейкстра (Dijkstra)" and r['path_found']] conclusions = [] if bfs_results: avg_bfs_time = sum(r['avg_time_ms'] for r in bfs_results) / len(bfs_results) avg_bfs_length = sum(r['avg_path_length'] for r in bfs_results) / len(bfs_results) conclusions.append(f" • BFS: среднее время {avg_bfs_time:.2f}мс, длина пути {avg_bfs_length:.1f}") if dfs_results: avg_dfs_time = sum(r['avg_time_ms'] for r in dfs_results) / len(dfs_results) avg_dfs_length = sum(r['avg_path_length'] for r in dfs_results) / len(dfs_results) conclusions.append(f" • DFS: среднее время {avg_dfs_time:.2f}мс, длина пути {avg_dfs_length:.1f}") if astar_results: avg_astar_time = sum(r['avg_time_ms'] for r in astar_results) / len(astar_results) avg_astar_length = sum(r['avg_path_length'] for r in astar_results) / len(astar_results) conclusions.append(f" • A*: среднее время {avg_astar_time:.2f}мс, длина пути {avg_astar_length:.1f}") if dijkstra_results: avg_dijkstra_time = sum(r['avg_time_ms'] for r in dijkstra_results) / len(dijkstra_results) avg_dijkstra_length = sum(r['avg_path_length'] for r in dijkstra_results) / len(dijkstra_results) conclusions.append(f" • Дейкстра: среднее время {avg_dijkstra_time:.2f}мс, длина пути {avg_dijkstra_length:.1f}") print("\n РЕЗУЛЬТАТЫ АНАЛИЗА:\n") for c in conclusions: print(c) print("\n РЕКОМЕНДАЦИИ:\n") print(" 1. Для маленьких лабиринтов - любой алгоритм работает быстро") print(" 2. Для больших лабиринтов - A* даёт лучший компромисс скорость/качество") print(" 3. BFS гарантирует кратчайший путь, но медленнее на больших картах") print(" 4. DFS самый быстрый, но путь может быть неоптимальным") print(" 5. Если путь не существует - BFS и A* эффективнее обнаруживают это") def plot_experiment_results(csv_filename="experiment_results.csv"): """построение графиков по результатам экспериментов""" try: import matplotlib.pyplot as plt import pandas as pd import numpy as np except ImportError: print("Установите: pip install matplotlib pandas") return if not os.path.exists(csv_filename): print("Файл результатов не найден") return if os.path.getsize(csv_filename) == 0: print(f"Файл {csv_filename} пустой. Сначала запустите эксперименты.") return df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') if 'strategy' not in df.columns: print(f"Ошибка: в файле {csv_filename} нет колонки 'strategy'") print(f"Доступные колонки: {list(df.columns)}") return fig = plt.figure(figsize=(16, 12)) fig.suptitle('Сравнение алгоритмов поиска в лабиринте', fontsize=16) # Время выполнения ax1 = fig.add_subplot(2, 2, 1) for strategy in df['strategy'].unique(): data = df[df['strategy'] == strategy] ax1.bar(data['maze_name'], data['avg_time_ms'], alpha=0.7, label=strategy) ax1.set_xlabel('Лабиринт') ax1.set_ylabel('Время (мс)') ax1.set_title('Время выполнения алгоритмов') ax1.legend(loc='upper left', fontsize=8) ax1.tick_params(axis='x', rotation=45, labelsize=8) # Посещённые клетки ax2 = fig.add_subplot(2, 2, 2) for strategy in df['strategy'].unique(): data = df[df['strategy'] == strategy] ax2.bar(data['maze_name'], data['avg_visited_cells'], alpha=0.7, label=strategy) ax2.set_xlabel('Лабиринт') ax2.set_ylabel('Посещено клеток') ax2.set_title('Эффективность поиска') ax2.legend(loc='upper left', fontsize=8) ax2.tick_params(axis='x', rotation=45, labelsize=8) # Длина пути ax3 = fig.add_subplot(2, 2, 3) for strategy in df['strategy'].unique(): data = df[df['strategy'] == strategy] ax3.bar(data['maze_name'], data['avg_path_length'], alpha=0.7, label=strategy) ax3.set_xlabel('Лабиринт') ax3.set_ylabel('Длина пути') ax3.set_title('Оптимальность пути') ax3.legend(loc='upper left', fontsize=8) ax3.tick_params(axis='x', rotation=45, labelsize=8) # Радар-диаграмма ax4 = fig.add_subplot(2, 2, 4, projection='polar') strategies = df['strategy'].unique() metrics = ['avg_time_ms', 'avg_visited_cells', 'avg_path_length'] metric_labels = ['Время', 'Посещено', 'Длина пути'] for strategy in strategies: data = df[df['strategy'] == strategy] values = [] for metric in metrics: val = data[metric].mean() max_val = df[metric].max() normalized = 1 - (val / max_val) if max_val > 0 else 0.5 values.append(normalized) values.append(values[0]) angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist() angles += angles[:1] ax4.plot(angles, values, 'o-', linewidth=2, label=strategy) ax4.fill(angles, values, alpha=0.25) ax4.set_xticks(angles[:-1]) ax4.set_xticklabels(metric_labels) ax4.set_ylim(0, 1) ax4.set_title('Радар-диаграмма (дальше от центра = лучше)') ax4.legend(loc='upper right', fontsize=7, bbox_to_anchor=(1.3, 1.0)) plt.tight_layout() plt.savefig('algorithm_comparison.png', dpi=150, bbox_inches='tight') plt.show() print("График сохранён: algorithm_comparison.png") def plot_by_maze(csv_filename="experiment_results.csv"): """построение графиков для каждого лабиринта""" try: import matplotlib.pyplot as plt import pandas as pd except ImportError: return if not os.path.exists(csv_filename): return df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') print("Колонки в файле:", list(df.columns)) if 'maze_name' not in df.columns: for col in df.columns: if 'maze' in col.lower() or 'name' in col.lower(): df.rename(columns={col: 'maze_name'}, inplace=True) break if 'maze_name' not in df.columns: print("Ошибка: колонка 'maze_name' не найдена") return mazes = df['maze_name'].unique() mazes = df['maze_name'].unique() fig, axes = plt.subplots(len(mazes), 3, figsize=(15, 4 * len(mazes))) fig.suptitle('Детальный анализ по каждому лабиринту', fontsize=14) if len(mazes) == 1: axes = axes.reshape(1, -1) for i, maze_name in enumerate(mazes): maze_data = df[df['maze_name'] == maze_name] axes[i, 0].bar(maze_data['strategy'], maze_data['avg_time_ms']) axes[i, 0].set_title(f'{maze_name}\nВремя (мс)') axes[i, 0].tick_params(axis='x', rotation=45, labelsize=8) axes[i, 1].bar(maze_data['strategy'], maze_data['avg_visited_cells']) axes[i, 1].set_title('Посещено клеток') axes[i, 1].tick_params(axis='x', rotation=45, labelsize=8) bars = axes[i, 2].bar(maze_data['strategy'], maze_data['avg_path_length']) axes[i, 2].set_title('Длина пути') axes[i, 2].tick_params(axis='x', rotation=45, labelsize=8) for j, (bar, found) in enumerate(zip(bars, maze_data['path_found'])): if not found: bar.set_color('red') plt.tight_layout() plt.savefig('maze_detailed_analysis.png', dpi=150, bbox_inches='tight') plt.show() print("Детальные графики сохранены: maze_detailed_analysis.png") def print_analysis(csv_filename="experiment_results.csv"): """вывод анализа эффективности алгоритмов""" try: import pandas as pd except ImportError: return if not os.path.exists(csv_filename): return df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') print("DEBUG: Колонки в файле:", list(df.columns)) if 'strategy' not in df.columns: for col in df.columns: if 'strategy' in col.lower() or 'algo' in col.lower(): df.rename(columns={col: 'strategy'}, inplace=True) break if 'strategy' not in df.columns: print("Ошибка: колонка 'strategy' не найдена в CSV") return print("АНАЛИЗ ЭФФЕКТИВНОСТИ АЛГОРИТМОВ") print("\nОбщая статистика по алгоритмам:") print(f"{'Алгоритм':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден %':<10}") for strategy in df['strategy'].unique(): data = df[df['strategy'] == strategy] avg_time = data['avg_time_ms'].mean() avg_visited = data['avg_visited_cells'].mean() avg_length = data['avg_path_length'].mean() found_rate = data['path_found'].mean() * 100 print(f"{strategy:<25} {avg_time:<12.2f} {avg_visited:<12.0f} {avg_length:<12.1f} {found_rate:<10.1f}%") print("АНАЛИЗ ПО ТИПАМ ЛАБИРИНТОВ") for maze_name in df['maze_name'].unique(): maze_data = df[df['maze_name'] == maze_name] print(f"\nЛабиринт: {maze_name}") if 'tiny' in maze_name: maze_type = "Маленький (10x10)" elif 'small' in maze_name: maze_type = "Небольшой (20x20)" elif 'medium' in maze_name: maze_type = "Средний (30x30-40x40)" elif 'large' in maze_name: maze_type = "Большой (50x50)" elif 'no_exit' in maze_name: maze_type = "Без выхода" else: maze_type = "Обычный" print(f" Тип: {maze_type}") best_time = maze_data.loc[maze_data['avg_time_ms'].idxmin()] print(f" Самый быстрый: {best_time['strategy']} ({best_time['avg_time_ms']:.2f} мс)") best_visited = maze_data.loc[maze_data['avg_visited_cells'].idxmin()] print(f" Самый экономный: {best_visited['strategy']} ({best_visited['avg_visited_cells']:.0f} клеток)") path_data = maze_data[maze_data['path_found'] == True] if not path_data.empty: best_path = path_data.loc[path_data['avg_path_length'].idxmin()] print(f" Самый короткий путь: {best_path['strategy']} ({best_path['avg_path_length']:.1f} шагов)") else: print(" Путь не найден ни одним алгоритмом") print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") print(""" 1. BFS (Поиск в ширину): - Гарантирует кратчайший путь - Медленнее на больших лабиринтах - Рекомендуется для маленьких лабиринтов (до 20x20) 2. DFS (Поиск в глубину): - Самый быстрый по времени - Не гарантирует кратчайший путь - Рекомендуется когда важна скорость, а не оптимальность 3. A* (A Star): - Лучший компромисс между скоростью и оптимальностью - Рекомендуется для больших и сложных лабиринтов (40x40+) 4. Дейкстра: - Гарантирует оптимальный путь - Работает с взвешенными графами - Медленнее A* на больших лабиринтах ИТОГОВЫЕ РЕКОМЕНДАЦИИ: - Маленькие лабиринты (до 20x20): BFS - Средние лабиринты (20x20 - 40x40): A* - Большие лабиринты (40x40+): A* или DFS - Когда нужен кратчайший путь: BFS или A* - Когда важна только скорость: DFS - Лабиринты без выхода: BFS или A* (быстрее обнаруживают) """) def run_full_analysis(): """Запуск полного анализа с построением графиков""" if not os.path.exists("experiment_results.csv"): print("Результаты не найдены. Запускаем эксперименты...") runner = ExperimentRunner(runs_per_experiment=5) runner.run_all_experiments() runner.save_to_csv() runner.print_summary() runner.print_conclusions() plot_experiment_results() plot_by_maze() print_analysis() def run_experiments(): """Запуск экспериментов с построением графиков и анализом""" runner = ExperimentRunner(runs_per_experiment=5) runner.run_all_experiments() runner.save_to_csv() runner.print_summary() runner.print_conclusions() run_full_analysis() if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == 'experiment': run_experiments() else: # Интерактивный режим sample = """############### #S # # ### ####### # # # # # ### # ### # # # # # # # # # ### # ### ### # # E # ###############""" with open("maze.txt", "w") as f: f.write(sample) builder = TextFileMazeBuilder() maze = builder.buildFromFile("maze.txt") print(f"Лабиринт загружен: {maze.width}x{maze.height}") print(f"Старт: ({maze.start.x}, {maze.start.y})") print(f"Выход: ({maze.exit.x}, {maze.exit.y})") game = GameController(maze) game.run()