import time import csv from abc import ABC, abstractmethod from collections import deque from typing import List, Dict, Optional, Tuple import heapq #Модель лабиринта class Cell: def __init__(self, x: int, y: int): self.x = x self.y = y self.is_wall = False self.is_start = False self.is_exit = False self.weight = 1 def is_passable(self) -> bool: return not self.is_wall def __lt__(self, other): return (self.x, self.y) < (other.x, other.y) def __repr__(self): return f"Cell({self.x}, {self.y})" class Maze: def __init__(self, width: int, height: int): self.width = width self.height = height self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)] self.start: Optional[Cell] = None self.exit: Optional[Cell] = None def get_cell(self, x: int, y: int) -> Optional[Cell]: if 0 <= x < self.width and 0 <= y < self.height: return self.cells[x][y] return None def get_neighbors(self, cell: Cell) -> List[Cell]: neighbors = [] directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] for dx, dy in directions: nx, ny = cell.x + dx, cell.y + dy neighbor = self.get_cell(nx, ny) if neighbor and neighbor.is_passable(): neighbors.append(neighbor) return neighbors #Постройка лабиринта class MazeBuilder(ABC): @abstractmethod def build_from_string_list(self, lines: List[str]) -> Maze: pass class TextMazeBuilder(MazeBuilder): def build_from_string_list(self, lines: List[str]) -> Maze: height = len(lines) width = len(lines[0]) if height > 0 else 0 maze = Maze(width, height) for y, line in enumerate(lines): for x, char in enumerate(line): cell = maze.get_cell(x, y) if char == '#': cell.is_wall = True elif char == 'S': cell.is_start = True maze.start = cell elif char == 'E': cell.is_exit = True maze.exit = cell elif char == 'W': cell.weight = 3 elif char == 'D': cell.weight = 2 return maze #Стратегии поиска пути class PathFindingStrategy(ABC): def __init__(self): self.visited_count = 0 @abstractmethod def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: pass def _reconstruct_path(self, came_from: Dict, start: Cell, exit: Cell) -> List[Cell]: if exit not in came_from: return [] path = [] current = exit while current != start: path.append(current) current = came_from[current] path.append(start) path.reverse() return path class BFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: self.visited_count = 0 queue = deque([start]) came_from = {start: None} while queue: current = queue.popleft() self.visited_count += 1 if current == exit: break for neighbor in maze.get_neighbors(current): if neighbor not in came_from: queue.append(neighbor) came_from[neighbor] = current return self._reconstruct_path(came_from, start, exit) class DFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: self.visited_count = 0 stack = [start] came_from = {start: None} while stack: current = stack.pop() self.visited_count += 1 if current == exit: break for neighbor in maze.get_neighbors(current): if neighbor not in came_from: stack.append(neighbor) came_from[neighbor] = current return self._reconstruct_path(came_from, start, exit) class AStarStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: self.visited_count = 0 def heuristic(a: Cell, b: Cell) -> int: return abs(a.x - b.x) + abs(a.y - b.y) priority_queue = [] heapq.heappush(priority_queue, (0, start)) came_from = {start: None} g_score = {start: 0} while priority_queue: _, current = heapq.heappop(priority_queue) self.visited_count += 1 if current == exit: break for neighbor in maze.get_neighbors(current): tentative_g_score = g_score[current] + neighbor.weight if neighbor not in g_score or tentative_g_score < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g_score f_score = tentative_g_score + heuristic(neighbor, exit) heapq.heappush(priority_queue, (f_score, neighbor)) return self._reconstruct_path(came_from, start, exit) class DijkstraStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: self.visited_count = 0 priority_queue = [] heapq.heappush(priority_queue, (0, start)) came_from = {start: None} g_score = {start: 0} while priority_queue: current_g, current = heapq.heappop(priority_queue) self.visited_count += 1 if current == exit: break for neighbor in maze.get_neighbors(current): tentative_g_score = g_score[current] + neighbor.weight if neighbor not in g_score or tentative_g_score < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g_score heapq.heappush(priority_queue, (tentative_g_score, neighbor)) return self._reconstruct_path(came_from, start, exit) #Оркестратор поиска class SearchStats: def __init__(self, time_ms: float, visited_cells: int, path_length: int): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length def __str__(self): return f"Time: {self.time_ms:.3f}ms | Visited: {self.visited_cells} | Path length: {self.path_length}" class Observer(ABC): @abstractmethod def update(self, event: str): pass class MazeSolver: def __init__(self, maze: Maze, strategy: PathFindingStrategy): self.maze = maze self.strategy = strategy self.observers = [] def set_strategy(self, strategy: PathFindingStrategy): self.strategy = strategy def add_observer(self, observer: Observer): self.observers.append(observer) def _notify(self, event: str): for observer in self.observers: observer.update(event) def solve(self) -> Tuple[List[Cell], SearchStats]: self._notify("Search started") start_time = time.perf_counter() path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() time_ms = (end_time - start_time) * 1000 stats = SearchStats(time_ms, self.strategy.visited_count, len(path)) self._notify("Search completed") return path, stats #Визуализация class ConsoleView(Observer): def update(self, event: str): print(f"[Event] {event}") def render(self, maze: Maze, path: List[Cell]): path_set = set(path) for y in range(maze.height): row = "" for x in range(maze.width): cell = maze.get_cell(x, y) if cell == maze.start: row += "S" elif cell == maze.exit: row += "E" elif cell in path_set: row += "*" elif cell.is_wall: row += "#" elif cell.weight == 3: row += "W" # Болото elif cell.weight == 2: row += "D" # Песок else: row += "." print(row) #Экспериментальная часть def create_test_mazes() -> Dict[str, Maze]: builder = TextMazeBuilder() mazes = {} #Маленький лабиринт 10x10 с простым путём small_maze = [ "S.........", "#####.####", "..........", "####.#####", "..........", "#.#######.", "..........", "######.###", "..........", ".........E" ] mazes["Small (10x10)"] = builder.build_from_string_list(small_maze) #Пустой лабиринт 50x50 empty_maze = ["." * 50 for _ in range(50)] empty_maze[0] = "S" + empty_maze[0][1:] empty_maze[-1] = empty_maze[-1][:-1] + "E" mazes["Empty (50x50)"] = builder.build_from_string_list(empty_maze) #Средний лабиринт 50x50 с тупиками medium_maze = [] for y in range(50): if y == 0: row = "S" + "." * 49 elif y == 49: row = "." * 49 + "E" elif y % 2 == 1: row = "#" * 45 + "." * 5 if y % 4 == 1 else "." * 5 + "#" * 45 else: row = "." * 50 medium_maze.append(row) mazes["Medium with dead ends (50x50)"] = builder.build_from_string_list(medium_maze) # Большой лабиринт 100x100 large_maze = [] for y in range(100): if y == 0: row = "S" + "." * 99 elif y == 99: row = "." * 99 + "E" elif y % 2 == 1: row = ("#" * 9 + ".") * 10 else: row = "." * 100 large_maze.append(row) mazes["Large (100x100)"] = builder.build_from_string_list(large_maze) # Лабиринт без выхода no_exit_maze = [ "S....#....", "##########", "##########", "##########", "##########", "##########", "##########", "##########", "##########", "######...E" ] mazes["No exit (10x10)"] = builder.build_from_string_list(no_exit_maze) return mazes def run_experiments() -> None: mazes = create_test_mazes() strategies = { "BFS": BFSStrategy(), "DFS": DFSStrategy(), "A*": AStarStrategy(), "Dijkstra": DijkstraStrategy() } results = [] print("=" * 80) print("ЗАПУСК ЭКСПЕРИМЕНТОВ ПО СРАВНЕНИЮ АЛГОРИТМОВ ПОИСКА ПУТИ") print("=" * 80) for maze_name, maze in mazes.items(): print(f"\nТестирование: {maze_name}") print("-" * 60) for strategy_name, strategy in strategies.items(): solver = MazeSolver(maze, strategy) runs = 5 total_time = 0 path = [] stats = None for _ in range(runs): path, stats = solver.solve() total_time += stats.time_ms avg_time = total_time / runs results.append([ maze_name, strategy_name, f"{avg_time:.4f}", stats.visited_cells, stats.path_length ]) print(f" {strategy_name:10} -> " f"Время: {avg_time:8.3f}мс | " f"Посещено: {stats.visited_cells:5} | " f"Длина пути: {stats.path_length:3}") with open("results_all.csv", "w", newline="", encoding="utf-8") as csvfile: writer = csv.writer(csvfile) writer.writerow(["Лабиринт", "Стратегия", "Время (мс)", "Посещено клеток", "Длина пути"]) writer.writerows(results) print("\n" + "=" * 80) print("Все эксперименты завершены!") print("Результаты сохранены в файл 'results_all.csv'") print("=" * 80) def demonstrate_visualization() -> None: """Демонстрация визуализации и паттерна Observer.""" builder = TextMazeBuilder() maze_data = [ "S...#.....", ".###.####.", ".....#....", "####.#####", ".....#....", ".#######..", "..........", "######.###", "..........", ".........E" ] maze = builder.build_from_string_list(maze_data) strategy = AStarStrategy() solver = MazeSolver(maze, strategy) console_view = ConsoleView() solver.add_observer(console_view) print("\nДЕМОНСТРАЦИЯ ВИЗУАЛИЗАЦИИ") print("=" * 40) path, stats = solver.solve() print("\nНайденный путь:") console_view.render(maze, path) print(f"\nСтатистика: {stats}") print(f" Время: {stats.time_ms:.3f}мс") print(f" Посещено клеток: {stats.visited_cells}") print(f" Длина пути: {stats.path_length}") if __name__ == "__main__": demonstrate_visualization() run_experiments()