Example #1
0
    def __init__(self, fill_type='filler', solver=None):
        if not solver:
            self.solver = ShortestPaths()
        else:
            self.solver = solver

        if fill_type == 'sealer':
            self._remove_dead_end = SEALER
        else:
            self._remove_dead_end = FILLER
Example #2
0
    def testShortestPaths(self):
        """ test against a maze with outer/inner entraces """
        starts = [True, False]
        ends = [True, False]

        for s in starts:
            for e in ends:
                m = self._create_maze_with_varied_entrances(s, e)
                m.solver = ShortestPaths()
                m.solve()

                for sol in m.solutions:
                    self.assertFalse(self._duplicates_in_solution(sol))
                    self.assertTrue(self._one_away(m.start, sol[0]))
                    self.assertTrue(self._one_away(m.end, sol[-1]))
Example #3
0
    def test_shortest_paths(self):
        """ test against a maze with outer/inner entraces """
        starts = [True, False]
        ends = [True, False]

        for s in starts:
            for e in ends:
                m = self.create_maze_with_varied_entrances(s, e)
                m.solver = ShortestPaths()
                m.solve()

                for sol in m.solutions:
                    assert not self.duplicates_in_solution(sol)
                    assert self.one_away(m.start, sol[0])
                    assert self.one_away(m.end, sol[-1])
                    assert self.solution_is_sane(sol)
Example #4
0
class DeadEndFiller(MazeSolveAlgo):
    """
    1. Scan the maze in any order, looking for dead ends.
    2. Fill in each dead end, and any dead-end passages attached to them.
    3. What you will get is a maze with only solution tiles.
    4. Use a different solver (ShortestPaths) to build a solution path.
    """
    def __init__(self, solver=None):
        if not solver:
            self.solver = ShortestPaths()
        else:
            self.solver = solver

    def _solve(self):
        self.grid[self.start] = self.grid[self.end] = 0
        self._fill_dead_ends()

        return self._build_solutions()

    def _build_solutions(self):
        """Now that all of the dead ends have been cut out, the maze still needs to be solved."""
        return self.solver.solve(self.grid, self.start, self.end)

    def _fill_dead_ends(self):
        """fill all dead ends in the maze"""
        # loop through the maze serpentine, and find dead ends
        dead_end = self._find_dead_end()
        while dead_end != (-1, -1):
            # fill-in and wall-off the dead end
            self._fill_dead_end(dead_end)

            # from the dead end, travel one cell
            ns = self._find_unblocked_neighbors(dead_end)

            if len(ns) == 0: break

            # look at the next cell, if it is a dead end, restart the loop
            if len(ns) == 1:
                # continue until you are in a junction cell
                if self._is_dead_end(ns[0]):
                    dead_end = ns[0]
                    continue

            # otherwise, find another dead end in the maze
            dead_end = self._find_dead_end()

    def _fill_dead_end(self, dead_end):
        """After moving from a dead end, we want to fill in it and all
        the walls around it.
        """
        r,c = dead_end
        self.grid[r, c] = 1
        self.grid[r - 1, c] = 1
        self.grid[r + 1, c] = 1
        self.grid[r, c - 1] = 1
        self.grid[r, c + 1] = 1

    def _find_dead_end(self):
        """A "dead end" is a cell with only zero or one open neighbors.
        The start end end count as open.
        """
        for r in range(1, self.grid.height, 2):
            for c in range(1, self.grid.width, 2):
                if (r, c) in [self.start, self.end]:
                    continue
                if self._is_dead_end((r, c)):
                    return (r, c)

        return (-1, -1)

    def _is_dead_end(self, cell):
        """A dead end has zero or one open neighbors."""
        ns = self._find_neighbors(cell)

        if self.grid[cell] == 1:
            return False
        elif len(ns) in [0, 1]:
            return True
        else:
            return False
Example #5
0
 def __init__(self, solver=None):
     if not solver:
         self.solver = ShortestPaths()
     else:
         self.solver = solver
Example #6
0
class DeadEndFiller(MazeSolveAlgo):
    """
    1. Scan the maze in any order, looking for dead ends.
    2. Fill in each dead end, and any dead-end passages attached to them.
    3. What you will get is a maze with only solution tiles.
    4. Use a different solver (ShortestPaths) to build a solution path.
    """
    def __init__(self, solver=None):
        if not solver:
            self.solver = ShortestPaths()
        else:
            self.solver = solver

    def _solve(self):
        self.grid[self.start] = self.grid[self.end] = 0
        self._fill_dead_ends()

        return self._build_solutions()

    def _build_solutions(self):
        """Now that all of the dead ends have been cut out, the maze still needs to be solved."""
        return self.solver.solve(self.grid, self.start, self.end)

    def _fill_dead_ends(self):
        """ fill all dead ends in the maze """
        # loop through the maze serpentine, and find dead ends
        dead_end = self._find_dead_end()
        while dead_end != (-1, -1):
            # fill-in and wall-off the dead end
            self._fill_dead_end(dead_end)

            # from the dead end, travel one cell
            ns = self._find_unblocked_neighbors(dead_end)

            if len(ns) == 0: break

            # look at the next cell, if it is a dead end, restart the loop
            if len(ns) == 1:
                # continue until you are in a junction cell
                if self._is_dead_end(ns[0]):
                    dead_end = ns[0]
                    continue

            # otherwise, find another dead end in the maze
            dead_end = self._find_dead_end()

    def _fill_dead_end(self, dead_end):
        """After moving from a dead end, we want to fill in it and all
        the walls around it.
        """
        r,c = dead_end
        self.grid[r, c] = 1
        self.grid[r - 1, c] = 1
        self.grid[r + 1, c] = 1
        self.grid[r, c - 1] = 1
        self.grid[r, c + 1] = 1

    def _find_dead_end(self):
        """A "dead end" is a cell with only zero or one open neighbors.
        The start end end count as open.
        """
        for r in range(1, self.grid.shape[0], 2):
            for c in range(1, self.grid.shape[1], 2):
                if (r, c) in [self.start, self.end]:
                    continue
                if self._is_dead_end((r, c)):
                    return (r, c)

        return (-1, -1)

    def _is_dead_end(self, cell):
        """A dead end has zero or one open neighbors."""
        ns = self._find_neighbors(cell)

        if self.grid[cell] == 1:
            return False
        elif len(ns) in [0, 1]:
            return True
        else:
            return False
Example #7
0
 def __init__(self, solver=None):
     if not solver:
         self.solver = ShortestPaths()
     else:
         self.solver = solver
Example #8
0
class BlindAlley(MazeSolveAlgo):
    """
    1. Scan the maze, identify all fully-connected wall systems.
    2. Any wall system that touches the border is not a cul-de-sac, remove it.
    3. Determine if remaining wall systems are cul-de-sacs.
    4. If so, add a wall segment to turn the cul-de-sac into a dead end.
    5. Solve using Dead End Filler.
    """
    def __init__(self, fill_type='filler', solver=None):
        if not solver:
            self.solver = ShortestPaths()
        else:
            self.solver = solver

        if fill_type == 'filler':
            self._remove_dead_end = self._dead_end_filler
        elif fill_type == 'sealer':
            self._remove_dead_end = self._dead_end_sealer
        else:
            self._remove_dead_end = self._dead_end_filler

    def _solve(self):
        self._seal_culdesacs()
        self._fill_dead_ends()

        return self._build_solutions()

    def _seal_culdesacs(self):
        """ identify and seal off all culdesacs """
        # identify all fully-connected wall systems
        walls = self._find_wall_systems()
        # connect wall systems that are disconnected above
        walls = self._reduce_wall_systems(walls)

        # remove any wall system that touches the maze boundary
        walls = self._remove_border_walls(walls)

        for wall in walls:
            border = self._find_bordering_cells(wall)
            if self._wall_is_culdesac(border):
                self._fix_culdesac(border)

    def _reduce_wall_systems(self, walls):
        """ Reduce a collection of walls in a maze to realize
            when two walls are actually connected and should be one.
        """
        N = len(walls)

        for i in range(N - 1):
            if walls[i] is None:
                continue
            for j in range(i, N):
                if walls[j] is None:
                    continue
                if self._walls_are_connected(walls[i], walls[j]):
                    walls[i] += walls[j]
                    walls[j] = None

        # remove "None" walls
        return filter(lambda w: w != None, walls)

    def _walls_are_connected(self, wall1, wall2):
        """ Figure out if two walls are connected at any point. """
        if wall1 is None or wall2 is None:
            return False

        for cell1 in wall1:
            for cell2 in wall2:
                if self._is_neighbor(cell1, cell2):
                    return True

        return False

    def _build_solutions(self):
        """ Now that all of the cul-de-sac have been cut out,
            the maze still needs to be solved.
        """
        return self.solver.solve(self.grid, self.start, self.end)

    def _fix_culdesac(self, border):
        """ Destroy the culdesac by blocking off the loop. """
        if len(border) > 1:
            self.grid[self._midpoint(border[0], border[1])] = 1

    def _wall_is_culdesac(self, border):
        """ A cul-de-sac is a loop with only one entrance. """
        num_entrances = 0

        for cell in border:
            num_neighbors = len(self._find_unblocked_neighbors(cell))
            # if a cell has more than 2 neighbors, one must be a cul-de-sac entrance
            if num_neighbors > 2:
                num_entrances += 1
            # if it has more than one entrance, it's not a cul-de-sac
            if num_entrances > 1:
                return False

        return True

    def _find_bordering_cells(self, wall):
        """ build a buffer, one cell wide, around the wall """
        border = set()

        # buffer each wall cell by one, add those buffer cells to a set
        for cell in wall:
            r,c = cell
            for rdiff in range(-1, 2):
                for cdiff in range(-1, 2):
                    border.add((r + rdiff, c + cdiff))

        # remove all wall cells from the buffer
        border = filter(lambda b: b not in wall, border)

        # remove all non-navigable cells from the buffer
        border = list(filter(lambda b: b[0] % 2 == 1 and b[1] % 2 == 1, border))

        # remove all dead ends within the cul-de-sac
        return self._remove_internal_deadends(border)

    def _remove_internal_deadends(self, border):
        """ Complicated cul-de-Sacs can have internal dead ends.
            These seriously complicate the logic and need to be removed.
        """
        found = True
        while found:
            found = False
            new_border = border
            for cell in border:
                if len(self._find_unblocked_neighbors(cell)) < 2:
                    new_border.remove(cell)
                    found = True
            border = new_border

        return border

    def _remove_border_walls(self, walls):
        """ remove any wall system that touches the maze border """
        new_walls = []

        for wall in walls:
            on_edge = False
            for cell in wall:
                if self._on_edge(cell):
                    on_edge = True
                    break
            if not on_edge:
                new_walls.append(wall)

        return new_walls

    def _find_wall_systems(self):
        """ A wall system is any continiously-adjacent set of walls. """
        walls = []
        # loop through each cell in the maze
        for r in range(self.grid.shape[0]):
            for c in range(self.grid.shape[1]):
                # if the cell is a wall
                if self.grid[r, c] == 1:
                    found = False
                    # determine which wall system it belongs in
                    for i in range(len(walls)):
                        if self._has_neighbor((r, c), walls[i]):
                            found = True
                            walls[i].append((r, c))
                    if not found:
                        walls.append([(r, c)])

        return walls

    def _is_neighbor(self, cell1, cell2):
        """ Determine if one cell is adjacent to another """
        r_diff = abs(cell1[0] - cell2[0])
        c_diff = abs(cell1[1] - cell2[1])

        if r_diff == 0 and c_diff == 1:
            return True
        elif c_diff == 0 and r_diff == 1:
            return True
        else:
            return False

    def _has_neighbor(self, cell, list_cells):
        """ Determine if your cell has a neighbor in a list of cells """
        for target in list_cells:
            if self._is_neighbor(cell, target):
                return True

        return False

    def _fill_dead_ends(self):
        """ fill all dead ends in the maze """
        # loop through the maze serpentine, and find dead ends
        for r in range(1, self.grid.shape[0], 2):
            for c in range(1, self.grid.shape[1], 2):
                if self._is_dead_end((r, c)):
                    # fill-in or wall-off the dead end
                    self._remove_dead_end((r, c))

    def _dead_end_filler(self, dead_end):
        """ Back away from the dead end until you reach an intersection.
            Fill the path as you go.
        """
        current = dead_end
        ns = self._find_unblocked_neighbors(current)

        if len(ns) == 1:
            self.grid[current] = 1
            self.grid[self._midpoint(ns[0], current)] = 1

    def _dead_end_sealer(self, dead_end):
        """ Back away from the dead end until you reach an intersection.
            Block off the dead end passage.
        """
        current = dead_end
        ns = self._find_unblocked_neighbors(current)

        if len(ns) == 1:
            last = current
            current = ns[0]

        self.grid[self._midpoint(last, current)] = 1

    def _is_dead_end(self, cell):
        """ A dead end has zero or one open neighbors. """
        ns = self._find_unblocked_neighbors(cell)

        if self._within_one(cell, self.start) or self._within_one(cell, self.end):
            return False
        elif self.grid[cell] == 1:
            return False
        elif len(ns) in [0, 1]:
            return True
        else:
            return False
Example #9
0
class BlindAlley(MazeSolveAlgo):
    """
    1. Scan the maze, identify all fully-connected wall systems.
    2. Any wall system that touches the border is not a cul-de-sac, remove it.
    3. Determine if remaining wall systems are cul-de-sacs.
    4. If so, add a wall segment to turn the cul-de-sac into a dead end.
    5. Solve using Dead End Filler.
    """
    def __init__(self, fill_type='filler', solver=None):
        if not solver:
            self.solver = ShortestPaths()
        else:
            self.solver = solver

        if fill_type == 'sealer':
            self._remove_dead_end = SEALER
        else:
            self._remove_dead_end = FILLER

    def _solve(self):
        self._seal_culdesacs()
        self._fill_dead_ends()

        return self._build_solutions()

    def _seal_culdesacs(self):
        """ identify and seal off all culdesacs """
        # identify all fully-connected wall systems
        walls = self._find_wall_systems()
        # connect wall systems that are disconnected above
        walls = self._reduce_wall_systems(walls)

        # remove any wall system that touches the maze boundary
        walls = self._remove_border_walls(walls)

        for wall in walls:
            border = self._find_bordering_cells(wall)
            if self._wall_is_culdesac(border):
                self._fix_culdesac(border)

    def _reduce_wall_systems(self, walls):
        """ Reduce a collection of walls in a maze to realize
            when two walls are actually connected and should be one.
        """
        N = len(walls)

        for i in range(N - 1):
            if walls[i] is None:
                continue
            for j in range(i, N):
                if walls[j] is None:
                    continue
                if self._walls_are_connected(walls[i], walls[j]):
                    walls[i] += walls[j]
                    walls[j] = None

        # remove "None" walls
        return [w for w in walls if w != None]

    def _walls_are_connected(self, wall1, wall2):
        """ Figure out if two walls are connected at any point. """
        if wall1 is None or wall2 is None:
            return False

        for cell1 in wall1:
            for cell2 in wall2:
                if self._is_neighbor(cell1, cell2):
                    return True

        return False

    def _build_solutions(self):
        """ Now that all of the cul-de-sac have been cut out,
            the maze still needs to be solved.
        """
        return self.solver.solve(self.grid, self.start, self.end)

    def _fix_culdesac(self, border):
        """ Destroy the culdesac by blocking off the loop. """
        if len(border) > 1:
            r, c = self._midpoint(border[0], border[1])
            self.grid[r, c] = 1

    def _wall_is_culdesac(self, border):
        """ A cul-de-sac is a loop with only one entrance. """
        num_entrances = 0

        for cell in border:
            num_neighbors = len(self._find_unblocked_neighbors(cell))
            # if a cell has more than 2 neighbors, one must be a cul-de-sac entrance
            if num_neighbors > 2:
                num_entrances += 1
            # if it has more than one entrance, it's not a cul-de-sac
            if num_entrances > 1:
                return False

        return True

    def _find_bordering_cells(self, wall):
        """ build a buffer, one cell wide, around the wall """
        border = []

        # buffer each wall cell by one, append those buffer cells to a list
        for cell in wall:
            r, c = cell
            for rdiff in range(-1, 2):
                for cdiff in range(-1, 2):
                    border.append((r + rdiff, c + cdiff))

        # remove all non-unique cells
        border = list(set(border))

        # remove all wall cells from the buffer
        border = [b for b in border if b not in wall]

        # remove all non-navigable cells from the buffer
        border = [b for b in border if b[0] % 2 == 1 and b[1] % 2 == 1]

        # remove all dead ends within the cul-de-sac
        return self._remove_internal_deadends(border)

    def _remove_internal_deadends(self, border):
        """ Complicated cul-de-Sacs can have internal dead ends.
            These seriously complicate the logic and need to be removed.
        """
        found = True
        while found:
            found = False
            new_border = border
            for cell in border:
                if len(self._find_unblocked_neighbors(cell)) < 2:
                    new_border.remove(cell)
                    found = True
            border = new_border

        return border

    def _remove_border_walls(self, walls):
        """ remove any wall system that touches the maze border """
        new_walls = []

        for wall in walls:
            on_edge = False
            for cell in wall:
                if self._on_edge(cell):
                    on_edge = True
                    break
            if not on_edge:
                new_walls.append(wall)

        return new_walls

    def _find_wall_systems(self):
        """ A wall system is any continiously-adjacent set of walls. """
        walls = []
        # loop through each cell in the maze
        for r in range(self.grid.shape[0]):
            for c in range(self.grid.shape[1]):
                # if the cell is a wall
                if self.grid[r, c] == 1:
                    found = False
                    # determine which wall system it belongs in
                    for i in range(len(walls)):
                        if self._has_neighbor((r, c), walls[i]):
                            found = True
                            walls[i].append((r, c))
                    if not found:
                        walls.append([(r, c)])

        return walls

    def _is_neighbor(self, cell1, cell2):
        """ Determine if one cell is adjacent to another """
        r_diff = abs(cell1[0] - cell2[0])
        c_diff = abs(cell1[1] - cell2[1])

        if r_diff == 0 and c_diff == 1:
            return True
        elif c_diff == 0 and r_diff == 1:
            return True
        else:
            return False

    def _has_neighbor(self, cell, list_cells):
        """ Determine if your cell has a neighbor in a list of cells """
        for target in list_cells:
            if self._is_neighbor(cell, target):
                return True

        return False

    def _fill_dead_ends(self):
        """ fill all dead ends in the maze """
        # loop through the maze serpentine, and find dead ends
        for r in range(1, self.grid.shape[0], 2):
            for c in range(1, self.grid.shape[1], 2):
                if self._is_dead_end((r, c)):
                    # fill-in or wall-off the dead end
                    if self._remove_dead_end == SEALER:
                        self._dead_end_sealer((r, c))
                    else:
                        self._dead_end_filler((r, c))

    def _dead_end_filler(self, dead_end):
        """ Back away from the dead end until you reach an intersection.
            Fill the path as you go.
        """
        r, c = dead_end
        ns = self._find_unblocked_neighbors((r, c))

        if len(ns) == 1:
            self.grid[r, c] = 1
            r, c = self._midpoint(ns[0], (r, c))
            self.grid[r, c] = 1

    def _dead_end_sealer(self, dead_end):
        """ Back away from the dead end until you reach an intersection.
            Block off the dead end passage.
        """
        current = dead_end
        ns = self._find_unblocked_neighbors(current)

        if len(ns) == 1:
            last = current
            current = ns[0]

        r, c = self._midpoint(last, current)
        self.grid[r, c] = 1

    def _is_dead_end(self, cell):
        """ A dead end has zero or one open neighbors. """
        ns = self._find_unblocked_neighbors(cell)

        if self._within_one(cell, self.start) or self._within_one(cell, self.end):
            return False
        elif self.grid[cell[0], cell[1]] == True:  # TODO: WARNING: Index should be typed for more efficient access
            return False
        elif len(ns) in [0, 1]:
            return True
        else:
            return False
Example #10
0
 def __init__(self, solver=None):
     if not solver:
         self.solver = DeadEndFiller(ShortestPaths())
     else:
         self.solver = DeadEndFiller(solver)