import sys import csv from collections import deque import heapq import time import matplotlib.pyplot as plt import numpy as np class Cell: def __init__(self, x, y): self._x = x self._y = y self._is_wall = False self._is_start = False self._is_exit = False @property def x(self): return self._x @property def y(self): return self._y @property def is_wall(self): return self._is_wall @is_wall.setter def is_wall(self, value): self._is_wall = value @property def is_start(self): return self._is_start @is_start.setter def is_start(self, value): self._is_start = value @property def is_exit(self): return self._is_exit @is_exit.setter def is_exit(self, value): self._is_exit = value 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 @property def width(self): return self._width @property def height(self): return self._height @property def start(self): return self._start @property def exit(self): return self._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 set_cell(self, x, y, cell_type): cell = self.get_cell(x, y) if cell is None: return if cell_type == 'wall': cell.is_wall = True elif cell_type == 'start': if self._start: self._start.is_start = False cell.is_start = True cell.is_wall = False self._start = cell elif cell_type == 'exit': if self._exit: self._exit.is_exit = False cell.is_exit = True cell.is_wall = False self._exit = cell elif cell_type == 'path': cell.is_wall = False 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: def build_from_file(self, filename): raise NotImplementedError class TextFileMazeBuilder(MazeBuilder): def build_from_file(self, filename): with open(filename, 'r') 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 start_en = 0 exit_en = 0 maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): if ch == "#": maze.set_cell(x, y, "wall") elif ch == "S": maze.set_cell(x, y, "start") start_en += 1 elif ch == "E": maze.set_cell(x, y, "exit") exit_en += 1 else: maze.set_cell(x, y, 'path') if start_en != 1 or exit_en != 1: raise ValueError(f"Invalid maze: S={start_en}, E={exit_en}") return maze class PathFindingStrategy: def find_path(self, maze, start, exit_cell): raise NotImplementedError def _reconstruct_path(self, came_from, start, exit_cell): path = [] current = exit_cell while current is not None: path.append(current) current = came_from.get(current) path.reverse() return path def get_visited_count(self): return getattr(self, '_visited_count', 0) class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit_cell): queue = deque() queue.append(start) came_from = {start: None} visited = {start} while queue: current = queue.popleft() if current == exit_cell: self._visited_count = len(visited) return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) came_from[neighbor] = current queue.append(neighbor) self._visited_count = len(visited) return [] class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit_cell): stack = [start] came_from = {start: None} visited = {start} while stack: current = stack.pop() if current == exit_cell: self._visited_count = len(visited) return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) came_from[neighbor] = current stack.append(neighbor) self._visited_count = len(visited) return [] class AStarStrategy(PathFindingStrategy): def _heuristic(self, cell, exit_cell): return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) def find_path(self, maze, start, exit_cell): heap = [] counter = 0 start_f = self._heuristic(start, exit_cell) heapq.heappush(heap, (start_f, counter, start)) counter += 1 came_from = {} g_score = {start: 0} f_score = {start: start_f} visited = set() while heap: current_f, _, current = heapq.heappop(heap) visited.add(current) if current == exit_cell: self._visited_count = len(visited) return self._reconstruct_path(came_from, start, exit_cell) 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_cell) f_score[neighbor] = new_f heapq.heappush(heap, (new_f, counter, neighbor)) counter += 1 self._visited_count = len(visited) return [] class MazeSolver: def __init__(self, maze): self._maze = maze self._strategy = None def set_strategy(self, strategy): self._strategy = strategy def solve(self): if self._strategy is None: return None 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 return { 'time_ms': time_ms, 'visited_cells': self._strategy.get_visited_count(), 'path_length': len(path) } def run_experiment(maze_file, strategy, runs=5): builder = TextFileMazeBuilder() maze = builder.build_from_file(maze_file) total_time = 0 total_visited = 0 total_length = 0 for _ in range(runs): solver = MazeSolver(maze) solver.set_strategy(strategy) stats = solver.solve() if stats: total_time += stats['time_ms'] total_visited += stats['visited_cells'] total_length += stats['path_length'] return { 'time_ms': total_time / runs, 'visited_cells': total_visited / runs, 'path_length': total_length / runs } def generate_plots(results): mazes = list(set([r['maze'] for r in results])) strategies = ['BFS', 'DFS', 'AStar'] fig, axes = plt.subplots(1, 3, figsize=(15, 5)) x = np.arange(len(mazes)) width = 0.25 for i, strat in enumerate(strategies): times = [] for maze in mazes: val = next((r['time_ms'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) times.append(val) axes[0].bar(x + i*width, times, width, label=strat) axes[0].set_xlabel('Maze') axes[0].set_ylabel('Time (ms)') axes[0].set_title('Execution Time Comparison') axes[0].set_xticks(x + width) axes[0].set_xticklabels(mazes, rotation=45, ha='right') axes[0].legend() axes[0].grid(True, alpha=0.3) for i, strat in enumerate(strategies): visited = [] for maze in mazes: val = next((r['visited_cells'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) visited.append(val) axes[1].bar(x + i*width, visited, width, label=strat) axes[1].set_xlabel('Maze') axes[1].set_ylabel('Visited Cells') axes[1].set_title('Visited Cells Comparison') axes[1].set_xticks(x + width) axes[1].set_xticklabels(mazes, rotation=45, ha='right') axes[1].legend() axes[1].grid(True, alpha=0.3) for i, strat in enumerate(strategies): lengths = [] for maze in mazes: val = next((r['path_length'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) lengths.append(val) axes[2].bar(x + i*width, lengths, width, label=strat) axes[2].set_xlabel('Maze') axes[2].set_ylabel('Path Length') axes[2].set_title('Path Length Comparison') axes[2].set_xticks(x + width) axes[2].set_xticklabels(mazes, rotation=45, ha='right') axes[2].legend() axes[2].grid(True, alpha=0.3) plt.tight_layout() plt.savefig('performance_comparison_2-nd-exercise.png', dpi=150, bbox_inches='tight') plt.show() if __name__ == "__main__": mazes = [ ("maze1.txt", "Small 10x6"), ("maze10x10.txt", "Medium 10x10"), ("maze20x20.txt", "Large 20x20"), ("maze_empty.txt", "Empty 15x15"), ("maze_no_exit.txt", "No exit 10x10") ] strategies = [ ("BFS", BFSStrategy()), ("DFS", DFSStrategy()), ("AStar", AStarStrategy()) ] results = [] for maze_file, maze_name in mazes: print(f"Testing {maze_name}...") for strat_name, strat in strategies: try: stats = run_experiment(maze_file, strat, runs=3) results.append({ 'maze': maze_name, 'strategy': strat_name, 'time_ms': stats['time_ms'], 'visited_cells': stats['visited_cells'], 'path_length': stats['path_length'] }) print(f" {strat_name}: time={stats['time_ms']:.3f}ms, visited={stats['visited_cells']:.0f}, length={stats['path_length']:.0f}") except Exception as e: print(f" {strat_name}: ERROR - {e}") results.append({ 'maze': maze_name, 'strategy': strat_name, 'time_ms': -1, 'visited_cells': -1, 'path_length': -1 }) valid_results = [r for r in results if r['time_ms'] >= 0] with open('experiment_results_2-nd-exercise.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length']) writer.writeheader() writer.writerows(valid_results) if valid_results: generate_plots(valid_results) print("\nResults saved to experiment_results_2-nd-exercise.csv") print("Plot saved to performance_comparison_2-nd-exercise.png")