~shreyasminocha/maze-solver

ee50a7de6767e17b4241e3a3db9fb5e9115c2bde — Shreyas Minocha 9 months ago
Initialize repository
2 files changed, 98 insertions(+), 0 deletions(-)

A README.md
A maze.py
A  => README.md +1 -0
@@ 1,1 @@
Need a shitty maze solver for a CTF? Well here you go.

A  => maze.py +97 -0
@@ 1,97 @@
from collections import defaultdict
from enum import Enum
from typing import Dict, List, Set, Tuple


class Direction(Enum):
    Up = "n"
    Down = "s"
    Left = "w"
    Right = "e"


Maze = List[str]
CellCoords = Tuple[int, int]
Graph = Dict[CellCoords, Set[CellCoords]]


def solve_maze(
    maze: Maze, start_indicator: str, goal_indicator: str, wall_indicator: str
) -> List[Direction]:
    assert len(set(map(len, maze))) == 1
    assert len(start_indicator) == len(goal_indicator) == len(wall_indicator) == 1

    start, goal = None, None
    for r, row in enumerate(maze):
        for c, cell in enumerate(row):
            if cell == start_indicator:
                start = (r, c)
            if cell == goal_indicator:
                goal = (r, c)

    if start is None:
        raise ValueError("start not found")
    if goal is None:
        raise ValueError("goal not found")

    def graphify(grid: Maze) -> Graph:
        m, n = len(grid), len(grid[0])

        graph = defaultdict(set)
        for r, row in enumerate(grid):
            for c, cell in enumerate(row):
                for dr, dc in [(0, -1), (0, 1), (1, 0), (-1, 0)]:
                    if 0 <= r + dr < m and 0 <= c + dc < n:
                        if cell != wall_indicator:
                            if grid[r + dr][c + dc] != wall_indicator:
                                graph[(r, c)].add((r + dr, c + dc))
                                graph[(r + dr, c + dc)].add((r, c))

        return graph

    def bfs(graph: Graph, node: CellCoords) -> Dict[CellCoords, CellCoords]:
        queue = [node]
        distance: Dict[CellCoords, float] = defaultdict(lambda: float("inf"))

        distance[node] = 0
        visited = {node: True}
        ancestors = {node: node}

        while queue:
            curr = queue.pop()
            visited[curr] = True
            for nbr in graph[curr]:
                distance[nbr] = min(distance[nbr], distance[curr] + 1)
                if nbr not in visited:
                    ancestors[nbr] = curr
                    queue.append(nbr)

        return ancestors

    def traceback(
        anc: Dict[CellCoords, CellCoords], start: CellCoords, goal: CellCoords
    ) -> List[Direction]:
        curr = goal
        directions = []
        while curr != start:
            new = anc[curr]
            if curr[0] > new[0]:
                directions.append(Direction.Down)
            elif curr[1] > new[1]:
                directions.append(Direction.Right)
            elif curr[1] < new[1]:
                directions.append(Direction.Left)
            elif curr[0] < new[0]:
                directions.append(Direction.Up)
            else:
                raise ValueError("unreachable destination?")

            curr = new
        return directions[::-1]

    graph = graphify(maze)

    ancestors = bfs(graph, start)
    dirs = traceback(ancestors, start, goal)

    return dirs