1
1
import pygame
2
2
from random import randint
3
3
import random
4
+ from collections import deque
4
5
5
6
pygame .init ()
6
7
7
- GRID_SIZE = 100
8
- SQUARE_SIZE = 5
9
- SPEED = float ( 'inf' )
8
+ GRID_SIZE = 20
9
+ SQUARE_SIZE = 25
10
+ SPEED = 10 # SPEED can be changed to speed up the visual process
10
11
11
12
WHITE = (255 , 255 , 255 )
12
- BLACK = (0 , 0 , 0 )
13
- RED = (255 , 0 , 0 )
14
13
BLUE = (0 , 0 , 255 )
15
- GREEN = (0 , 255 , 0 )
14
+ PURPLE = (255 , 0 , 255 )
15
+ BLACK = (0 , 0 , 0 )
16
+
16
17
18
+ # Line drawing function
17
19
18
- def lines (screen , clock , tup , x , y ):
20
+ def lines (screen , tup , x , y ):
19
21
if tup [0 ]:
20
22
pygame .draw .line (screen , BLACK , (x , y + SQUARE_SIZE ), (x + SQUARE_SIZE , y + SQUARE_SIZE ), SQUARE_SIZE // 5 )
21
23
if tup [1 ]:
@@ -24,14 +26,12 @@ def lines(screen, clock, tup, x, y):
24
26
pygame .draw .line (screen , BLACK , (x , y ), (x , y + SQUARE_SIZE ), SQUARE_SIZE // 5 )
25
27
if tup [3 ]:
26
28
pygame .draw .line (screen , BLACK , (x + SQUARE_SIZE , y ), (x + SQUARE_SIZE , y + SQUARE_SIZE ), SQUARE_SIZE // 5 )
27
- clock .tick (SPEED )
28
- pygame .display .flip ()
29
29
30
30
31
- def new_maze ():
32
- # New mazes are created by using depth-first search
33
- # to fill out a grid from a random spot on that grid without overlapping with discovered "cells"
31
+ # New mazes are created by using depth-first search to fill out a grid from a random spot on that grid
32
+ # without overlapping with discovered "cells"
34
33
34
+ def new_maze (screen ):
35
35
start = randint (0 , (GRID_SIZE ** 2 - 1 ))
36
36
discovered = [False ] * (GRID_SIZE ** 2 )
37
37
todo = [(start , - 1 )]
@@ -56,75 +56,114 @@ def new_maze():
56
56
random .shuffle (consider )
57
57
todo += consider
58
58
59
+ for cell in connected :
60
+ tup = [True , True , True , True ]
61
+ if cell + GRID_SIZE in connected [cell ]:
62
+ tup [0 ] = False
63
+ if cell - GRID_SIZE in connected [cell ]:
64
+ tup [1 ] = False
65
+ if cell - 1 in connected [cell ]:
66
+ tup [2 ] = False
67
+ if cell + 1 in connected [cell ]:
68
+ tup [3 ] = False
69
+ x = (cell % GRID_SIZE ) * SQUARE_SIZE
70
+ y = (cell // GRID_SIZE ) * SQUARE_SIZE
71
+ lines (screen , tup , x , y )
59
72
return connected
60
73
61
74
62
- def draw_maze (screen , clock , start , graph ):
63
- # To visually show the maze being drawn, I used pygame to display the solver discovering walls as it moves throughout the invisible maze.
64
- # The intention was to make it look as if a person who was plopped into a random maze were filling out their own map of said maze.
65
- # Green squares mark fully processed "cells", blue squares mark discovered but not processed "cells", and the black lines represent the walls blocking movement from each "cell"
75
+ # Paths are drawn by using breadth-first search to first determine the fastest path
76
+ # from point A to B (The first blue to purple)
66
77
78
+ def draw_path (screen , clock , start , graph ):
67
79
pygame .draw .line (screen , BLACK , (0 , 0 ), (GRID_SIZE * SQUARE_SIZE - 1 , 0 ), SQUARE_SIZE // 5 )
68
80
pygame .draw .line (screen , BLACK , (GRID_SIZE * SQUARE_SIZE - 1 , 0 ),
69
81
(GRID_SIZE * SQUARE_SIZE - 1 , GRID_SIZE * SQUARE_SIZE - 1 ), SQUARE_SIZE // 5 )
70
82
pygame .draw .line (screen , BLACK , (0 , (GRID_SIZE * SQUARE_SIZE ) - 1 ),
71
83
(GRID_SIZE * SQUARE_SIZE - 1 , GRID_SIZE * SQUARE_SIZE - 1 ), SQUARE_SIZE // 5 )
72
84
pygame .draw .line (screen , BLACK , (0 , 0 ), (0 , GRID_SIZE * SQUARE_SIZE - 1 ), SQUARE_SIZE // 5 )
73
85
74
- # This depth-first search algorithm both does all of the jobs said above and also displays the "cells" in their updated states
75
- discovered = [False ] * (len (graph ))
76
- processed = [False ] * (len (graph ))
77
- todo = [(start )]
78
- connected = {}
79
- while todo :
80
- square = todo .pop ()
81
- row = square // GRID_SIZE
82
- column = square % GRID_SIZE
86
+ # The endpoint of the maze is randomly generated
87
+ end = randint (0 , (GRID_SIZE ** 2 - 1 ))
88
+
89
+ # Breadth-first search is used to find the fastest point from A to B
90
+ discovered = deque ([start ])
91
+ processed = set ()
92
+ paths = {start : [start ]}
93
+ while discovered :
94
+ if end in discovered :
95
+ break
96
+ node = discovered .popleft ()
97
+ if node not in processed :
98
+ processed .add (node )
99
+ for vertex in graph [node ]:
100
+ if not vertex == node :
101
+ paths [vertex ] = list (paths [node ])
102
+ paths [vertex ].append (vertex )
103
+ discovered += graph [node ]
104
+
105
+ row = end // GRID_SIZE
106
+ column = end % GRID_SIZE
107
+ x = column * SQUARE_SIZE
108
+ y = row * SQUARE_SIZE
109
+ tup = [True , True , True , True ]
110
+ for neighbor in graph [end ]:
111
+ if neighbor == end + GRID_SIZE :
112
+ tup [0 ] = False
113
+ elif neighbor == end - GRID_SIZE :
114
+ tup [1 ] = False
115
+ elif neighbor == end - 1 :
116
+ tup [2 ] = False
117
+ elif neighbor == end + 1 :
118
+ tup [3 ] = False
119
+ rectangle = ((x , y ), (SQUARE_SIZE , SQUARE_SIZE ))
120
+ pygame .draw .rect (screen , PURPLE , rectangle )
121
+ lines (screen , tup , x , y )
122
+ clock .tick (SPEED )
123
+ pygame .display .flip ()
124
+
125
+ # The fastest path from point A to point B is now being displayed
126
+ for current_cell in paths [end ]:
127
+ row = current_cell // GRID_SIZE
128
+ column = current_cell % GRID_SIZE
83
129
x = column * SQUARE_SIZE
84
130
y = row * SQUARE_SIZE
85
- rectangle = ((x , y ), (SQUARE_SIZE , SQUARE_SIZE ))
86
- pygame .display .flip ()
87
131
tup = [True , True , True , True ]
88
- pygame .draw .rect (screen , RED , rectangle )
132
+ for neighbor in graph [current_cell ]:
133
+ if neighbor == current_cell + GRID_SIZE :
134
+ tup [0 ] = False
135
+ elif neighbor == current_cell - GRID_SIZE :
136
+ tup [1 ] = False
137
+ elif neighbor == current_cell - 1 :
138
+ tup [2 ] = False
139
+ elif neighbor == current_cell + 1 :
140
+ tup [3 ] = False
141
+ rectangle = ((x , y ), (SQUARE_SIZE , SQUARE_SIZE ))
142
+ pygame .draw .rect (screen , BLUE , rectangle )
143
+ lines (screen , tup , x , y )
89
144
clock .tick (SPEED )
90
145
pygame .display .flip ()
91
- if discovered [square ] and not processed [square ]:
92
- tup = list (connected [square ])
93
- pygame .draw .rect (screen , GREEN , rectangle )
94
- lines (screen , clock , tup , x , y )
95
- processed [square ] = True
96
- elif not discovered [square ]:
97
- discovered [square ] = True
98
- todo .append (square )
99
- for neighbor in graph [square ]:
100
- if neighbor == square + GRID_SIZE :
101
- tup [0 ] = False
102
- elif neighbor == square - GRID_SIZE :
103
- tup [1 ] = False
104
- elif neighbor == square - 1 :
105
- tup [2 ] = False
106
- elif neighbor == square + 1 :
107
- tup [3 ] = False
108
- if not discovered [neighbor ]:
109
- todo .append (neighbor )
110
- connected [square ] = list (tup )
111
- pygame .draw .rect (screen , BLUE , rectangle )
112
- lines (screen , clock , tup , x , y )
113
146
114
147
115
148
def main ():
116
149
clock = pygame .time .Clock ()
117
150
screen = pygame .display .set_mode ((GRID_SIZE * SQUARE_SIZE , GRID_SIZE * SQUARE_SIZE ))
118
151
152
+ # This continuously makes new mazes to be solved
119
153
while True :
120
154
for event in pygame .event .get ():
121
155
if event .type == pygame .QUIT :
122
156
pygame .quit ()
123
157
124
158
screen .fill (WHITE )
125
- start = randint (0 , (GRID_SIZE ** 2 - 1 )) # Picks random starting point for the filler
126
- g = new_maze () # Generates a new maze to fill
127
- draw_maze (screen , clock , start , g ) # Fills maze
159
+ start = randint (0 , (GRID_SIZE ** 2 - 1 ))
160
+
161
+ # Mazes are of course randomly generated but, in the right format,
162
+ # could be predetermined and put into the "new_maze" function as a replacement for "g"
163
+ g = new_maze (screen )
164
+ draw_path (screen , clock , start , g )
165
+
166
+ pygame .display .flip ()
128
167
129
168
130
169
if __name__ == "__main__" :
0 commit comments