Initial commit
This commit is contained in:
7
src/solvers/__init__.py
Normal file
7
src/solvers/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Maze solving algorithms."""
|
||||
|
||||
from .base import BaseSolver
|
||||
from .dfs import DFSSolver
|
||||
from .bfs import BFSSolver
|
||||
|
||||
__all__ = ["BaseSolver", "DFSSolver", "BFSSolver"]
|
||||
139
src/solvers/base.py
Normal file
139
src/solvers/base.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Base class for maze solving algorithms."""
|
||||
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from ..core.cell import Cell
|
||||
from ..core.maze import Maze
|
||||
|
||||
|
||||
class BaseSolver(ABC):
|
||||
"""Abstract base class for maze solving algorithms.
|
||||
|
||||
All maze solving algorithms should inherit from this class
|
||||
and implement the solve method.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
"""Initialize the solver.
|
||||
|
||||
Args:
|
||||
name: Name of the algorithm
|
||||
"""
|
||||
self.name = name
|
||||
|
||||
@abstractmethod
|
||||
def _solve_maze(self, maze: Maze, start: Cell, end: Cell) -> Tuple[Optional[List[Cell]], List[Cell]]:
|
||||
"""Solve the maze (to be implemented by subclasses).
|
||||
|
||||
Args:
|
||||
maze: Maze instance to solve
|
||||
start: Starting cell
|
||||
end: Ending cell
|
||||
|
||||
Returns:
|
||||
Tuple of (solution_path, visited_cells_in_order)
|
||||
"""
|
||||
pass
|
||||
|
||||
def solve(self, maze: Maze) -> Dict:
|
||||
"""Solve the maze and return solution with metadata.
|
||||
|
||||
Args:
|
||||
maze: Maze instance to solve
|
||||
|
||||
Returns:
|
||||
Dictionary with solution path, visited cells, and timing
|
||||
"""
|
||||
# Reset visited flags
|
||||
maze.reset_visited()
|
||||
|
||||
# Get start and end cells
|
||||
start = maze.get_cell(maze.start[0], maze.start[1])
|
||||
end = maze.get_cell(maze.end[0], maze.end[1])
|
||||
|
||||
if not start or not end:
|
||||
return {
|
||||
"success": False,
|
||||
"path": None,
|
||||
"visited": [],
|
||||
"time_ms": 0,
|
||||
"algorithm": self.name,
|
||||
"path_length": 0
|
||||
}
|
||||
|
||||
# Track solving time
|
||||
start_time = time.time()
|
||||
path, visited = self._solve_maze(maze, start, end)
|
||||
end_time = time.time()
|
||||
|
||||
# Reset visited flags again
|
||||
maze.reset_visited()
|
||||
|
||||
return {
|
||||
"success": path is not None,
|
||||
"path": [(cell.row, cell.col) for cell in path] if path else None,
|
||||
"visited": [(cell.row, cell.col) for cell in visited],
|
||||
"time_ms": (end_time - start_time) * 1000,
|
||||
"algorithm": self.name,
|
||||
"path_length": len(path) if path else 0
|
||||
}
|
||||
|
||||
def can_move(self, maze: Maze, from_cell: Cell, to_cell: Cell) -> bool:
|
||||
"""Check if movement between two cells is possible.
|
||||
|
||||
Args:
|
||||
maze: The maze
|
||||
from_cell: Starting cell
|
||||
to_cell: Target cell
|
||||
|
||||
Returns:
|
||||
True if movement is possible, False otherwise
|
||||
"""
|
||||
# Calculate direction
|
||||
row_diff = to_cell.row - from_cell.row
|
||||
col_diff = to_cell.col - from_cell.col
|
||||
|
||||
# Determine direction
|
||||
if row_diff == -1:
|
||||
direction = "north"
|
||||
elif row_diff == 1:
|
||||
direction = "south"
|
||||
elif col_diff == -1:
|
||||
direction = "west"
|
||||
elif col_diff == 1:
|
||||
direction = "east"
|
||||
else:
|
||||
return False
|
||||
|
||||
# Check if wall exists
|
||||
return not from_cell.has_wall(direction)
|
||||
|
||||
def reconstruct_path(self, came_from: Dict[Cell, Cell], start: Cell, end: Cell) -> List[Cell]:
|
||||
"""Reconstruct path from start to end using came_from dict.
|
||||
|
||||
Args:
|
||||
came_from: Dictionary mapping cell to its predecessor
|
||||
start: Starting cell
|
||||
end: Ending cell
|
||||
|
||||
Returns:
|
||||
List of cells forming the path
|
||||
"""
|
||||
path = []
|
||||
current = end
|
||||
|
||||
while current != start:
|
||||
path.append(current)
|
||||
current = came_from.get(current)
|
||||
if current is None:
|
||||
return []
|
||||
|
||||
path.append(start)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation of the solver."""
|
||||
return f"{self.__class__.__name__}('{self.name}')"
|
||||
62
src/solvers/bfs.py
Normal file
62
src/solvers/bfs.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""Breadth-First Search maze solver."""
|
||||
|
||||
from collections import deque
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from ..core.cell import Cell
|
||||
from ..core.maze import Maze
|
||||
from .base import BaseSolver
|
||||
|
||||
|
||||
class BFSSolver(BaseSolver):
|
||||
"""Solves mazes using Breadth-First Search.
|
||||
|
||||
BFS explores all neighbors at the current depth before moving deeper.
|
||||
Guarantees the shortest path in unweighted graphs.
|
||||
|
||||
Time Complexity: O(V + E) where V is vertices and E is edges
|
||||
Space Complexity: O(V) for the queue
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the BFS solver."""
|
||||
super().__init__("Breadth-First Search (BFS)")
|
||||
|
||||
def _solve_maze(self, maze: Maze, start: Cell, end: Cell) -> Tuple[Optional[List[Cell]], List[Cell]]:
|
||||
"""Solve maze using BFS.
|
||||
|
||||
Args:
|
||||
maze: Maze instance to solve
|
||||
start: Starting cell
|
||||
end: Ending cell
|
||||
|
||||
Returns:
|
||||
Tuple of (solution_path, visited_cells_in_order)
|
||||
"""
|
||||
queue = deque([start])
|
||||
came_from: Dict[Cell, Cell] = {}
|
||||
visited_order = []
|
||||
|
||||
start.visited = True
|
||||
visited_order.append(start)
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
# Check if we reached the end
|
||||
if current == end:
|
||||
path = self.reconstruct_path(came_from, start, end)
|
||||
return path, visited_order
|
||||
|
||||
# Explore neighbors
|
||||
neighbors = maze.get_neighbors(current)
|
||||
|
||||
for neighbor, direction in neighbors:
|
||||
if not neighbor.visited and self.can_move(maze, current, neighbor):
|
||||
neighbor.visited = True
|
||||
visited_order.append(neighbor)
|
||||
came_from[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
# No path found
|
||||
return None, visited_order
|
||||
61
src/solvers/dfs.py
Normal file
61
src/solvers/dfs.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Depth-First Search maze solver."""
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from ..core.cell import Cell
|
||||
from ..core.maze import Maze
|
||||
from .base import BaseSolver
|
||||
|
||||
|
||||
class DFSSolver(BaseSolver):
|
||||
"""Solves mazes using Depth-First Search.
|
||||
|
||||
DFS explores as far as possible along each branch before backtracking.
|
||||
Does not guarantee shortest path but is memory efficient.
|
||||
|
||||
Time Complexity: O(V + E) where V is vertices and E is edges
|
||||
Space Complexity: O(V) for the stack
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the DFS solver."""
|
||||
super().__init__("Depth-First Search (DFS)")
|
||||
|
||||
def _solve_maze(self, maze: Maze, start: Cell, end: Cell) -> Tuple[Optional[List[Cell]], List[Cell]]:
|
||||
"""Solve maze using DFS.
|
||||
|
||||
Args:
|
||||
maze: Maze instance to solve
|
||||
start: Starting cell
|
||||
end: Ending cell
|
||||
|
||||
Returns:
|
||||
Tuple of (solution_path, visited_cells_in_order)
|
||||
"""
|
||||
stack = [start]
|
||||
came_from: Dict[Cell, Cell] = {}
|
||||
visited_order = []
|
||||
|
||||
start.visited = True
|
||||
visited_order.append(start)
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
|
||||
# Check if we reached the end
|
||||
if current == end:
|
||||
path = self.reconstruct_path(came_from, start, end)
|
||||
return path, visited_order
|
||||
|
||||
# Explore neighbors
|
||||
neighbors = maze.get_neighbors(current)
|
||||
|
||||
for neighbor, direction in neighbors:
|
||||
if not neighbor.visited and self.can_move(maze, current, neighbor):
|
||||
neighbor.visited = True
|
||||
visited_order.append(neighbor)
|
||||
came_from[neighbor] = current
|
||||
stack.append(neighbor)
|
||||
|
||||
# No path found
|
||||
return None, visited_order
|
||||
Reference in New Issue
Block a user