-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmaze.py
207 lines (177 loc) · 7.96 KB
/
maze.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import pygame
import sys
# Screen size and cell size used by the maze window
# Width and height of SCREEN_SIZE should be divisible by CELL_SIZE
SCREEN_SIZE = (640, 480)
CELL_SIZE = 32
# Some useful numbers needed for the bit manipulation
# Left-most 4 bits store backtrack path, next 4 bits solution,
# next 4 bits border and last 4 bits walls knocked down.
# From left to right, each 4 bit cluster represents W, S, E, N
# NOTE: Border bits are not currently used
# directions
# WSENWSENWSENWSEN
DEFAULT_CELL = 0b0000000000000000
# |bt||s ||b ||w |
WALL_BITS = 0b0000000000001111
BACKTRACK_BITS = 0b1111000000000000
SOLUTION_BITS = 0b0000111100000000
# Indices match each other
# WALLS[i] corresponds with COMPASS[i], DIRECTION[i], and OPPOSITE_WALLS[i]
WALLS = [0b1000, 0b0100, 0b0010, 0b0001]
OPPOSITE_WALLS = [0b0010, 0b0001, 0b1000, 0b0100]
COMPASS = [(-1, 0), (0, 1), (1, 0), (0, -1)]
DIRECTION = ['W', 'S', 'E', 'N']
# Colors
BLACK = (0, 0, 0, 255)
NO_COLOR = (0, 0, 0, 0)
WHITE = (255, 255, 255, 255)
GREEN = (0, 255, 0, 255)
RED = (255, 0, 0, 255)
BLUE = (0, 0, 255, 255)
LIGHT_BLUE = (0, 0, 255, 100)
PURPLE = (150, 0, 150, 255)
class Maze:
def __init__(self, initial_state='idle'):
# Maze set up
self.state = initial_state
self.w_cells = int(SCREEN_SIZE[0] / CELL_SIZE)
self.h_cells = int(SCREEN_SIZE[1] / CELL_SIZE)
self.total_cells = self.w_cells * self.h_cells
self.maze_array = [DEFAULT_CELL] * self.total_cells
# Pygame set up
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE)
self.background = pygame.Surface(self.screen.get_size())
self.m_layer = pygame.Surface(self.screen.get_size())
self.s_layer = pygame.Surface(self.screen.get_size())
self.setup_maze_window()
# Return cell neighbors within bounds of the maze
# Use self.state to determine which neighbors should be included
def cell_neighbors(self, cell):
# TODO: Logic for getting neighbors based on self.state
pass
# Connect two cells by knocking down the wall between them
# Update wall bits of from_cell and to_cell
def connect_cells(self, from_cell, to_cell, compass_index):
# TODO: Logic for updating cell bits
self.draw_connect_cells(from_cell, compass_index)
# Visit a cell along a possible solution path
# Update solution bits of from_cell and backtrack bits of to_cell
def visit_cell(self, from_cell, to_cell, compass_index):
# TODO: Logic for updating cell bits
self.draw_visited_cell(from_cell)
# Backtrack from cell
# Blank out the solution bits so it is no longer on the solution path
def backtrack(self, cell):
# TODO: Logic for updating cell bits
self.draw_backtracked_cell(cell)
# Visit cell in BFS search
# Update backtrack bits for use in reconstruct_solution
def bfs_visit_cell(self, cell, from_compass_index):
# TODO: Logic for updating cell bits
self.draw_bfs_visited_cell(cell)
# Reconstruct path to start using backtrack bits
def reconstruct_solution(self, cell):
self.draw_visited_cell(cell)
# TODO: Logic for reconstructing solution path in BFS
# Check if x, y values of cell are within bounds of maze
def cell_in_bounds(self, x, y):
return ((x >= 0) and (y >= 0) and (x < self.w_cells)
and (y < self.h_cells))
# Cell index from x, y values
def cell_index(self, x, y):
return y * self.w_cells + x
# x, y values from a cell index
def x_y(self, index):
x = index % self.w_cells
y = int(index / self.w_cells)
return x, y
# x, y point values from a cell index
def x_y_pos(self, index):
x, y = self.x_y(index)
x_pos = x * CELL_SIZE
y_pos = y * CELL_SIZE
return x_pos, y_pos
# Build solution array using solution bits
# Use DIRECTION to return cardinal directions representing solution path
def solution_array(self):
solution = []
# TODO: Logic to return cardinal directions representing solution path
return solution
# 'Knock down' wall between two cells, use in creation as walls removed
def draw_connect_cells(self, from_cell, compass_index):
x_pos, y_pos = self.x_y_pos(from_cell)
if compass_index == 0:
pygame.draw.line(self.m_layer, NO_COLOR, (x_pos, y_pos + 1),
(x_pos, y_pos + CELL_SIZE - 1))
elif compass_index == 1:
pygame.draw.line(self.m_layer, NO_COLOR, (x_pos + 1,
y_pos + CELL_SIZE), (x_pos + CELL_SIZE - 1,
y_pos + CELL_SIZE))
elif compass_index == 2:
pygame.draw.line(self.m_layer, NO_COLOR, (x_pos + CELL_SIZE,
y_pos + 1), (x_pos + CELL_SIZE,
y_pos + CELL_SIZE - 1))
elif compass_index == 3:
pygame.draw.line(self.m_layer, NO_COLOR, (x_pos + 1, y_pos),
(x_pos + CELL_SIZE - 1, y_pos))
# Draw green square at cell, use to visualize solution path being explored
def draw_visited_cell(self, cell):
x_pos, y_pos = self.x_y_pos(cell)
pygame.draw.rect(self.s_layer, GREEN, pygame.Rect(x_pos, y_pos,
CELL_SIZE, CELL_SIZE))
# Draws a red square at cell, use to change cell to visualize backtrack
def draw_backtracked_cell(self, cell):
x_pos, y_pos = self.x_y_pos(cell)
pygame.draw.rect(self.s_layer, RED, pygame.Rect(x_pos, y_pos,
CELL_SIZE, CELL_SIZE))
# Draw green square at cell, use to visualize solution path being explored
def draw_bfs_visited_cell(self, cell):
x_pos, y_pos = self.x_y_pos(cell)
pygame.draw.rect(self.s_layer, LIGHT_BLUE, pygame.Rect(x_pos, y_pos,
CELL_SIZE, CELL_SIZE))
# Process events, add each layer to screen (blip) and refresh (flip)
# Call this at the end of each traversal step to watch the maze as it is
# generated. Skip call until end of creation/solution to make faster.
def refresh_maze_view(self):
check_for_exit()
self.screen.blit(self.background, (0, 0))
self.screen.blit(self.s_layer, (0, 0))
self.screen.blit(self.m_layer, (0, 0))
pygame.display.flip()
def setup_maze_window(self):
# Set up window and layers
pygame.display.set_caption('Maze Generation and Solving')
pygame.mouse.set_visible(0)
self.background = self.background.convert()
self.background.fill(WHITE)
self.m_layer = self.m_layer.convert_alpha()
self.m_layer.fill(NO_COLOR)
self.s_layer = self.s_layer.convert_alpha()
self.s_layer.fill(NO_COLOR)
# Draw grid lines (will be whited out as walls knocked down)
for y in range(self.h_cells + 1):
pygame.draw.line(self.m_layer, BLACK, (0, y * CELL_SIZE),
(SCREEN_SIZE[0], y * CELL_SIZE))
for x in range(self.w_cells + 1):
pygame.draw.line(self.m_layer, BLACK, (x * CELL_SIZE, 0),
(x * CELL_SIZE, SCREEN_SIZE[1]))
# Color start cell blue and exit cell purple.
# Assumes start at top-left and exit at bottom-right
pygame.draw.rect(self.s_layer, BLUE,
pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE))
pygame.draw.rect(self.s_layer, PURPLE, pygame.Rect(
SCREEN_SIZE[0] - CELL_SIZE, SCREEN_SIZE[1] -
CELL_SIZE, CELL_SIZE, CELL_SIZE))
def check_for_exit():
# Aims for 60fps, checks for events.
# Without event check every frame, window will not exit!
clock = pygame.time.Clock()
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
sys.exit(0)