def create_grid(self) -> None: top_left = self.START_COORDS + (UP * self.GRID_OFFSET) + ( LEFT * self.GRID_OFFSET) top_right = top_left + (RIGHT * self.NUM_COLUMNS * self.vertex_offset) bottom_left = top_left + (DOWN * self.NUM_ROWS * self.vertex_offset) bottom_right = top_right + (DOWN * self.NUM_ROWS * self.vertex_offset) self.create_wall(top_left, top_right) self.create_wall(top_left, bottom_left) self.create_wall(bottom_left, bottom_right) self.create_wall(top_right, bottom_right) for row in range(self.NUM_ROWS): for column in range(self.NUM_COLUMNS): cell = self.maze[row, column] for neighbor in self.maze.neighbors(cell): direction = Direction.between(cell, neighbor) if direction not in cell.open_walls: if direction == Direction.S: start_coords = top_left + (RIGHT * column * self.vertex_offset) + \ (DOWN * (row + 1) * self.vertex_offset) end_coords = start_coords + RIGHT * self.vertex_offset self.create_wall(start_coords, end_coords) elif direction == Direction.E: start_coords = top_left + (RIGHT * (column + 1) * self.vertex_offset) + \ (DOWN * row * self.vertex_offset) end_coords = start_coords + DOWN * self.vertex_offset self.create_wall(start_coords, end_coords)
def select_cell(self, row: int, column: int) -> None: """Select (or deselect) the maze cell at the given row and column, toggling its color and updating the path.""" clicked_cell = self.maze[row, column] add = True if not self.path: if self.validate_moves and clicked_cell != self.maze.start_cell: # print('Path must start at (0, 0)') return else: last_cell = self.path[-1] if clicked_cell in self.path: if self.validate_moves and clicked_cell != last_cell: # print('Can only undo the last move') return add = False elif self.validate_moves and clicked_cell not in self.maze.neighbors( last_cell): # print('Path must be continuous') return elif self.validate_moves: direction = Direction.between(last_cell, clicked_cell) if direction not in last_cell.open_walls: # print(f'Invalid move (through {direction.name} wall)') return if add: self.fill_cell(clicked_cell, PATH_COLOR) self.path.append(clicked_cell) else: self.clear_cell(clicked_cell) self.path = self.path[:-1]
def solve_maze(self) -> List[Cell]: """Find and return a path through the current maze.""" self.prev_cells = {} self.junction_graph = self.construct_junction_graph() self.junction_graph.depth_first_search(self.maze.start_cell, self.junction_visitor) end_cell = self.maze.end_cell path = [end_cell] prev_cell = end_cell cell = self.prev_cells.get(end_cell) while path[-1] != self.maze.start_cell: if Direction.between(prev_cell, cell) is None: # fill in corridors direction = self.junction_direction(prev_cell, cell) neighbor = self.maze.neighbor(prev_cell, direction) while neighbor != cell: path.append(neighbor) neighbor = self.maze.neighbor(neighbor, direction) path.append(cell) prev_cell = cell cell = self.prev_cells.get(cell) return list(reversed(path))
def get_edges_to_remove(self) -> List[Line]: edges = [] for wall in self.maze.walls: start_cell, end_cell = wall direction = Direction.between(start_cell, end_cell) if direction not in start_cell.open_walls: edges.append(self.edges[((start_cell.row, start_cell.column), (end_cell.row, end_cell.column))]) return edges
def remove_edge(self, start_cell: Cell, end_cell: Cell) -> None: """Remove the edge between the given start cell and end cell (if rendering the maze as a graph).""" if self.display_mode == DisplayMode.GRAPH: direction = Direction.between(start_cell, end_cell) tag = self.get_wall_tag(start_cell.row, start_cell.column, direction) self.canvas.delete(tag) opposite_tag = self.get_wall_tag(end_cell.row, end_cell.column, direction.opposite) self.canvas.delete(opposite_tag)
def cell_visitor(cell: Cell) -> None: visited[cell] = True for neighbor in self.maze.neighbors(cell): direction = Direction.between(cell, neighbor) if direction in cell.open_walls and not visited[neighbor]: while self.is_corridor_cell(neighbor): neighbor = self.maze.neighbor(neighbor, direction) junction_graph.add_vertex(cell) junction_graph.add_vertex(neighbor) junction_graph.add_edge(cell, neighbor)
def remove_wall(self, start_cell: Cell, end_cell: Cell) -> None: """Remove the wall between the given start cell and end cell, also clearing any color from the cells.""" direction = Direction.between(start_cell, end_cell) wall_tag = self.get_wall_tag(start_cell.row, start_cell.column, direction) opposite_wall_tag = self.get_wall_tag(end_cell.row, end_cell.column, direction.opposite) if self.display_mode == DisplayMode.GRID: self.canvas.delete(wall_tag) self.canvas.delete(opposite_wall_tag) start_cell_tag = self.get_cell_tag(start_cell.row, start_cell.column) self.canvas.delete(start_cell_tag) end_cell_tag = self.get_cell_tag(end_cell.row, end_cell.column) self.canvas.delete(end_cell_tag) else: for tag in {wall_tag, opposite_wall_tag}: self.canvas.itemconfigure(tag, dash=())
def walk(self) -> List[Tuple[Cell, Direction]]: """Perform a random walk through unvisited cells of the maze and return the path walked.""" start_cell = self.get_random_unvisited_cell() visits = {} cell = start_cell while True: neighbor = random.choice(list(self.maze.neighbors(cell))) # pick a random neighbor direction = Direction.between(cell, neighbor) visits[cell] = direction if neighbor in self.included_cells: break cell = neighbor path = [] cell = start_cell while cell in visits: direction = visits[cell] path.append((cell, direction)) cell = self.maze.neighbor(cell, direction) return path
def create_graph(self) -> None: for row in range(self.NUM_ROWS): for column in range(self.NUM_COLUMNS): coords = (row, column) vertex = self.create_vertex(*coords) edges = [] if row > 0: prev_coords = (row - 1, column) edges.append((self.create_edge(self.vertices[prev_coords], vertex), prev_coords)) if column > 0: prev_coords = (row, column - 1) edges.append((self.create_edge(self.vertices[prev_coords], vertex), prev_coords)) for edge, prev_coords in edges: cell = self.maze[coords] if self.SHOW_TREE: prev_cell = self.maze[prev_coords] if Direction.between(prev_cell, cell) in prev_cell.open_walls: self.edges[(prev_coords, coords)] = edge else: self.edges[(prev_coords, coords)] = edge
def open_wall(start_cell: Cell, end_cell: Cell) -> None: """Open (remove) the walls between the given start and end cells, which are assumed to be adjacent.""" direction = Direction.between(start_cell, end_cell) start_cell.open_walls.add(direction) end_cell.open_walls.add(direction.opposite)
def neighbor(self, cell: Cell, direction: Direction) -> Optional[Cell]: """Return the cell neighboring the given cell in the given direction, if there is one.""" return next((n for n in self.neighbors(cell) if Direction.between(cell, n) == direction), None)