Initial commit

This commit is contained in:
2025-11-20 22:58:11 -05:00
commit 6d75c8e94e
51 changed files with 5141 additions and 0 deletions

7
src/solvers/__init__.py Normal file
View 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
View 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
View 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
View 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