Skip to content

Commit e9472ef

Browse files
committed
Major refactoring, package preparation and maze unittesting
1 parent b0c78ca commit e9472ef

File tree

7 files changed

+160
-103
lines changed

7 files changed

+160
-103
lines changed

a_star/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
__author__ = 'Pablo Galindo Salgado'
2+
3+
from .a_star import a_star,Node,DijkstraHeap

a_star/a_star.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def a_star(graph, start, end):
8888

8989
current_node = frontier.pop()
9090

91-
if not current_node or current_node.point == end:
91+
if not current_node: #or current_node.point == end:
9292
return frontier
9393

9494
for neighbor in graph.neighbors( current_node.point ):

a_star/tools.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
class SquareGrid():
3+
4+
def __init__(self, width, height):
5+
self.width = width
6+
self.height = height
7+
self.walls = set()
8+
9+
def is_inside(self, point):
10+
""" Helper function to know if a given point is inside the Grid.
11+
12+
:param point: Tuple of two ints
13+
:return: Boolean
14+
"""
15+
x, y = point
16+
return 0 <= x < self.width and 0 <= y < self.height
17+
18+
def is_wall(self, point):
19+
""" Helper function to know if a given point is a wall.
20+
21+
:param point: Tuple of two ints
22+
:return: Boolean
23+
"""
24+
return point not in self.walls
25+
26+
def neighbors(self, point):
27+
""" Yields the valid neighbours of a given point.
28+
29+
:param point: Tuple of two ints
30+
:return: Generator of tuples of ints
31+
"""
32+
33+
x, y = point
34+
candidates = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1)]
35+
candidates = filter(self.is_inside, candidates)
36+
candidates = filter(self.is_wall, candidates)
37+
yield from candidates
38+
39+
def _draw_tile(self, point, style, width):
40+
""" Returns a symbol for the current point given the style dictionary and the drawing width.
41+
42+
:param point: Tuple of two ints
43+
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
44+
:param width: Integer representing the width of the graph
45+
:return: string (character)
46+
"""
47+
48+
character = "."
49+
if 'number' in style and point in style['number']:
50+
character = "%d" % style['number'][point]
51+
if 'point_to' in style and style['point_to'].get(point, None) is not None:
52+
(x1, y1) = point
53+
(x2, y2) = style['point_to'][point]
54+
if x2 == x1 + 1:
55+
character = "\u2192"
56+
if x2 == x1 - 1:
57+
character = "\u2190"
58+
if y2 == y1 + 1:
59+
character = "\u2193"
60+
if y2 == y1 - 1:
61+
character = "\u2191"
62+
if 'start' in style and point == style['start']:
63+
character = "S"
64+
if 'goal' in style and point == style['goal']:
65+
character = "E"
66+
if 'path' in style and point in style['path']:
67+
character = "@"
68+
if point in self.walls:
69+
character = "#" * width
70+
return character
71+
72+
def draw(self, width=2, **style):
73+
"""
74+
Draws the grid given the style dictionary and the width of the drawin.
75+
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
76+
:param width: Integer representing the width of the graph
77+
:return: None
78+
"""
79+
for y in range(self.height):
80+
for x in range(self.width):
81+
print("%%-%ds" % width %
82+
self._draw_tile((x, y), style, width), end="")
83+
print()
84+
85+
86+
class GridWithWeights(SquareGrid):
87+
88+
def __init__(self, width, height):
89+
super().__init__(width, height)
90+
self.weights = {}
91+
92+
def cost(self, from_node, to_node):
93+
"""
94+
Gives the cost of going from from_node to to_node.
95+
96+
:param from_node: Tuple of two ints
97+
:param to_node: Tuple of two ints
98+
:returns: int
99+
"""
100+
return self.weights.get(to_node, 1)
101+
102+

examples/maze_solving_example.py

Lines changed: 2 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,4 @@
11

2-
class SquareGrid():
3-
4-
def __init__(self, width, height):
5-
self.width = width
6-
self.height = height
7-
self.walls = set()
8-
9-
def is_inside(self, point):
10-
""" Helper function to know if a given point is inside the Grid.
11-
12-
:param point: Tuple of two ints
13-
:return: Boolean
14-
"""
15-
x, y = point
16-
return 0 <= x < self.width and 0 <= y < self.height
17-
18-
def is_wall(self, point):
19-
""" Helper function to know if a given point is a wall.
20-
21-
:param point: Tuple of two ints
22-
:return: Boolean
23-
"""
24-
return point not in self.walls
25-
26-
def neighbors(self, point):
27-
""" Yields the valid neighbours of a given point.
28-
29-
:param point: Tuple of two ints
30-
:return: Generator of tuples of ints
31-
"""
32-
33-
x, y = point
34-
candidates = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1)]
35-
candidates = filter(self.is_inside, candidates)
36-
candidates = filter(self.is_wall, candidates)
37-
yield from candidates
38-
39-
def _draw_tile(self, point, style, width):
40-
""" Returns a symbol for the current point given the style dictionary and the drawing width.
41-
42-
:param point: Tuple of two ints
43-
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
44-
:param width: Integer representing the width of the graph
45-
:return: string (character)
46-
"""
47-
48-
character = "."
49-
if 'number' in style and point in style['number']:
50-
character = "%d" % style['number'][point]
51-
if 'point_to' in style and style['point_to'].get(point, None) is not None:
52-
(x1, y1) = point
53-
(x2, y2) = style['point_to'][point]
54-
if x2 == x1 + 1:
55-
character = "\u2192"
56-
if x2 == x1 - 1:
57-
character = "\u2190"
58-
if y2 == y1 + 1:
59-
character = "\u2193"
60-
if y2 == y1 - 1:
61-
character = "\u2191"
62-
if 'start' in style and point == style['start']:
63-
character = "S"
64-
if 'goal' in style and point == style['goal']:
65-
character = "E"
66-
if 'path' in style and point in style['path']:
67-
character = "@"
68-
if point in self.walls:
69-
character = "#" * width
70-
return character
71-
72-
def draw(self, width=2, **style):
73-
"""
74-
Draws the grid given the style dictionary and the width of the drawin.
75-
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
76-
:param width: Integer representing the width of the graph
77-
:return: None
78-
"""
79-
for y in range(self.height):
80-
for x in range(self.width):
81-
print("%%-%ds" % width %
82-
self._draw_tile((x, y), style, width), end="")
83-
print()
84-
85-
86-
class GridWithWeights(SquareGrid):
87-
88-
def __init__(self, width, height):
89-
super().__init__(width, height)
90-
self.weights = {}
91-
92-
def cost(self, from_node, to_node):
93-
"""
94-
Gives the cost of going from from_node to to_node.
95-
96-
:param from_node: Tuple of two ints
97-
:param to_node: Tuple of two ints
98-
:returns: int
99-
"""
100-
return self.weights.get(to_node, 1)
101-
1022

1033
def from_id_width(point, width):
1044
"""
@@ -112,7 +12,8 @@ def from_id_width(point, width):
11212

11313
if __name__ == '__main__':
11414

115-
import a_star.a_star as a_star
15+
import a_star
16+
from a_star.tools import GridWithWeights
11617
import random
11718

11819
# Construct a cool wall collection from this aparently arbitraty points.
-3.14 KB
Binary file not shown.

tests/test_suite.py renamed to tests/test_dijkstra_heap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
import unittest
3-
import a_star.a_star as a_star
3+
import a_star
44

55

66

tests/test_maze.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
3+
import unittest
4+
import a_star
5+
from a_star.tools import GridWithWeights
6+
7+
8+
def backtrack( came_from_dict , start, end):
9+
10+
current = end
11+
yield current
12+
while current != start:
13+
current = came_from_dict[current]
14+
yield current
15+
16+
class MazeTests(unittest.TestCase):
17+
18+
def test_maze_1(self):
19+
maze = GridWithWeights(4,4)
20+
walls = [(1,1),(2,2)]
21+
maze.walls = walls
22+
weights = {(1,0):20,(3,0) : 2}
23+
maze.weights = weights
24+
my_solution = [(3,0),(3,1),(3,2),(3,3),(2,3),(1,3),(1,2),(0,2)]
25+
end = (3,0)
26+
start = (0,2)
27+
28+
# Call the A* algorithm and get the frontier
29+
frontier = a_star.a_star(graph = maze, start=start, end=end)
30+
solution = list(backtrack(frontier.visited,start,end))
31+
self.assertTrue( solution == my_solution )
32+
33+
34+
def test_2(self):
35+
maze = GridWithWeights(4,4)
36+
walls = []
37+
maze.walls = walls
38+
weights = {(0,0): 3, (0,1):1, (1,1): 4, (2,1):5,(3,1):1,(0,2): 2, (1,2):3, (2,2):3, (3,2): 2}
39+
maze.weights = weights
40+
41+
start = (0,1)
42+
end = (3,1)
43+
44+
# Call the A* algorithm and get the frontier
45+
frontier = a_star.a_star(graph = maze, start=start, end=end)
46+
maze.draw(width=3, point_to = frontier.visited, start=start, goal=end)
47+
maze.draw(width=3, number = frontier.costs, start=start, goal=end)
48+
print(frontier.visited)
49+
50+
51+
if __name__ == "__main__":
52+
unittest.main()

0 commit comments

Comments
 (0)