def testDeadEndFiller(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 = DeadEndFiller() 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]))
class CuldeSacFiller(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, solver=None): if not solver: self.solver = DeadEndFiller(ShortestPaths()) else: self.solver = DeadEndFiller(solver) def _solve(self): self._seal_culdesacs() 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: 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 = [] # buffer each wall cell by one, add those buffer cells to a set for cell in wall: r, c = cell for rdiff in [-1, 0, 1]: for cdiff in [-1, 0, 1]: border.append((r + rdiff, c + cdiff)) # remove non-unique values 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 __init__(self, solver=None): if not solver: self.solver = DeadEndFiller(ShortestPaths()) else: self.solver = DeadEndFiller(solver)