Skip to content

Commit 174244e

Browse files
committed
implemented bidirectional bfs
1 parent a180c2b commit 174244e

File tree

5 files changed

+157
-52
lines changed

5 files changed

+157
-52
lines changed

algo.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from cell import Cell
12
from grid import Grid
23
from queue import PriorityQueue, Queue
34
from utils import aborted, Heuristic
45

56

6-
def reconstruct_path(came_from, current) -> list:
7+
def reconstruct_path(came_from, current) -> list[Cell]:
78
path = []
89
while current in came_from:
910
current = came_from[current]
@@ -13,7 +14,19 @@ def reconstruct_path(came_from, current) -> list:
1314
path.reverse()
1415

1516
#pop start cell from the path list, so animation wouldn't redraw it
16-
path.pop(0)
17+
if len(path) != 0:
18+
path.pop(0)
19+
20+
return path
21+
22+
23+
def reconstruct_path_bbfs(came_from, current) -> list[Cell]:
24+
path = [current]
25+
while current in came_from:
26+
current = came_from[current]
27+
path.append(current)
28+
29+
path.pop()
1730

1831
return path
1932

@@ -128,6 +141,7 @@ def dijkstra(draw, grid, start, end, animation: bool) -> None:
128141
current.visit()
129142

130143

144+
131145
def dfs(draw, grid, start, end, animation) -> None:
132146
marked = {cell: False for row in grid.raw_grid for cell in row}
133147
stack = [start]
@@ -191,6 +205,54 @@ def bfs(draw, grid, start, end, animation: bool) -> None:
191205
current.visit()
192206

193207

208+
def bidirectional_bfs(draw, grid, start, end, animation: bool) -> None:
209+
Qf = Queue()
210+
Qb = Queue()
211+
explored_forward = {start}
212+
explored_backwards = {end}
213+
Qf.put(start)
214+
Qb.put(end)
215+
came_from_forward = {}
216+
came_from_backwards = {}
217+
218+
while not Qf.empty() and not Qb.empty():
219+
if aborted():
220+
return
221+
222+
u = Qf.get()
223+
v = Qb.get()
224+
225+
if u in explored_backwards:
226+
path: list[Cell] = reconstruct_path(came_from_forward, u) + reconstruct_path_bbfs(came_from_backwards, u)
227+
animate_path(draw, path, grid, animation)
228+
return
229+
230+
for neighbor in u.neighbors:
231+
if neighbor not in explored_forward:
232+
explored_forward.add(neighbor)
233+
Qf.put(neighbor)
234+
came_from_forward[neighbor] = u
235+
if neighbor != end and neighbor != start:
236+
neighbor.make_open()
237+
238+
for neighbor in v.neighbors:
239+
if neighbor not in explored_backwards:
240+
explored_backwards.add(neighbor)
241+
Qb.put(neighbor)
242+
came_from_backwards[neighbor] = v
243+
if neighbor != end and neighbor != start:
244+
neighbor.make_open()
245+
246+
if animation:
247+
draw()
248+
249+
if u != start and u != end:
250+
u.visit()
251+
252+
if v != end and v != start:
253+
v.visit()
254+
255+
194256
def gbfs(draw, grid: Grid, start, end, animation: bool):
195257
queue = PriorityQueue()
196258
visited = {start}
@@ -211,8 +273,7 @@ def gbfs(draw, grid: Grid, start, end, animation: bool):
211273
if neighbor not in visited:
212274
count += 1
213275
if neighbor.is_end():
214-
path = []
215-
path.append(current_cell)
276+
path = [current_cell]
216277
while current_cell in came_from:
217278
current_cell = came_from[current_cell]
218279
path.append(current_cell)
@@ -233,3 +294,4 @@ def gbfs(draw, grid: Grid, start, end, animation: bool):
233294
draw()
234295

235296

297+

grid.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ def update_neighbors_by_direction_for_every_cell(self) -> None:
4747
cell.update_neighbors_by_direction(self.raw_grid)
4848

4949

50-
def draw_under_grid_cells(self) -> None:
50+
def draw_under_grid_lines(self) -> None:
5151
for row in self.raw_grid:
5252
for cell in row:
5353
if not cell.is_wall():
5454
cell.draw(self.win)
5555

56-
def draw_over_grid_cells(self) -> None:
56+
def draw_over_grid_lines(self) -> None:
5757
for row in self.raw_grid:
5858
for cell in row:
5959
if cell.is_wall():
@@ -93,6 +93,11 @@ def clear(self, start_end_except = False, barrier_except = False) -> None:
9393
cell.reset()
9494

9595

96+
def draw_grid_frame(self) -> None:
97+
pygame.draw.line(self.win, GREY, ((self.x - 3, self.y)), (self.x - 3, self.y + self.height), width=5)
98+
pygame.draw.line(self.win, GREY, ((self.x + self.width + 3, self.y)), (self.x + self.width + 3, self.y + self.height), width=5)
99+
pygame.draw.line(self.win, GREY, ((0,self.y - 2)), (self.win.get_width(), self.y - 2), width=5)
100+
pygame.draw.line(self.win, GREY, ((0, self.y + self.height + 2)), (self.win.get_width(), self.y + self.height + 2), width=5)
96101

97102

98103
def make_all_cells_wall(self) -> None:

gui_theme.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,11 @@
3434
}
3535
},
3636

37-
"#legend_lable":
37+
"@legend_cell_lables":
3838
{
3939
"colours":
4040
{
4141
"normal_text":"#45494e"
42-
},
43-
44-
"font_info_dict":
45-
{
46-
"size": "25"
4742
}
48-
4943
}
5044
}

legend_cell.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pygame
2+
from grid import Grid
3+
4+
class LegendCell:
5+
def __init__(self, win, x, y, color, frame_color, grid: Grid) -> None:
6+
self.win = win
7+
self.x = x
8+
self.y = y
9+
self.color = color
10+
self.width, self.height = grid.gap, grid.gap
11+
self.frame_color = frame_color
12+
13+
14+
def draw_legend_cell(self):
15+
pygame.draw.rect(self.win, self.color, (self.x, self.y, self.width, self.height))
16+
pygame.draw.line(self.win, self.frame_color, (self.x, self.y), (self.x, + self.y + self.height))
17+
pygame.draw.line(self.win, self.frame_color, (self.x + self.width, self.y), (self.x + self.width, self.y + self.height))
18+
pygame.draw.line(self.win, self.frame_color, (self.x, self.y), (self.x + self.width, self.y))
19+
pygame.draw.line(self.win, self.frame_color, (self.x, self.y + self.height), (self.x + self.width, self.y + self.height))

main.py

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
2-
from typing import Callable
32
import maze
43
import algo
4+
import cell
55
import pygame
66
import pygame_gui
7-
from grid import Grid, GREY
8-
from cell import Cell
7+
from typing import Callable
98
from strenum import StrEnum
9+
from grid import Grid, GREY
10+
from legend_cell import LegendCell
11+
from pygame_gui.core import ObjectID
1012

1113

1214
# """1:1 cell size = 16px"""
@@ -18,7 +20,6 @@
1820
# GRID_DIMENSIONS = (1680, 720) #px
1921

2022

21-
2223
WIDTH, HEIGHT = 1291, 765
2324
GRID_WIDTH, GRID_HEIGHT = 1280, 680
2425
GRID_SIZE = (34, 64) #(rows, columns)
@@ -38,18 +39,16 @@
3839
pygame.display.set_icon(LOGO)
3940

4041

41-
def draw(win, grid, UI_MANAGER, time_delta) -> None:
42+
def draw(win, grid, UI_MANAGER, time_delta, legend_cells) -> None:
4243
win.fill(BG_COLOR)
43-
grid.draw_under_grid_cells()
44+
grid.draw_under_grid_lines()
4445
grid.draw_grid_lines()
45-
grid.draw_over_grid_cells()
46+
grid.draw_over_grid_lines()
4647
UI_MANAGER.update(time_delta)
47-
48-
#grid frame lines
49-
pygame.draw.line(win, GREY, ((grid.x - 3, grid.y)), (grid.x - 3, grid.y + grid.height), width=5)
50-
pygame.draw.line(win, GREY, ((grid.x + grid.width + 3, grid.y)), (grid.x + grid.width + 3, grid.y + grid.height), width=5)
51-
pygame.draw.line(win, GREY, ((0, grid.y - 2)), (WIDTH, grid.y - 2), width=5)
52-
pygame.draw.line(win, GREY, ((0, grid.y + grid.height + 2)), (WIDTH, grid.y + grid.height + 2), width=5)
48+
grid.draw_grid_frame()
49+
50+
for legend_cell in legend_cells:
51+
legend_cell.draw_legend_cell()
5352

5453
UI_MANAGER.draw_ui(WIN)
5554
pygame.display.update()
@@ -61,22 +60,22 @@ class Algorithms(StrEnum):
6160
DFS = "Depth-first Search"
6261
BFS ="Breadth-first Search"
6362
GBFS = "Greedy Best-first Search"
63+
BBFS = "Bidirectional BFS"
6464

6565
class Mazes(StrEnum):
6666
RECDIV = "Recursive Division Maze"
6767
RANDOM_DFS = "Randomized Depth-first Search Maze"
6868
SPIRAL = "Spiral Maze"
69-
7069

7170

7271
def main() -> None:
7372
clock = pygame.time.Clock()
7473
grid = Grid(WIN, GRID_SIZE, (GRID_WIDTH, GRID_HEIGHT), GRID_POSITION)
7574

7675
#scales images to correct cell size
77-
Cell.scale_cell_imgs(grid.gap, grid.gap)
76+
cell.Cell.scale_cell_imgs(grid.gap, grid.gap)
7877

79-
draw_lambda = lambda: draw(WIN, grid, UI_MANAGER, time_delta)
78+
draw_lambda = lambda: draw(WIN, grid, UI_MANAGER, time_delta, legend_cells)
8079

8180

8281
#gui elements initialization
@@ -108,11 +107,45 @@ def main() -> None:
108107
text='CE',
109108
manager=UI_MANAGER)
110109

111-
# legend_lable = pygame_gui.elements.UILabel(
112-
# relative_rect=pygame.Rect((grid.x, HEIGHT - 35), (100, 20)),
113-
# text="Legend: ",
114-
# manager=UI_MANAGER,
115-
# object_id="#legend_lable")
110+
unvisited_cell_lable = pygame_gui.elements.UILabel(
111+
relative_rect=pygame.Rect((grid.x + grid.gap, HEIGHT - 35), (130, grid.gap)),
112+
text="-unvisited cell",
113+
manager=UI_MANAGER,
114+
object_id=ObjectID(class_id="@legend_cell_lables"))
115+
116+
visited_cell_lable = pygame_gui.elements.UILabel(
117+
relative_rect=pygame.Rect((grid.x + 200 + grid.gap, HEIGHT - 35), (115, grid.gap)),
118+
text="-visited cell",
119+
manager=UI_MANAGER,
120+
object_id=ObjectID(class_id="@legend_cell_lables"))
121+
122+
open_cell_lable = pygame_gui.elements.UILabel(
123+
relative_rect=pygame.Rect((grid.x + 400 + grid.gap, HEIGHT - 35), (90, grid.gap)),
124+
text="-open cell",
125+
manager=UI_MANAGER,
126+
object_id=ObjectID(class_id="@legend_cell_lables"))
127+
128+
path_cell_lable = pygame_gui.elements.UILabel(
129+
relative_rect=pygame.Rect((grid.x + 600 + grid.gap, HEIGHT - 35), (90, grid.gap)),
130+
text="-path cell",
131+
manager=UI_MANAGER,
132+
object_id=ObjectID(class_id="@legend_cell_lables"))
133+
134+
wall_cell_lable = pygame_gui.elements.UILabel(
135+
relative_rect=pygame.Rect((grid.x + 800 + grid.gap, HEIGHT - 35), (90, grid.gap)),
136+
text="-wall cell",
137+
manager=UI_MANAGER,
138+
object_id=ObjectID(class_id="@legend_cell_lables"))
139+
140+
legend_cells = [
141+
LegendCell(WIN, grid.x, HEIGHT - 35, cell.UNVISITED_COLOR, GREY, grid),
142+
LegendCell(WIN, grid.x + 200, HEIGHT - 35, cell.VISITED_COLOR, GREY, grid),
143+
LegendCell(WIN, grid.x + 400, HEIGHT - 35, cell.OPEN_COLOR, GREY, grid),
144+
LegendCell(WIN, grid.x + 600, HEIGHT - 35, cell.PATH_COLOR, GREY, grid),
145+
LegendCell(WIN, grid.x + 800, HEIGHT - 35, cell.WALL_COLOR, GREY, grid)]
146+
147+
148+
116149

117150
start = grid[grid.total_rows // 2][grid.total_columns // 2 - 2]
118151
end = grid[grid.total_rows // 2][grid.total_columns // 2 + 2]
@@ -128,7 +161,7 @@ def main() -> None:
128161

129162
while running:
130163
time_delta = clock.tick(FPS)/1000.0
131-
draw(WIN, grid, UI_MANAGER, time_delta)
164+
draw(WIN, grid, UI_MANAGER, time_delta, legend_cells)
132165

133166

134167
for event in pygame.event.get():
@@ -258,26 +291,15 @@ def main() -> None:
258291
pygame.quit()
259292

260293

261-
# def set_default_position_for_points(start: list, end: list, grid: Grid):
262-
# start[0].reset()
263-
# end[0].reset()
264-
# start[0] = grid[grid.total_rows // 2][grid.total_columns // 2 - 2]
265-
# end[0] = grid[grid.total_rows // 2][grid.total_columns // 2 + 2]
266-
# start[0].make_start()
267-
# end[0].make_end()
268-
269-
270-
271-
272294
def run_current_algorithm(
273295
algo_menu: pygame_gui.elements.UIDropDownMenu,
274296
draw: Callable,
275297
grid: Grid,
276-
start: Cell,
277-
end: Cell,
298+
start: cell.Cell,
299+
end: cell.Cell,
278300
animation: bool) -> None:
279301

280-
"""Calls funcion that visulizes selected algorithm"""
302+
"""Calls function that visulizes selected algorithm"""
281303

282304
if algo_menu.selected_option == Algorithms.ASTAR:
283305
algo.astar(draw, grid, start, end, animation)
@@ -293,6 +315,9 @@ def run_current_algorithm(
293315

294316
elif algo_menu.selected_option == Algorithms.GBFS:
295317
algo.gbfs(draw, grid, start, end, animation)
318+
319+
elif algo_menu.selected_option == Algorithms.BBFS:
320+
algo.bidirectional_bfs(draw, grid, start, end, animation)
296321

297322
else:
298323
algo.astar(draw, grid, start, end, animation)
@@ -302,11 +327,11 @@ def generate_current_maze(
302327
maze_menu: pygame_gui.elements.UIDropDownMenu,
303328
draw: Callable,
304329
grid: Grid,
305-
start: Cell,
306-
end: Cell,
330+
start: cell.Cell,
331+
end: cell.Cell,
307332
animation: bool) -> None:
308333

309-
"""Calls funcion that generates selected maze"""
334+
"""Calls function that generates selected maze"""
310335

311336
if maze_menu.selected_option == Mazes.RECDIV:
312337
maze.recursive_division_maze_gen(draw, grid, animation)

0 commit comments

Comments
 (0)