/
p1.py
225 lines (186 loc) · 8.35 KB
/
p1.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# Kyle Cilia
# Alex Hoyt
# Dijkstra's in a Dungeon
# PA1
# CMPM146
from p1_support import load_level, show_level, save_level_costs
from math import inf, sqrt
from heapq import heappop, heappush
from contextlib import redirect_stdout
def dijkstras_shortest_path(initial_position, destination, graph, adj):
""" Searches for a minimal cost path through a graph using Dijkstra's algorithm.
Args:
initial_position: The initial cell from which the path extends.
destination: The end location for the path.
graph: A loaded level, containing walls, spaces, and waypoints.
adj: An adjacency function returning cells adjacent to a given cell as well as their respective edge costs.
Returns:
If a path exits, return a list containing all cells from initial_position to destination.
Otherwise, return None.
"""
dist = {} # distance from source to destination
prev = {} # previous node in optimal path from source
queue = [] # queue initialization
dist[initial_position] = 0
prev[initial_position] = None #prev from source
#queue = [0, start]
heappush(queue, (dist[initial_position], initial_position))
while queue:
# Pop least cost node
curr_cost, curr_node = heappop(queue)
# Once we find the destination, break the loop
if curr_node == destination:
break
# Use navigation_edges to get adjacent cells
adjacent = adj(graph, curr_node)
# Iterate through adjacency list and calculate cost
for acell, cost in adjacent:
# Variable to store cost of path consisting of current cost and the cost of the
# adjacent cell
pathcost = curr_cost + cost
if acell not in dist or pathcost < dist[acell]:
dist[acell] = pathcost
prev[acell] = curr_node
heappush(queue, (pathcost, acell))
# Build path to return
if curr_node == destination:
path = []
# Building path in reverse order because we're at the destination
while curr_node:
path.append(curr_node)
curr_node = prev[curr_node]
# Reversing the path
path.reverse()
return path
else:
# Return empty list if there is no path
return []
pass
def dijkstras_shortest_path_to_all(initial_position, graph, adj):
""" Calculates the minimum cost to every reachable cell in a graph from the initial_position.
Args:
initial_position: The initial cell from which the path extends.
graph: A loaded level, containing walls, spaces, and waypoints.
adj: An adjacency function returning cells adjacent to a given cell as well as their respective edge costs.
Returns:
A dictionary, mapping destination cells to the cost of a path from the initial_position.
"""
dist = {} # distance from source to destination
prev = {} # previous node in optimal path from source
queue = [] # queue initialization
dist[initial_position] = 0
prev[initial_position] = None #prev from source
heappush(queue, (dist[initial_position], initial_position))
while queue:
curr_cost, curr_node = heappop(queue)
# Use navigation_edges to get adjacent cells
adjacent = adj(graph, curr_node)
# Iterate through adjacency list and calculate cost
for acell, cost in adjacent:
# Variable to store cost of path consisting of current cost and the cost of the
# adjacent cell
tempcost = curr_cost + cost
# Updating dist of cells
if acell not in dist or tempcost < dist[acell]:
dist[acell] = tempcost
prev[acell] = curr_node
heappush(queue, (tempcost, acell))
return dist
pass
def navigation_edges(level, cell):
""" Provides a list of adjacent cells and their respective costs from the given cell.
Args:
level: A loaded level, containing walls, spaces, and waypoints.
cell: A target location.
Returns:
A list of tuples containing an adjacent cell's coordinates and the cost of the edge joining it and the
originating cell.
E.g. from (0,0):
[((0,1), 1),
((1,0), 1),
((1,1), 1.4142135623730951),
... ]
"""
# List to return
adjacency_list = []
# deltas for x coordinate
for dx in (-1, 0, 1):
# deltas for y coordinates
for dy in (-1, 0, 1):
# get adjacent cell using deltas
adjcell = (cell[0] + dx, cell[1] + dy)
# if statement for normal cost formula, checking if the cell is not in a corner
if (dx == 0 and dy != 0) or (dx != 0 and dy == 0):
# checking to make sure adjcell is a space rather than a wall
if adjcell in level['spaces']:
adjacent = (adjcell, level['spaces'][adjcell]/2 + level['spaces'][cell]/2)
adjacency_list.append(adjacent)
# if statement for diagonal cost formula, checking if cell is a corner
if (dx != 0 and dy != 0):
if adjcell in level['spaces']:
adjacent = (adjcell, (sqrt(2)*0.5*level['spaces'][adjcell]) + (sqrt(2)*0.5*level['spaces'][cell]))
adjacency_list.append(adjacent)
# Do nothing if both change in x and y is not 0 because that is the origin cell
return adjacency_list
pass
def test_route(filename, src_waypoint, dst_waypoint):
""" Loads a level, searches for a path between the given waypoints, and displays the result.
Args:
filename: The name of the text file containing the level.
src_waypoint: The character associated with the initial waypoint.
dst_waypoint: The character associated with the destination waypoint.
"""
# Load and display the level.
level = load_level(filename)
show_level(level)
# Retrieve the source and destination coordinates from the level.
src = level['waypoints'][src_waypoint]
dst = level['waypoints'][dst_waypoint]
# Search for and display the path from src to dst.
path = dijkstras_shortest_path(src, dst, level, navigation_edges)
if path:
show_level(level, path)
else:
print("No path possible!")
def cost_to_all_cells(filename, src_waypoint, output_filename):
""" Loads a level, calculates the cost to all reachable cells from
src_waypoint, then saves the result in a csv file with name output_filename.
Args:
filename: The name of the text file containing the level.
src_waypoint: The character associated with the initial waypoint.
output_filename: The filename for the output csv file.
"""
# Load and display the level.
level = load_level(filename)
show_level(level)
# Retrieve the source coordinates from the level.
src = level['waypoints'][src_waypoint]
# Calculate the cost to all reachable cells from src and save to a csv file.
costs_to_all_cells = dijkstras_shortest_path_to_all(src, level, navigation_edges)
save_level_costs(level, costs_to_all_cells, output_filename)
def export_new_maze(filename, destinationfile, src_waypoint, dst_waypoint):
level = load_level(filename)
show_level(level)
# Retrieve the source and destination coordinates from the level.
src = level['waypoints'][src_waypoint]
dst = level['waypoints'][dst_waypoint]
# Search for and display the path from src to dst.
path = dijkstras_shortest_path(src, dst, level, navigation_edges)
if path:
show_level(level, path)
with open(destinationfile, "w") as f:
with redirect_stdout(f):
show_level(level, path)
print("Wrote to " + destinationfile)
else:
print("No path possible!")
if __name__ == '__main__':
filename, src_waypoint, dst_waypoint = 'my_maze.txt', 'a','d'
destinationfile = "my_maze_path.txt"
# Use this function call to find the route between two waypoints.
test_route(filename, src_waypoint, dst_waypoint)
# Use this function to calculate the cost to all reachable cells from an origin point.
cost_to_all_cells(filename, src_waypoint, 'my_maze_costs.csv')
# Use this one for assignment testing purposes
# fname, src_wp, dst_wp = 'my_maze.txt', 'a','d'
export_new_maze(filename, destinationfile, src_waypoint, dst_waypoint)