import sys import time from collections import deque import heapq from dataclasses import dataclass 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, cells, start=None, exit=None): self.width = width self.height = height self.cells = cells self.start = start self.exit = exit 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 neighbor = self.get_cell(nx, ny) if neighbor and neighbor.is_passable(): neighbors.append(neighbor) return neighbors class MazeBuilder: @staticmethod def build_from_file(filename): with open(filename, 'r') as f: lines = [line.rstrip('\n') for line in f] if not lines: raise ValueError("Empty file") height = len(lines) width = max(len(line) for line in lines) cells = [] start = None exit_cell = None for y, line in enumerate(lines): row = [] for x in range(width): ch = line[x] if x < len(line) else ' ' is_wall = (ch == '#') is_start = (ch == 'S') is_exit = (ch == 'E') if is_start: start = Cell(x, y, False, True, False) row.append(start) elif is_exit: exit_cell = Cell(x, y, False, False, True) row.append(exit_cell) else: row.append(Cell(x, y, is_wall, False, False)) cells.append(row) if start is None: raise ValueError("No start cell (S) found") if exit_cell is None: raise ValueError("No exit cell (E) found") return Maze(width, height, cells, start, exit_cell) class PathFindingStrategy: def find_path(self, maze, start, exit): raise NotImplementedError class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if start == exit: return [start], 1 queue = deque([start]) visited = {start} parent = {start: None} visited_count = 1 while queue: current = queue.popleft() if current == exit: path = [] while current: path.append(current) current = parent[current] path.reverse() return path, visited_count for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) parent[neighbor] = current queue.append(neighbor) visited_count += 1 return [], visited_count class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if start == exit: return [start], 1 stack = [start] visited = {start} parent = {start: None} visited_count = 1 while stack: current = stack.pop() if current == exit: path = [] while current: path.append(current) current = parent[current] path.reverse() return path, visited_count for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) parent[neighbor] = current stack.append(neighbor) visited_count += 1 return [], visited_count class AStarStrategy(PathFindingStrategy): @staticmethod def manhattan(cell, target): return abs(cell.x - target.x) + abs(cell.y - target.y) def find_path(self, maze, start, exit): if start == exit: return [start], 1 open_set = [] counter = 0 heapq.heappush(open_set, (0, counter, start)) g_score = {start: 0} f_score = {start: self.manhattan(start, exit)} parent = {start: None} visited_count = 0 visited = set() while open_set: _, _, current = heapq.heappop(open_set) if current in visited: continue visited.add(current) visited_count += 1 if current == exit: path = [] while current: path.append(current) current = parent[current] path.reverse() return path, visited_count 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]: parent[neighbor] = current g_score[neighbor] = tentative_g f = tentative_g + self.manhattan(neighbor, exit) f_score[neighbor] = f counter += 1 heapq.heappush(open_set, (f, counter, neighbor)) return [], visited_count @dataclass class SearchStats: time_ms: float visited_cells: int path_length: int class MazeSolver: def __init__(self, maze, strategy=None): self.maze = maze self.strategy = strategy def set_strategy(self, strategy): self.strategy = strategy def solve(self): if self.strategy is None: raise ValueError("Strategy not set") start_time = time.perf_counter() path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() time_ms = (end_time - start_time) * 1000 return path, SearchStats(time_ms, visited, len(path) if path else 0) def print_maze(maze, path=None): path_set = set(path) if path else set() for y in range(maze.height): row = [] for x in range(maze.width): cell = maze.get_cell(x, y) if cell in path_set and not cell.is_start and not cell.is_exit: row.append('*') elif cell.is_start: row.append('S') elif cell.is_exit: row.append('E') elif cell.is_wall: row.append('#') else: row.append(' ') print(''.join(row)) def main(): if len(sys.argv) > 1: filename = sys.argv[1] else: filename = 'maze1.txt' try: maze = MazeBuilder.build_from_file(filename) print(f"Maze loaded ({maze.width}x{maze.height})") print("Select algorithm: (1) BFS, (2) DFS, (3) A*") choice = input("Choice: ").strip() if choice == '1': strategy = BFSStrategy() elif choice == '2': strategy = DFSStrategy() elif choice == '3': strategy = AStarStrategy() else: print("Invalid, using BFS") strategy = BFSStrategy() solver = MazeSolver(maze, strategy) path, stats = solver.solve() if path: print(f"Path found! Length: {len(path)}") print(f"Visited cells: {stats.visited_cells}") print(f"Time: {stats.time_ms:.4f} ms") print_maze(maze, path) else: print("No path found.") print(f"Visited cells: {stats.visited_cells}") print(f"Time: {stats.time_ms:.4f} ms") except Exception as e: print(f"Error: {e}") if __name__ == '__main__': main()