def run(self) -> None: """Run the program.""" if self.gui: self.run_gui() else: maze = Maze(self.width, self.height, generator=self.generator) if self.solve: solver = MazeSolver() maze.path = solver.solve(maze) print(maze)
def __init__( self, generator: Optional[MazeGenerator] = DepthFirstSearchGenerator() ) -> None: super().__init__() self.walls = [] self.vertices = {} self.edges = {} self.maze = Maze(width=self.NUM_COLUMNS, height=self.NUM_ROWS, generator=generator) self.create_graph()
def generate_new_maze(self, event: Optional[tk.Event] = None, generate: bool = True) -> None: """Create a new blank maze, and optionally generate paths through it.""" if self.generating_maze or self.solving_maze: return self.clear_path() self.frontier_cells.clear() self.generation_start_cell = None self.maze = Maze(self.width, self.height, generator=None) self.maze_generated = False self.start_time = None self.display_maze() if generate: self.generate_current_maze()
class Main: def __init__(self): self.lab = Maze() self.lab.create_lab_elements() def select_plateform(self): print(d.plat) x = input() if x == "g" or x == "G": self.gui() elif x == "c" or x == "C": self.console() else: print(x, d.inval_in) return self.select_plateform() def console(self): d.intro() d.print_lab(self.lab) inter = Interaction(self.lab) while inter.still_awake(): direction = input('\n' + d.ask_move) self.lab.macgyver.move_char(direction, self.lab.list_paths) if inter.check_chars_pos(): self.lab.chars_meet_up() if self.lab.list_items: inter.item_picking_process() d.print_lab(self.lab) def gui(self): G = Game(maze=self.lab) G.run() print(d.goodbye) def play(self): """ Work hard, play hard ! """ print(d.intro_text) self.select_plateform()
class MazeApp(Frame, MazeRenderer): """Class containing state and graphics elements for rendering the UI.""" BORDER_WIDTH = 4 BORDER_OFFSET = BORDER_WIDTH * 2 DEFAULT_SIZE = SizeCategory.SMALL DEFAULT_DISPLAY_MODE = DisplayMode.GRID DEFAULT_GENERATOR = DepthFirstSearchGenerator DELAY_EVENT_TYPES = { MazeUpdateType.START_CELL_CHOSEN, MazeUpdateType.CELL_MARKED, MazeUpdateType.WALL_REMOVED } SUPPORTED_GENERATORS = { DepthFirstSearchGenerator: "Depth First Search", KruskalsGenerator: "Kruskal's Algorithm", PrimsGenerator: "Prim's Algorithm", WilsonsGenerator: "Wilson's Algorithm", } TICK_DELAY_MILLIS = 500 def __init__(self, master: tk.Tk = None, width: int = 10, height: int = 10, generator: Optional[MazeGenerator] = None, size_category: Optional[SizeCategory] = None, validate_moves: bool = True) -> None: """Initialize a MazeApp instance.""" if master is None: master = tk.Tk() master.title('Maze Generator') super().__init__(master) self.width = width self.height = height self.validate_moves = validate_moves self.generating_maze = False self.drawing_path = False self.using_dialog_box = False self.solving_maze = False self.maze_generated = False self._generator = generator self.solver = MazeSolver() self.menu = None self.maze = None self.generation_start_cell = None self.frontier_cells = set() self.path = [] window = self.winfo_toplevel() window.configure(bg=BACKGROUND_COLOR) window.minsize(width=700, height=100) self.pack() canvas_frame = Frame() canvas_frame.pack(side='left') self.create_horizontal_spacer() self.menu = MazeAppMenu(self, size_category=size_category) self.canvas = self.create_canvas(canvas_frame) self.stats = self.create_stats_display(canvas_frame) self.menu.pack(side='left') self.start_time = None self.end_time = None self.generate_new_maze(generate=False) @property def cell_width(self) -> int: """Return the current cell width.""" size = self.DEFAULT_SIZE if self.menu is None else self.menu.size_category return size.value[0] @property def cell_height(self) -> int: """Return the current cell height.""" return self.cell_width @property def canvas_width(self) -> int: """Return the current canvas width.""" return self.cell_width * self.width + self.BORDER_OFFSET @property def canvas_height(self) -> int: """Return the current canvas height.""" return self.cell_height * self.height + self.BORDER_OFFSET @property def vertex_radius(self) -> int: """Return the current vertex radius.""" size = self.DEFAULT_SIZE if self.menu is None else self.menu.size_category return size.value[1] @property def vertex_diameter(self) -> int: """Return the current vertex diameter.""" return self.vertex_radius * 2 @property def generator_type(self) -> Type[MazeGenerator]: if self.menu is not None and self.menu.generator_type is not None: return self.menu.generator_type if self._generator is not None: return self._generator.__class__ return self.DEFAULT_GENERATOR @property def generator(self) -> MazeGenerator: """Return an instance of a MazeGenerator subclass to use for generating mazes.""" if self._generator is None or self._generator.__class__ != self.generator_type: self._generator = self.generator_type() return self._generator @property def display_mode(self) -> DisplayMode: return self.DEFAULT_DISPLAY_MODE if self.menu is None else self.menu.display_mode @property def is_solved(self) -> bool: """Return True if the current maze is solved, False otherwise.""" return self.path and (self.path[-1].row, self.path[-1].column) == (self.height - 1, self.width - 1) @property def elapsed_time(self) -> float: """Return the amount of time elapsed since the current maze was fully generated.""" if self.start_time is not None: end_time = self.end_time or time.time() return end_time - self.start_time return 0 def create_canvas(self, parent: Frame) -> tk.Canvas: """Create and return a graphics canvas representing the grid of cells in the maze.""" canvas = tk.Canvas(parent, width=self.canvas_width, height=self.canvas_height, borderwidth=0) canvas.bind(LEFT_CLICK, self.click_handler) canvas.bind(MOTION, self.motion_handler) canvas.pack(side='top') return canvas def refresh_canvas(self) -> None: """Refresh the canvas in response to maze size changes.""" if self.width != self.maze.width or self.height != self.maze.height: self.generate_new_maze(generate=False) if self.canvas_width != self.canvas[ 'width'] or self.canvas_height != self.canvas['height']: self.canvas.configure(width=self.canvas_width, height=self.canvas_height) self.display_maze() @staticmethod def create_stats_display(parent: Frame) -> Label: """Create and return a graphics element containing statistics about the current maze.""" stats = Label(parent, padx=10, pady=10, width=62) stats.pack(side='top') return stats @staticmethod def create_horizontal_spacer() -> None: Label(width=2).pack(side='left') @staticmethod def get_wall_tag(row: int, column: int, direction: Direction) -> str: """Return a formatted tag suitable for use when drawing maze walls on the canvas.""" return f'{row}_{column}_{direction.name}' @staticmethod def get_cell_tag(row: int, column: int) -> str: """Return a formatted tag suitable for use when drawing frontier cells on the canvas.""" return f'{row}_{column}' def click_handler(self, event: tk.Event) -> None: """Event handler for click events on the canvas.""" if self.generating_maze or self.solving_maze: return if self.drawing_path: self.drawing_path = False if self.end_time is None and self.is_solved: self.end_time = time.time() else: coordinates = self.get_selected_cell_coordinates(event) self.select_cell(*coordinates) self.drawing_path = True def motion_handler(self, event: tk.Event) -> None: """Event handler for mouse motion events on the canvas.""" if self.generating_maze or self.solving_maze: return if self.drawing_path: coordinates = self.get_selected_cell_coordinates(event) if self.path and coordinates != self.path[-1].coordinates: if len(self.path ) > 1 and coordinates == self.path[-2].coordinates: # moved back one, deselect the most recent cell self.select_cell(*self.path[-1].coordinates) else: self.select_cell(*coordinates) def generate_new_maze(self, event: Optional[tk.Event] = None, generate: bool = True) -> None: """Create a new blank maze, and optionally generate paths through it.""" if self.generating_maze or self.solving_maze: return self.clear_path() self.frontier_cells.clear() self.generation_start_cell = None self.maze = Maze(self.width, self.height, generator=None) self.maze_generated = False self.start_time = None self.display_maze() if generate: self.generate_current_maze() def generate_current_maze(self, event: Optional[tk.Event] = None) -> None: """Generate paths through the current maze.""" if self.menu.animate: self.generator.event_listener = self.update_maze else: self.generator.event_listener = None self.generating_maze = True self.clear_path() self.generator.generate(self.maze) self.clear_path() self.generating_maze = False self.maze_generated = True if not self.menu.animate or self.display_mode == DisplayMode.GRAPH: self.display_maze() self.start_time = time.time() self.end_time = None def create_maze_grid(self) -> None: """Populate the canvas with all of the walls in the current maze.""" for obj in self.canvas.find_all(): self.canvas.delete(obj) for row in range(self.height): for column in range(self.width): cell = self.maze[row, column] x0 = self.cell_width * column + self.BORDER_OFFSET y0 = self.cell_height * row + self.BORDER_OFFSET x1 = x0 + self.cell_width y1 = y0 + self.cell_height directions = { Direction.N: ((x0, y0, x1, y0), lambda r, c: r == 0), Direction.S: ((x0, y1, x1, y1), lambda r, c: r == self.height - 1), Direction.E: ((x1, y0, x1, y1), lambda r, c: c == self.width - 1), Direction.W: ((x0, y0, x0, y1), lambda r, c: c == 0), } for direction in directions: coordinates, width_predicate = directions[direction] width = 1 if direction not in cell.open_walls: if width_predicate(row, column): width = self.BORDER_WIDTH wall_tag = self.get_wall_tag(row, column, direction) self.canvas.create_line(*coordinates, width=width, tags=wall_tag) cell_tag = self.get_cell_tag(row, column) if cell in self.frontier_cells: self.fill_cell(cell, FRONTIER_COLOR, cell_tag) elif cell in self.path: color = GENERATE_PATH_COLOR if self.generating_maze else PATH_COLOR self.fill_cell(cell, color) elif not cell.open_walls and cell != self.generation_start_cell: self.fill_cell(cell, INITIAL_CELL_COLOR, cell_tag) def create_maze_graph(self) -> None: """Populate the canvas with a visual representation of the graph underlying the current maze.""" for obj in self.canvas.find_all(): self.canvas.delete(obj) for row in range(self.height): for column in range(self.width): cell = self.maze[row, column] cell_x0 = self.cell_width * column + self.BORDER_OFFSET cell_y0 = self.cell_height * row + self.BORDER_OFFSET pad_x = (self.cell_width - self.vertex_diameter) // 2 pad_y = (self.cell_height - self.vertex_diameter) // 2 vertex_x0 = cell_x0 + pad_x vertex_y0 = cell_y0 + pad_y vertex_x1 = vertex_x0 + self.vertex_diameter vertex_y1 = vertex_y0 + self.vertex_diameter tag = self.get_cell_tag(*cell.coordinates) if self.generating_maze and cell == self.generation_start_cell: color = VERTEX_COLOR elif cell in self.frontier_cells: color = FRONTIER_COLOR elif cell.open_walls: if cell in self.path: color = GENERATE_PATH_COLOR if self.generating_maze else PATH_COLOR else: color = VERTEX_COLOR else: color = INITIAL_CELL_COLOR self.canvas.create_oval(vertex_x0, vertex_y0, vertex_x1, vertex_y1, fill=color, tags=tag) for direction in {Direction.E, Direction.S}: neighbor = self.maze.neighbor(cell, direction) if (self.maze_generated and direction in cell.open_walls) or (not self.maze_generated and neighbor): dash = ( ) if self.maze_generated or direction in cell.open_walls else ( 2, ) if direction == Direction.E: edge_x0 = vertex_x1 edge_y0 = vertex_y0 + self.vertex_radius edge_x1 = 1 + edge_x0 + pad_x * 2 edge_y1 = edge_y0 elif direction == Direction.S: edge_x0 = vertex_x0 + self.vertex_radius edge_y0 = vertex_y1 edge_x1 = edge_x0 edge_y1 = 1 + edge_y0 + pad_y * 2 else: raise ValueError( f'Unexpected direction {direction.name}!') tag = self.get_wall_tag(cell.row, cell.column, direction) opposite_tag = self.get_wall_tag( neighbor.row, neighbor.column, direction.opposite) self.canvas.create_line(edge_x0, edge_y0, edge_x1, edge_y1, width=2, dash=dash, tags=(tag, opposite_tag)) def display_maze(self) -> None: """Display the current maze on the canvas.""" if self.display_mode == DisplayMode.GRID: self.create_maze_grid() else: self.create_maze_graph() def solve_maze(self, event: Optional[tk.Event] = None) -> None: """Solve the current maze.""" if self.solving_maze or self.generating_maze or self.using_dialog_box or not self.maze.start_cell.open_walls: return self.solving_maze = True self.start_time = None self.clear_path() path = self.solver.solve(self.maze) for cell in path: self.path.append(cell) self.fill_cell(cell, PATH_COLOR) if self.menu.animate: self.canvas.update() time.sleep(self.menu.delay_millis / 1000) self.solving_maze = False def get_selected_cell_coordinates(self, event: tk.Event) -> Tuple[int, int]: """Return the coordinates of the selected cell based on the (x, y) position of the given event.""" row = max(0, min(event.y // self.cell_height, self.height - 1)) column = max(0, min(event.x // self.cell_width, self.width - 1)) return row, column 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 fill_cell(self, cell: Cell, color: str, tag: str = 'path'): """Fill the given cell in the maze with the given color.""" if self.display_mode == DisplayMode.GRID: row_offset = 2 if cell.row in {0, self.height - 1} else 1 column_offset = 2 if cell.column in {0, self.width - 1} else 1 x0 = self.cell_width * cell.column + self.BORDER_OFFSET + column_offset y0 = self.cell_height * cell.row + self.BORDER_OFFSET + row_offset x1 = x0 + self.cell_width - (2 if cell.column == self.width - 1 else 1) * column_offset y1 = y0 + self.cell_height - (2 if cell.row == self.height - 1 else 1) * row_offset self.canvas.create_rectangle(x0, y0, x1, y1, fill=color, width=0, tags=tag) else: self.canvas.itemconfigure(self.get_cell_tag(*cell.coordinates), fill=color) def clear_cell(self, cell: Cell) -> None: """Reset the given cell back to its default state on the canvas.""" tag = self.get_cell_tag(*cell.coordinates) if self.display_mode == DisplayMode.GRID: self.canvas.delete(tag) self.fill_cell(cell, 'white') else: self.canvas.itemconfigure(tag, fill=VERTEX_COLOR) @override def set_start_cell(self, cell: Cell) -> None: """Update the cell where the maze generator chose to start.""" self.generation_start_cell = cell self.clear_cell(cell) @override def clear_path(self) -> None: """Clear the current path of highlighted cells in the maze.""" if self.display_mode == DisplayMode.GRID: self.canvas.delete('path') else: for cell in self.path: self.canvas.itemconfigure(self.get_cell_tag(*cell.coordinates), fill=VERTEX_COLOR) self.path.clear() @override def add_cell_to_generated_path(self, cell: Cell) -> None: """Fill the given cell with the 'generate path' color.""" if cell in self.frontier_cells: self.frontier_cells.remove(cell) self.path.append(cell) self.fill_cell(cell, GENERATE_PATH_COLOR) @override def get_end_of_current_path(self) -> Optional[Cell]: """Return the cell at the end of the path currently being generated, if any.""" return self.path[-1] if self.path else None @override def add_cells_to_frontier(self, frontier_cells: Set[Cell]) -> None: """Fill the given frontier cells with the frontier color and add them to the set of frontier cells.""" if frontier_cells: self.frontier_cells |= frontier_cells for cell in frontier_cells: self.fill_cell(cell, FRONTIER_COLOR, tag=self.get_cell_tag(*cell.coordinates)) @override 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=()) @override 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) @override def delay(self) -> None: """Delay rendering to allow the user to see the latest updates.""" time.sleep(self.menu.delay_millis / 1000) @override def refresh(self) -> None: """Refresh the canvas after applying updates.""" self.canvas.update() def tick(self) -> None: """Update the UI on a regular interval.""" self.stats['text'] = ( f'Maze Size: {self.width} x {self.height} ' f'Current Path Length: {len(self.path)} ' f'Elapsed Time: {int(self.elapsed_time)} sec') self.after(self.TICK_DELAY_MILLIS, self.tick) def run(self) -> None: """Run the GUI.""" self.tick() super().mainloop()
def __init__(self): self.lab = Maze() self.lab.create_lab_elements()
class MazeScene(Scene): ANIMATION_RUN_TIME = 2 DASHED_LINES = False GRID_OFFSET = 0.75 INITIAL_VERTEX_COLOR = DARK_BLUE WALL_COLOR = GOLD NUM_COLUMNS = 5 NUM_ROWS = NUM_COLUMNS SHOW_TREE = True START_COORDS = LEFT * 3 + UP * 3 SCALE_FACTOR = 1 VERTEX_OFFSET = 1.5 VERTEX_RADIUS = 0.5 def __init__( self, generator: Optional[MazeGenerator] = DepthFirstSearchGenerator() ) -> None: super().__init__() self.walls = [] self.vertices = {} self.edges = {} self.maze = Maze(width=self.NUM_COLUMNS, height=self.NUM_ROWS, generator=generator) self.create_graph() @property def vertex_offset(self) -> float: return self.VERTEX_OFFSET * self.SCALE_FACTOR @property def vertex_radius(self) -> float: return self.VERTEX_RADIUS * self.SCALE_FACTOR 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 create_wall(self, start: Any, end: Any) -> Line: wall = Line(start, end).set_stroke(color=self.WALL_COLOR) self.walls.append(wall) return wall 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 get_coordinates(self, row: int, column: int) -> Any: return self.START_COORDS + (RIGHT * column * self.vertex_offset) + ( DOWN * row * self.vertex_offset) def create_vertex(self, row: int, column: int) -> Circle: coords = self.get_coordinates(row, column) vertex = Circle(radius=self.vertex_radius) \ .move_to(coords) \ .set_stroke(WHITE) \ .set_fill(self.INITIAL_VERTEX_COLOR, opacity=1) self.vertices[row, column] = vertex return vertex def create_edge(self, start_vertex: Circle, end_vertex: Circle, dashed: Optional[bool] = None) -> Line: if dashed is None: dashed = self.DASHED_LINES line_type = DashedLine if dashed else Line return line_type(start_vertex.get_center(), end_vertex.get_center(), buff=self.vertex_radius) 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 get_solution(self) -> List[Circle]: solver = MazeSolver() return [ self.vertices[cell.row, cell.column] for cell in solver.solve(self.maze) ] def play_all(self, *animations, lag_ratio: float = 1) -> None: self.play(AnimationGroup(*animations, lag_ratio=lag_ratio)) def pause(self, duration: float = ANIMATION_RUN_TIME): self.wait(duration) def animate_grid_creation(self, style: str = 'distance_from_start', lag_ratio: float = 1) -> None: if not self.walls: self.create_grid() num_outer_walls = 4 self.play(*[ Create(wall, run_time=self.ANIMATION_RUN_TIME / 2) for wall in self.walls[:num_outer_walls] ]) inner_walls = self.walls[num_outer_walls:] if style == 'distance_from_center': inner_walls.sort(key=lambda l: line_distance(0, 0, l)) elif style == 'distance_from_start': inner_walls.sort(key=lambda l: line_distance( self.START_COORDS[0], self.START_COORDS[1], l)) elif style == 'random': random.shuffle(inner_walls) elif style != 'order': raise ValueError(f'Invalid style: {style}') self.play_all(*[ Create(wall, run_time=self.ANIMATION_RUN_TIME / 10) for wall in inner_walls ], lag_ratio=lag_ratio) def animate_graph_creation(self, lag_ratio: float = 0.1) -> None: self.play_all(*[ FadeIn(vertex, run_time=self.ANIMATION_RUN_TIME / 4) for vertex in self.vertices.values() ], lag_ratio=lag_ratio) self.play(*[ Create(edge, run_time=self.ANIMATION_RUN_TIME) for edge in self.edges.values() ]) def animate_graph_destruction(self, lag_ratio: float = 0.1) -> None: vertex_group = AnimationGroup(*[ FadeOut(vertex, run_time=self.ANIMATION_RUN_TIME / 4) for vertex in self.vertices.values() ], lag_ratio=lag_ratio) edge_group = AnimationGroup(*[ Uncreate(edge, run_time=self.ANIMATION_RUN_TIME / 2) for edge in self.edges.values() ]) self.play(vertex_group, edge_group) def animate_edge_removal(self, lag_ratio: float = 0, highlight: bool = True) -> None: edges = self.get_edges_to_remove() if highlight: self.play(*[ edge.animate(run_time=self.ANIMATION_RUN_TIME / 2).set_stroke(color=RED) for edge in edges ]) self.play_all(*[ Uncreate(edge, run_time=self.ANIMATION_RUN_TIME) for edge in edges ], lag_ratio=lag_ratio) def animate_solution(self) -> None: run_time = self.ANIMATION_RUN_TIME / 5 self.play_all(*[ vertex.animate(run_time=run_time).set_fill(GREEN, opacity=1) for vertex in self.get_solution() ]) def transform_grid_to_graph(self) -> None: grid_group = VGroup(*self.walls) graph_group = VGroup(*(list(self.vertices.values()) + list(self.edges.values()))) self.play( ReplacementTransform(grid_group, graph_group, run_time=self.ANIMATION_RUN_TIME))
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)) if __name__ == '__main__': maze = Maze() solver = MazeSolver() path = solver.solve(maze) print(maze) print(f'Solution: {[cell.coordinates for cell in path]}')