import time import csv import heapq from collections import deque from abc import ABC, abstractmethod import matplotlib.pyplot as plt import pandas as pd from dataclasses import dataclass import os class Cell: """Клетка лабиринта""" def __init__(self, x, y, is_wall=False): self.x = x self.y = y self.is_wall = is_wall self.is_start = False self.is_exit = False def is_passable(self): return not self.is_wall 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 = [] for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]: nx, ny = cell.x + dx, cell.y + dy nb = self.get_cell(nx, ny) if nb and nb.is_passable(): neighbors.append(nb) return neighbors def __str__(self): result = "" for y in range(self.height): for x in range(self.width): cell = self.get_cell(x, y) if cell is None: result += "?" elif cell.is_wall: result += "#" elif cell.is_start: result += "S" elif cell.is_exit: result += "E" else: result += " " result += "\n" return result class MazeBuilder(ABC): @abstractmethod def build_from_file(self, filename): pass class TextFileMazeBuilder(MazeBuilder): def build_from_file(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) maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): cell = maze.get_cell(x, y) if ch == '#': cell.is_wall = True elif ch == 'S': cell.is_start = True maze.start = cell elif ch == 'E': cell.is_exit = True maze.exit = cell else: cell.is_wall = False return maze class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze, start, exit): pass class BFSStrategy(PathFindingStrategy): """Поиск в ширину""" def find_path(self, maze, start, exit): visited = set() if start == exit: return [start], 1 queue = deque([start]) visited.add(start) parent = {start: None} while queue: current = queue.popleft() for nb in maze.get_neighbors(current): if nb not in visited: visited.add(nb) parent[nb] = current if nb == exit: path = [] node = nb while node is not None: path.append(node) node = parent[node] path.reverse() return path, len(visited) queue.append(nb) return [], len(visited) class DFSStrategy(PathFindingStrategy): """Поиск в глубину""" def find_path(self, maze, start, exit): visited = set() stack = [(start, [start])] while stack: current, path = stack.pop() if current == exit: return path, len(visited) visited.add(current) for nb in maze.get_neighbors(current): if nb not in visited: stack.append((nb, path + [nb])) return [], len(visited) class AStarStrategy(PathFindingStrategy): """Алгоритм A""" def heuristic(self, cell, exit): return abs(cell.x - exit.x) + abs(cell.y - exit.y) def find_path(self, maze, start, exit): open_set = [] counter = 0 heapq.heappush(open_set, (0, counter, start)) counter += 1 came_from = {} g_score = {start: 0} f_score = {start: self.heuristic(start, exit)} visited = set() while open_set: _, _, current = heapq.heappop(open_set) visited.add(current) if current == exit: path = [] node = current while node in came_from: path.append(node) node = came_from[node] path.append(start) path.reverse() return path, len(visited) for nb in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if tentative_g < g_score.get(nb, float('inf')): came_from[nb] = current g_score[nb] = tentative_g f = tentative_g + self.heuristic(nb, exit) heapq.heappush(open_set, (f, counter, nb)) counter += 1 return [], len(visited) @dataclass class SearchStats: time_ms: float visited_cells: int path_length: int algorithm: str class MazeSolver: def __init__(self, maze, strategy): self.maze = maze self.strategy = strategy def set_strategy(self, strategy): self.strategy = strategy def solve(self): if self.maze.start is None or self.maze.exit is None: raise ValueError("Лабиринт не имеет старта или выхода") start_time = time.perf_counter() path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() stats = SearchStats( time_ms=(end_time - start_time) * 1000, visited_cells=visited, path_length=len(path), algorithm=self.strategy.__class__.__name__ ) return path, stats