import heapq import time import csv from abc import ABC, abstractmethod 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 class Maze: def __init__(self, width, height): self.width = width self.height = height self.grid = [[None for _ in range(width)] for _ in range(height)] self.start = None self.exit = None def set_cell(self, x, y, cell): self.grid[y][x] = cell if cell.is_start: self.start = cell if cell.is_exit: self.exit = cell def get_cell(self, x, y): if 0 <= x < self.width and 0 <= y < self.height: return self.grid[y][x] return None def get_neighbors(self, 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_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 = len(lines[0]) if height > 0 else 0 maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): is_wall = (ch == '#') is_start = (ch == 'S') is_exit = (ch == 'E') is_passable = (ch == ' ' or is_start or is_exit) cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) maze.set_cell(x, y, cell) return maze class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze, start, exit): pass class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if not start or not exit: return [] queue = [(start, [start])] visited = set() while queue: current, path = queue.pop(0) if current == exit: return path if current in visited: continue visited.add(current) for neighbor in maze.get_neighbors(current): if neighbor not in visited: queue.append((neighbor, path + [neighbor])) return [] class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if not start or not exit: return [] stack = [(start, [start])] visited = set() while stack: current, path = stack.pop() if current == exit: return path if current in visited: continue visited.add(current) for neighbor in maze.get_neighbors(current): if neighbor not in visited: stack.append((neighbor, path + [neighbor])) return [] class AStarStrategy(PathFindingStrategy): def heuristic(self, cell, exit): return abs(cell.x - exit.x) + abs(cell.y - exit.y) def find_path(self, maze, start, exit): if not start or not exit: return [] open_set = [(0, id(start), start)] came_from = {} g_score = {start: 0} f_score = {start: self.heuristic(start, exit)} while open_set: _, _, current = heapq.heappop(open_set) if current == exit: path = [] while current in came_from: path.append(current) current = came_from[current] path.append(start) path.reverse() return path for neighbor in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g f_score[neighbor] = tentative_g + self.heuristic(neighbor, exit) heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor)) return [] class DijkstraStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if not start or not exit: return [] pq = [(0, id(start), start)] distances = {start: 0} came_from = {} while pq: dist, _, current = heapq.heappop(pq) if current == exit: path = [] while current in came_from: path.append(current) current = came_from[current] path.append(start) path.reverse() return path if dist > distances.get(current, float('inf')): continue for neighbor in maze.get_neighbors(current): new_dist = dist + 1 if new_dist < distances.get(neighbor, float('inf')): distances[neighbor] = new_dist came_from[neighbor] = current heapq.heappush(pq, (new_dist, id(neighbor), neighbor)) return [] class SearchStats: def __init__(self, time_ms, visited_cells, path_length, path=None): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length self.path = path class MazeSolver: def __init__(self, maze, strategy=None): self.maze = maze self.strategy = strategy self.observers = [] def set_strategy(self, strategy): self.strategy = strategy def attach(self, observer): self.observers.append(observer) def notify(self, event): for observer in self.observers: observer.update(event) def solve(self): if not self.strategy: raise ValueError("Strategy not set") 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 visited_cells = len(path) if path else 0 path_length = len(path) if path else 0 self.notify(f"Path found with length {path_length} in {time_ms:.2f}ms") return SearchStats(time_ms, visited_cells, path_length, path) class Observer(ABC): @abstractmethod def update(self, event): pass class ConsoleView(Observer): def __init__(self): self.last_path = None def update(self, event): print(f"[ConsoleView] {event}") def render(self, maze, player_pos=None, path=None): print("\n" + "=" * (maze.width * 2 + 2)) for y in range(maze.height): row = "" for x in range(maze.width): cell = maze.get_cell(x, y) if player_pos and cell == player_pos: row += "P " elif path and cell in path: row += "* " elif cell.is_start: row += "S " elif cell.is_exit: row += "E " elif cell.is_wall: row += "# " else: row += ". " print(row) print("=" * (maze.width * 2 + 2)) class Command(ABC): @abstractmethod def execute(self): pass @abstractmethod def undo(self): pass class Player: def __init__(self, start_cell): self.current = start_cell self.start = start_cell def move_to(self, cell): self.current = cell class MoveCommand(Command): def __init__(self, player, new_cell, maze): self.player = player self.new_cell = new_cell self.old_cell = player.current self.maze = maze def execute(self): if self.new_cell.is_passable(): self.player.move_to(self.new_cell) return True return False def undo(self): self.player.move_to(self.old_cell) def generate_test_mazes(): mazes = {} simple_maze = Maze(5, 5) for y in range(5): for x in range(5): is_wall = (x == 2 and y == 1) or (x == 2 and y == 2) or (x == 2 and y == 3) is_start = (x == 0 and y == 0) is_exit = (x == 4 and y == 4) cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit) simple_maze.set_cell(x, y, cell) mazes["simple"] = simple_maze empty_maze = Maze(20, 20) for y in range(20): for x in range(20): is_start = (x == 0 and y == 0) is_exit = (x == 19 and y == 19) cell = Cell(x, y, is_wall=False, is_start=is_start, is_exit=is_exit) empty_maze.set_cell(x, y, cell) mazes["empty"] = empty_maze return mazes def run_experiments(): mazes = generate_test_mazes() strategies = { "BFS": BFSStrategy(), "DFS": DFSStrategy(), "AStar": AStarStrategy(), "Dijkstra": DijkstraStrategy() } results = [] for maze_name, maze in mazes.items(): for strat_name, strategy in strategies.items(): solver = MazeSolver(maze, strategy) times = [] visited_counts = [] path_lengths = [] for run in range(5): stats = solver.solve() times.append(stats.time_ms) visited_counts.append(stats.visited_cells) path_lengths.append(stats.path_length) avg_time = sum(times) / len(times) avg_visited = sum(visited_counts) / len(visited_counts) avg_length = sum(path_lengths) / len(path_lengths) results.append([maze_name, strat_name, avg_time, avg_visited, avg_length]) print(f"{maze_name} | {strat_name}: {avg_time:.3f}ms, {avg_visited:.0f} cells, {avg_length:.0f} length") with open("maze_results.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Лабиринт", "Стратегия", "Время_мс", "Посещено_клеток", "Длина_пути"]) writer.writerows(results) return results def main(): print("Testing maze loading...") builder = TextFileMazeBuilder() try: maze = builder.build_from_file("maze.txt") print(f"Maze loaded: {maze.width}x{maze.height}") solver = MazeSolver(maze) view = ConsoleView() solver.attach(view) strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()] for strategy in strategies: solver.set_strategy(strategy) print(f"\n--- {strategy.__class__.__name__} ---") stats = solver.solve() view.render(maze, path=stats.path) print(f"Time: {stats.time_ms:.3f}ms, Path length: {stats.path_length}") except FileNotFoundError: print("maze.txt not found, running experiments with generated mazes instead") print("\n" + "="*50) print("Running experiments...") run_experiments() if __name__ == "__main__": main()