55import random
66import matplotlib .animation as animation
77
8+
89class MazeVisualizer :
910 """
10- A class to create a beautiful and interesting visualization
11- of a dynamic maze-solving algorithm (BFS).
11+ Dynamic BFS maze-solving visualizer with moving target and evolving obstacles.
1212 """
1313
1414 def __init__ (self , maze , start , target ):
15- self .maze = np .array (maze )
15+ self .maze = np .array (maze , dtype = int )
1616 self .start_pos = start
1717 self .target_pos = target
1818 self .solver_pos = start
19-
19+
2020 self .rows , self .cols = self .maze .shape
21-
22- # --- Configurable Parameters ---
23- self .step_delay_ms = 200 # Animation frame delay in milliseconds
24- self .target_move_interval = 5 # Target moves every N frames
25- self .obstacle_change_prob = 0.01 # Probability of a wall changing
21+ self .step_delay_ms = 200 # Animation frame delay
22+ self .target_move_interval = 5 # Target moves every N frames
23+ self .obstacle_change_prob = 0.01 # Random obstacle toggle probability
2624
2725 # --- State Tracking ---
2826 self .path = []
2927 self .visited_nodes = set ()
3028 self .breadcrumb_trail = [self .solver_pos ]
3129 self .frame_count = 0
3230
33- # --- Plotting Setup ---
31+ # --- Plot Setup ---
3432 self .fig , self .ax = plt .subplots (figsize = (8 , 6 ))
3533 plt .style .use ('seaborn-v0_8-darkgrid' )
3634 self .fig .patch .set_facecolor ('#2c2c2c' )
3735 self .ax .set_facecolor ('#1e1e1e' )
38-
39- # Hide axes ticks and labels for a cleaner look
36+
4037 self .ax .set_xticks ([])
4138 self .ax .set_yticks ([])
42-
43- # Maze plot
39+
40+ # Base maze
4441 self .maze_plot = self .ax .imshow (self .maze , cmap = 'magma' , interpolation = 'nearest' )
45-
46- # Visited nodes plot (semi-transparent overlay)
47- self .visited_overlay = np .zeros ((* self .maze .shape , 4 )) # RGBA
42+
43+ # Visited overlay
44+ self .visited_overlay = np .zeros ((* self .maze .shape , 4 ))
4845 self .visited_plot = self .ax .imshow (self .visited_overlay , interpolation = 'nearest' )
4946
50- # Path, solver, target, and breadcrumbs plots
47+ # Path, breadcrumbs, solver, target
5148 self .path_line , = self .ax .plot ([], [], 'g-' , linewidth = 3 , alpha = 0.7 , label = 'Path' )
5249 self .breadcrumbs_plot = self .ax .scatter ([], [], c = [], cmap = 'viridis_r' , s = 50 , alpha = 0.6 , label = 'Trail' )
53- self .solver_plot , = self .ax .plot (self .solver_pos [1 ], self .solver_pos [0 ], 'o' , markersize = 15 , color = '#00ffdd' , label = 'Solver' )
54- self .target_plot , = self .ax .plot (self .target_pos [1 ], self .target_pos [0 ], '*' , markersize = 20 , color = '#ff006a' , label = 'Target' )
55-
50+ self .solver_plot , = self .ax .plot (
51+ [self .solver_pos [1 ]], [self .solver_pos [0 ]],
52+ 'o' , markersize = 15 , color = '#00ffdd' , label = 'Solver'
53+ )
54+ self .target_plot , = self .ax .plot (
55+ [self .target_pos [1 ]], [self .target_pos [0 ]],
56+ '*' , markersize = 20 , color = '#ff006a' , label = 'Target'
57+ )
58+
5659 self .ax .legend (facecolor = 'gray' , framealpha = 0.5 , loc = 'upper right' )
5760 self .title = self .ax .set_title ("Initializing Maze..." , color = 'white' , fontsize = 14 )
5861
5962 def _bfs (self ):
60- """Performs BFS to find the shortest path and returns path and visited nodes ."""
63+ """Performs BFS to find shortest path."""
6164 queue = deque ([(self .solver_pos , [self .solver_pos ])])
6265 visited = {self .solver_pos }
6366
@@ -70,16 +73,14 @@ def _bfs(self):
7073 for dr , dc in [(- 1 , 0 ), (1 , 0 ), (0 , - 1 ), (0 , 1 )]:
7174 nr , nc = r + dr , c + dc
7275 if 0 <= nr < self .rows and 0 <= nc < self .cols and \
73- self .maze [nr ][nc ] == 0 and (nr , nc ) not in visited :
76+ self .maze [nr ][nc ] == 0 and (nr , nc ) not in visited :
7477 visited .add ((nr , nc ))
75- new_path = list (path )
76- new_path .append ((nr , nc ))
77- queue .append (((nr , nc ), new_path ))
78-
79- return None , visited # No path found
78+ queue .append (((nr , nc ), path + [(nr , nc )]))
79+
80+ return None , visited
8081
8182 def _update_target (self ):
82- """Moves the target to a random adjacent valid cell."""
83+ """Moves the target randomly to an adjacent open cell."""
8384 tr , tc = self .target_pos
8485 moves = [(- 1 , 0 ), (1 , 0 ), (0 , - 1 ), (0 , 1 )]
8586 random .shuffle (moves )
@@ -90,86 +91,86 @@ def _update_target(self):
9091 break
9192
9293 def _update_obstacles (self ):
93- """Randomly toggles a few obstacle cells ."""
94+ """Randomly toggle a few obstacles ."""
9495 for r in range (self .rows ):
9596 for c in range (self .cols ):
96- # Avoid changing start/target positions
97- if (r ,c ) == self .solver_pos or (r ,c ) == self .target_pos :
97+ if (r , c ) in [self .solver_pos , self .target_pos ]:
9898 continue
9999 if random .random () < self .obstacle_change_prob :
100- self .maze [r , c ] = 1 - self .maze [r , c ] # Toggle 0 to 1 or 1 to 0
100+ self .maze [r , c ] = 1 - self .maze [r , c ]
101101
102102 def _update_frame (self , frame ):
103- """Main animation loop function ."""
103+ """Main animation loop."""
104104 self .frame_count += 1
105-
106- # --- Update Game State ---
105+
106+ # --- State ---
107107 if self .frame_count % self .target_move_interval == 0 :
108108 self ._update_target ()
109-
110109 self ._update_obstacles ()
111-
110+
112111 self .path , self .visited_nodes = self ._bfs ()
113112
113+ # Move solver one step
114114 if self .path and len (self .path ) > 1 :
115- self .solver_pos = self .path [1 ] # Move solver one step
115+ self .solver_pos = self .path [1 ]
116116 self .breadcrumb_trail .append (self .solver_pos )
117117
118- # --- Update Visuals ---
119- # Update maze and visited nodes overlay
118+ # --- Visuals ---
120119 self .maze_plot .set_data (self .maze )
121- self .visited_overlay .fill (0 ) # Reset overlay
120+
121+ # Visited overlay
122+ self .visited_overlay .fill (0 )
122123 visited_color = mcolors .to_rgba ('#0077b6' , alpha = 0.3 )
123124 for r , c in self .visited_nodes :
124125 self .visited_overlay [r , c ] = visited_color
125126 self .visited_plot .set_data (self .visited_overlay )
126-
127- # Update path line
127+
128+ # Path line
128129 if self .path :
129- path_y , path_x = zip (* self .path )
130- self .path_line .set_data (path_x , path_y )
130+ y , x = zip (* self .path )
131+ self .path_line .set_data (x , y )
131132 else :
132133 self .path_line .set_data ([], [])
133134
134- # Update solver and target positions
135- self .solver_plot .set_data (self .solver_pos [1 ], self .solver_pos [0 ])
136- self .target_plot .set_data (self .target_pos [1 ], self .target_pos [0 ])
135+ # set_data() now receives sequences
136+ self .solver_plot .set_data ([ self .solver_pos [1 ]], [ self .solver_pos [0 ] ])
137+ self .target_plot .set_data ([ self .target_pos [1 ]], [ self .target_pos [0 ] ])
137138
138- # Update breadcrumbs
139+ # Breadcrumbs
139140 if self .breadcrumb_trail :
140- trail_y , trail_x = zip (* self .breadcrumb_trail )
141- colors = np .linspace (0.1 , 1.0 , len (trail_y ))
142- self .breadcrumbs_plot .set_offsets (np .c_ [trail_x , trail_y ])
141+ y , x = zip (* self .breadcrumb_trail )
142+ colors = np .linspace (0.1 , 1.0 , len (y ))
143+ self .breadcrumbs_plot .set_offsets (np .c_ [x , y ])
143144 self .breadcrumbs_plot .set_array (colors )
144145
145- # Update title and check for win condition
146+ # Title update
146147 if self .solver_pos == self .target_pos :
147- self .title .set_text ("Target Reached! 🎉 " )
148+ self .title .set_text ("Dynamic Maze Solver " )
148149 self .title .set_color ('lightgreen' )
149- self .anim .event_source .stop () # Stop animation
150+ self .anim .event_source .stop ()
150151 else :
151- path_len_str = len (self .path ) if self .path else "N/A"
152- self .title .set_text (f"Frame: { self .frame_count } | Path Length: { path_len_str } " )
153- if not self .path :
154- self .title .set_color ('coral' )
155- else :
156- self .title .set_color ('white' )
157-
158- return [self .maze_plot , self .visited_plot , self .path_line , self .solver_plot ,
159- self .target_plot , self .breadcrumbs_plot , self .title ]
152+ path_len = len (self .path ) if self .path else "N/A"
153+ self .title .set_text (f"Frame: { self .frame_count } | Path Length: { path_len } " )
154+ self .title .set_color ('white' if self .path else 'coral' )
155+
156+ return [
157+ self .maze_plot , self .visited_plot , self .path_line ,
158+ self .solver_plot , self .target_plot , self .breadcrumbs_plot , self .title
159+ ]
160160
161161 def run (self ):
162162 """Starts the animation."""
163163 self .anim = animation .FuncAnimation (
164- self .fig ,
165- self ._update_frame ,
166- frames = 200 , # Can be increased for longer animation
167- interval = self .step_delay_ms ,
168- blit = True ,
164+ self .fig ,
165+ self ._update_frame ,
166+ frames = 500 ,
167+ interval = self .step_delay_ms ,
168+ blit = False ,
169169 repeat = False
170170 )
171171 plt .show ()
172172
173+
173174if __name__ == "__main__" :
174175 initial_maze = [
175176 [0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 ],
@@ -182,9 +183,9 @@ def run(self):
182183 [1 , 1 , 1 , 1 , 0 , 1 , 1 , 1 , 1 , 0 ],
183184 [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ],
184185 ]
185-
186+
186187 start_point = (0 , 0 )
187188 end_point = (8 , 9 )
188189
189- visualizer = MazeVisualizer (maze = initial_maze , start = start_point , target = end_point )
190- visualizer .run ()
190+ visualizer = MazeVisualizer (initial_maze , start_point , end_point )
191+ visualizer .run ()
0 commit comments