-
Notifications
You must be signed in to change notification settings - Fork 0
/
2-bfs-grid.py
119 lines (88 loc) · 3.83 KB
/
2-bfs-grid.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
# Sample code from https://www.redblobgames.com/
# Copyright 2014 Red Blob Games <[email protected]>
# License: Apache v2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
from __future__ import annotations
from typing import Protocol, Iterator, Tuple, TypeVar, Optional
T = TypeVar('T')
# Grids can be expressed as graphs too. I’ll now define a new graph called SquareGrid, with GridLocation being a tuple (x: int, y: int) that labels each location in the grid.
GridLocation = Tuple[int, int]
class SquareGrid:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.walls: list[GridLocation] = []
def in_bounds(self, id: GridLocation) -> bool:
(x, y) = id
return 0 <= x < self.width and 0 <= y < self.height
def passable(self, id: GridLocation) -> bool:
return id not in self.walls
def neighbors(self, id: GridLocation) -> Iterator[GridLocation]:
(x, y) = id
neighbors = [(x+1, y), (x-1, y), (x, y-1), (x, y+1)] # E W N S
if (x + y) % 2 == 0: neighbors.reverse() # S N W E
results = filter(self.in_bounds, neighbors)
results = filter(self.passable, results)
return results
# utility functions for dealing with square grids
def from_id_width(id, width):
return (id % width, id // width)
def draw_tile(graph, id, style):
r = " . "
if 'number' in style and id in style['number']: r = " %-2d" % style['number'][id]
if 'point_to' in style and style['point_to'].get(id, None) is not None:
(x1, y1) = id
(x2, y2) = style['point_to'][id]
if x2 == x1 + 1: r = " > "
if x2 == x1 - 1: r = " < "
if y2 == y1 + 1: r = " v "
if y2 == y1 - 1: r = " ^ "
if 'path' in style and id in style['path']: r = " @ "
if 'start' in style and id == style['start']: r = " A "
if 'goal' in style and id == style['goal']: r = " Z "
if id in graph.walls: r = "###"
return r
def draw_grid(graph, **style):
print("___" * graph.width)
for y in range(graph.height):
for x in range(graph.width):
print("%s" % draw_tile(graph, (x, y), style), end="")
print()
print("~~~" * graph.width)
DIAGRAM1_WALLS = [from_id_width(id, width=30) for id in [21,22,51,52,81,82,93,94,111,112,123,124,133,134,141,142,153,154,163,164,171,172,173,174,175,183,184,193,194,201,202,203,204,205,213,214,223,224,243,244,253,254,273,274,283,284,303,304,313,314,333,334,343,344,373,374,403,404,433,434]]
# Create a grid and print it out
g = SquareGrid(30, 15)
g.walls = DIAGRAM1_WALLS
print("Grid before bfs search")
draw_grid(g) # uncomment to see the grid
import collections
# Queue a data structure used by the search algorithm to decide the order in which to process the graph locations.
class Queue:
def __init__(self):
self.elements = collections.deque()
def empty(self) -> bool:
return not self.elements
def put(self, x: T):
self.elements.append(x)
def get(self) -> T:
return self.elements.popleft()
def breadth_first_search(graph: Graph, start: Location, goal: Location):
frontier = Queue()
frontier.put(start)
came_from: dict[Location, Optional[Location]] = {}
came_from[start] = None
while not frontier.empty():
current: Location = frontier.get()
if current == goal: # early exit
break
for next in graph.neighbors(current):
if next not in came_from:
frontier.put(next)
came_from[next] = current
return came_from
g = SquareGrid(30, 15)
g.walls = DIAGRAM1_WALLS
start = (8, 7)
goal = (17, 2)
parents = breadth_first_search(g, start, goal)
print("Grid after bfs search, A is the starting point, Z the goal")
draw_grid(g, point_to=parents, start=start, goal=goal)