import time import csv from collections import deque import heapq from abc import ABC, abstractmethod class Cell: def __init__(self, x: int, y: int): self.x = x self.y = y self.isWall = False self.isStart = False self.isExit = False self.weight = 1 def isPassable(self) -> bool: return not self.isWall def __lt__(self, other): return (self.x, self.y) < (other.x, other.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 = None self.exit = None def getCell(self, x: int, y: int) -> Cell: if 0 <= x < self.width and 0 <= y < self.height: return self.cells[x][y] return None def getNeighbors(self, cell: 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.getCell(nx, ny) if neighbor and neighbor.isPassable(): neighbors.append(neighbor) return neighbors class MazeBuilder(ABC): @abstractmethod def buildFromStringList(self, lines: list) -> Maze: pass class TextMazeBuilder(MazeBuilder): def buildFromStringList(self, lines: list) -> 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.getCell(x, y) if char == '#': cell.isWall = True elif char == 'S': cell.isStart = True maze.start = cell elif char == 'E': cell.isExit = 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 findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: pass def _reconstruct_path(self, came_from: dict, start: Cell, exit: Cell) -> list: 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 findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: 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.getNeighbors(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 findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: 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.getNeighbors(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 findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: self.visited_count = 0 def heuristic(a, b): return abs(a.x - b.x) + abs(a.y - b.y) pq = [] heapq.heappush(pq, (0, start)) came_from = {start: None} g_score = {start: 0} while pq: _, current = heapq.heappop(pq) self.visited_count += 1 if current == exit: break for neighbor in maze.getNeighbors(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(pq, (f_score, neighbor)) return self._reconstruct_path(came_from, start, exit) class DijkstraStrategy(PathFindingStrategy): def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: self.visited_count = 0 pq = [] heapq.heappush(pq, (0, start)) came_from = {start: None} g_score = {start: 0} while pq: current_g, current = heapq.heappop(pq) self.visited_count += 1 if current == exit: break for neighbor in maze.getNeighbors(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(pq, (tentative_g_score, neighbor)) return self._reconstruct_path(came_from, start, exit)