2026-05-23 16:34:11 +00:00
|
|
|
|
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
|
2026-05-23 16:39:52 +00:00
|
|
|
|
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} (нужно по одному)")
|
|
|
|
|
|
|
2026-05-23 16:48:29 +00:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-05-23 16:49:44 +00:00
|
|
|
|
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):
|
|
|
|
|
|
"""
|
|
|
|
|
|
решение лабиринта с использованием текущей стратегии.
|
2026-05-24 16:17:11 +00:00
|
|
|
|
возвращает время, посещённые клетки, длина пути
|
2026-05-23 16:49:44 +00:00
|
|
|
|
"""
|
|
|
|
|
|
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
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-23 16:51:32 +00:00
|
|
|
|
return path, stats
|
2026-05-24 16:17:11 +00:00
|
|
|
|
|
2026-05-23 16:51:32 +00:00
|
|
|
|
|
|
|
|
|
|
class Observer:
|
2026-05-24 16:17:11 +00:00
|
|
|
|
"""интерфейс наблюдателя"""
|
2026-05-23 16:51:32 +00:00
|
|
|
|
|
|
|
|
|
|
def update(self, event, data):
|
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConsoleDisplay(Observer):
|
2026-05-24 16:17:11 +00:00
|
|
|
|
"""консольная визуализация - наблюдатель"""
|
2026-05-23 16:51:32 +00:00
|
|
|
|
|
|
|
|
|
|
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):
|
2026-05-24 16:17:11 +00:00
|
|
|
|
"""отрисовка лабиринта с игроком"""
|
2026-05-23 16:51:32 +00:00
|
|
|
|
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("УПРАВЛЕНИЕ:")
|
2026-05-24 16:17:11 +00:00
|
|
|
|
print(" H/J/K/Ll - движение")
|
2026-05-23 16:51:32 +00:00
|
|
|
|
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':
|
2026-05-24 16:17:11 +00:00
|
|
|
|
print("До встречи!")
|
2026-05-23 16:51:32 +00:00
|
|
|
|
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
|
2026-05-24 16:17:11 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2026-05-24 17:40:20 +00:00
|
|
|
|
if cell and not cell.is_wall:
|
2026-05-24 16:17:11 +00:00
|
|
|
|
dist = abs(x - start_x) + abs(y - start_y)
|
|
|
|
|
|
if dist > max_dist:
|
|
|
|
|
|
max_dist = dist
|
|
|
|
|
|
farthest = (x, y)
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
|
|
|
|
|
# Устанавливаем выход
|
2026-05-24 16:17:11 +00:00
|
|
|
|
maze.set_exit(farthest[0], farthest[1])
|
|
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
# Дополнительная проверка: если выход всё ещё 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
|
|
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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})")
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
|
|
|
|
|
# Проверяем, есть ли выход
|
|
|
|
|
|
if maze.exit:
|
|
|
|
|
|
print(f" Выход: ({maze.exit.x}, {maze.exit.y})")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f" Выход: ОТСУТСТВУЕТ")
|
2026-05-24 16:17:11 +00:00
|
|
|
|
|
|
|
|
|
|
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} шагов")
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
def save_to_csv(self, filename="experiment_results.csv"):
|
|
|
|
|
|
import csv
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
|
|
|
|
|
if not self.results:
|
|
|
|
|
|
print("Нет результатов для сохранения")
|
|
|
|
|
|
return
|
2026-05-24 16:17:11 +00:00
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile:
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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'
|
|
|
|
|
|
]
|
2026-05-24 17:40:20 +00:00
|
|
|
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=';')
|
2026-05-24 16:17:11 +00:00
|
|
|
|
writer.writeheader()
|
|
|
|
|
|
|
|
|
|
|
|
for result in self.results:
|
|
|
|
|
|
writer.writerow(result)
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
|
|
|
|
|
if os.path.exists(filename):
|
|
|
|
|
|
print(f"\n Результаты сохранены в {filename}")
|
|
|
|
|
|
print(f" Размер файла: {os.path.getsize(filename)} байт")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"\n Ошибка: файл {filename} не создан")
|
|
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
if os.path.getsize(csv_filename) == 0:
|
|
|
|
|
|
print(f"Файл {csv_filename} пустой. Сначала запустите эксперименты.")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig')
|
2026-05-24 16:17:11 +00:00
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
if 'strategy' not in df.columns:
|
|
|
|
|
|
print(f"Ошибка: в файле {csv_filename} нет колонки 'strategy'")
|
|
|
|
|
|
print(f"Доступные колонки: {list(df.columns)}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-24 18:11:54 +00:00
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig')
|
2026-05-24 18:11:54 +00:00
|
|
|
|
print("Колонки в файле:", list(df.columns))
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-24 18:11:54 +00:00
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
if 'maze_name' not in df.columns:
|
|
|
|
|
|
print("Ошибка: колонка 'maze_name' не найдена")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
mazes = df['maze_name'].unique()
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig')
|
|
|
|
|
|
|
2026-05-24 18:11:54 +00:00
|
|
|
|
print("DEBUG: Колонки в файле:", list(df.columns))
|
2026-05-24 17:40:20 +00:00
|
|
|
|
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
|
2026-05-24 18:11:54 +00:00
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
if 'strategy' not in df.columns:
|
|
|
|
|
|
print("Ошибка: колонка 'strategy' не найдена в CSV")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-05-24 18:11:54 +00:00
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 17:40:20 +00:00
|
|
|
|
|
2026-05-24 16:17:11 +00:00
|
|
|
|
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()
|