2026-rff_mp/nikolaevda/task2/Zadanie2.py
2026-05-24 21:11:54 +03:00

1318 lines
48 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()