/
test_pathfinding.gd
331 lines (309 loc) · 9.39 KB
/
test_pathfinding.gd
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
extends "res://addons/gut/test.gd"
var HexCell = load("res://HexCell.gd")
var HexGrid = load("res://HexGrid.gd")
var grid
var map
# This is the hex map we'll test with:
# remember: +y is N, +x is NE
"""
.
.
. .
. .
. . .
. . .
. . . .
. O B .
OF O . C
. E O .
O O D
. O .
. .
. A
G
. <- (0, 0)
"""
var a_pos = Vector2(2, 0)
var b_pos = Vector2(4, 2)
var c_pos = Vector2(7, 0)
var d_pos = Vector2(5, 0)
var e_pos = Vector2(2, 2)
var f_pos = Vector2(1, 3)
var g_pos = Vector2(1, 0)
var obstacles = [
Vector2(2, 1),
Vector2(3, 1),
Vector2(4, 1),
Vector2(1, 2),
Vector2(3, 2),
Vector2(1, 3),
Vector2(2, 3),
]
func setup():
grid = HexGrid.new()
grid.set_bounds(Vector2(0, 0), Vector2(7, 4))
grid.add_obstacles(obstacles)
func test_bounds():
# Push the boundaries
# Check that the test boundary works properly
assert_eq(grid.get_hex_cost(Vector2(0, 0)), grid.path_cost_default, "SW is open")
assert_eq(grid.get_hex_cost(Vector2(0, 4)), grid.path_cost_default, "W is open")
assert_eq(grid.get_hex_cost(Vector2(7, 0)), grid.path_cost_default, "E is open")
assert_eq(grid.get_hex_cost(Vector2(7, 4)), grid.path_cost_default, "NE is open")
assert_eq(grid.get_hex_cost(Vector2(8, 2)), 0, "Too much X is blocked")
assert_eq(grid.get_hex_cost(Vector2(6, 5)), 0, "Too much Y is blocked")
assert_eq(grid.get_hex_cost(Vector2(-1, 2)), 0, "Too little X is blocked")
assert_eq(grid.get_hex_cost(Vector2(6, -1)), 0, "Too little Y is blocked")
func test_negative_bounds():
# Test negative space
grid = HexGrid.new()
grid.set_bounds(Vector2(-5, -5), Vector2(-2, -2))
assert_eq(grid.get_hex_cost(Vector2(-2, -2)), grid.path_cost_default)
assert_eq(grid.get_hex_cost(Vector2(-5, -5)), grid.path_cost_default)
assert_eq(grid.get_hex_cost(Vector2(0, 0)), 0)
assert_eq(grid.get_hex_cost(Vector2(-6, -3)), 0)
assert_eq(grid.get_hex_cost(Vector2(-3, -1)), 0)
func test_roundabounds():
# We can also go both ways
grid.set_bounds(Vector2(-3, -3), Vector2(2, 2))
assert_eq(grid.get_hex_cost(Vector2(-3, -3)), grid.path_cost_default)
assert_eq(grid.get_hex_cost(Vector2(2, 2)), grid.path_cost_default)
assert_eq(grid.get_hex_cost(Vector2(0, 0)), grid.path_cost_default)
assert_eq(grid.get_hex_cost(Vector2(-4, 0)), 0)
assert_eq(grid.get_hex_cost(Vector2(0, 3)), 0)
func test_grid_obstacles():
# Make sure we can obstacleize the grid
assert_eq(grid.get_obstacles().size(), obstacles.size())
# Test adding via a HexCell instance
grid.add_obstacles(HexCell.new(Vector2(0, 0)))
assert_eq(grid.get_obstacles()[Vector2(0, 0)], 0)
# Test replacing an obstacle
grid.add_obstacles(Vector2(0, 0), 2)
assert_eq(grid.get_obstacles()[Vector2(0, 0)], 2)
# Test removing an obstacle
grid.remove_obstacles(Vector2(0, 0))
assert_does_not_have(grid.get_obstacles(), Vector2(0, 0))
# Make sure removing a non-obstacle doesn't error
grid.remove_obstacles(Vector2(0, 0))
func test_grid_barriers():
# Make sure we can barrier things on the grid
assert_eq(grid.get_barriers().size(), 0)
# Add a barrier
var coords = Vector2(0, 0)
var barriers = grid.get_barriers()
grid.add_barriers(coords, HexCell.DIR_N)
assert_eq(barriers.size(), 1)
assert_has(barriers, coords)
assert_eq(barriers[coords].size(), 1)
assert_has(barriers[coords], HexCell.DIR_N)
# Overwrite the barrier
grid.add_barriers(coords, HexCell.DIR_N, 1337)
assert_eq(barriers[coords][HexCell.DIR_N], 1337)
# Add more barrier to the hex
grid.add_barriers(coords, [HexCell.DIR_S, HexCell.DIR_NE])
assert_eq(barriers[coords].size(), 3)
assert_has(barriers[coords], HexCell.DIR_N)
# Remove part of the hex's barrier
grid.remove_barriers(coords, [HexCell.DIR_N])
assert_eq(barriers[coords].size(), 2)
assert_does_not_have(barriers[coords], HexCell.DIR_N)
assert_has(barriers[coords], HexCell.DIR_S)
assert_has(barriers[coords], HexCell.DIR_NE)
# Remove all the hex's barriers
grid.remove_barriers(coords)
assert_eq(barriers.size(), 0)
# Remove no barrier with no error
grid.remove_barriers([Vector2(1, 1), Vector2(2, 2)])
func test_hex_costs():
# Test that the price is right
assert_eq(grid.get_hex_cost(HexCell.new(Vector2(1, 1))), grid.path_cost_default, "Open hex is open")
assert_eq(grid.get_hex_cost(Vector3(2, 1, -3)), 0, "Obstacle being obstructive")
# Test partial obstacle
grid.add_obstacles(Vector2(1, 1), 1.337)
assert_eq(grid.get_hex_cost(Vector2(1, 1)), 1.337, "9")
func test_move_costs():
# Test that more than just hex costs are at work
assert_eq(grid.get_move_cost(Vector2(0, 0), HexCell.DIR_N), grid.path_cost_default)
func test_move_cost_barrier():
# Put up a barrier
grid.add_barriers(Vector2(0, 0), HexCell.DIR_N)
assert_eq(grid.get_move_cost(Vector2(0, 0), HexCell.DIR_N), 0)
func test_move_cost_barrier_backside():
# The destination has a barrier
grid.add_barriers(Vector2(0, 1), HexCell.DIR_S)
assert_eq(grid.get_move_cost(Vector2(0, 0), HexCell.DIR_N), 0)
func test_move_cost_cumulative():
# Test that moving adds up hex and barrier values
# But NOT from the *starting* hex!
grid.add_obstacles(Vector2(0, 0), 1)
grid.add_obstacles(Vector2(0, 1), 2)
grid.add_barriers(Vector2(0, 0), HexCell.DIR_N, 4)
grid.add_barriers(Vector2(0, 1), HexCell.DIR_S, 8)
assert_eq(grid.get_move_cost(Vector2(0, 0), HexCell.DIR_N), 14)
func check_path(got, expected):
# Assert that the gotten path was the expected route
assert_eq(got.size(), expected.size(), "Path should be as long as expected")
for idx in range(min(got.size(), expected.size())):
var hex = got[idx]
var check = expected[idx]
if typeof(check) == TYPE_ARRAY:
# In case of multiple valid paths
assert_has(check, hex.axial_coords)
else:
assert_eq(check, hex.axial_coords)
func test_straight_line():
# Path between A and C is straight
var path = [
a_pos,
Vector2(3, 0),
Vector2(4, 0),
Vector2(5, 0),
Vector2(6, 0),
c_pos,
]
check_path(grid.find_path(a_pos, c_pos), path)
func test_wonky_line():
# Path between B and C is a bit wonky
var path = [
b_pos,
[Vector2(5, 1), Vector2(5, 2)],
[Vector2(6, 0), Vector2(6, 1)],
c_pos,
]
check_path(grid.find_path(HexCell.new(b_pos), HexCell.new(c_pos)), path)
func test_obstacle():
# Path between A and B should go around the bottom
var path = [
a_pos,
Vector2(3, 0),
Vector2(4, 0),
Vector2(5, 0),
Vector2(5, 1),
b_pos,
]
check_path(grid.find_path(a_pos, b_pos), path)
func test_walls():
# Test that we can't walk through walls
var walls = [
HexCell.DIR_N,
HexCell.DIR_NE,
HexCell.DIR_SE,
HexCell.DIR_S,
# DIR_SE is the only opening
HexCell.DIR_NW,
]
grid.add_barriers(g_pos, walls)
var path = [
a_pos,
Vector2(1, 1),
Vector2(0, 1),
Vector2(0, 0),
g_pos,
]
check_path(grid.find_path(a_pos, g_pos), path)
func test_slopes():
# Test that we *can* walk through *some* walls
# A barrier which is passable, but not worth our hex
grid.add_barriers(g_pos, HexCell.DIR_NE, 3)
# A barrier which is marginally better than moving that extra hex
grid.add_barriers(g_pos, HexCell.DIR_N, grid.path_cost_default - 0.1)
var path = [
a_pos,
Vector2(1, 1),
g_pos,
]
check_path(grid.find_path(a_pos, g_pos), path)
func test_rough_terrain():
# Path between A and B depends on the toughness of D
var short_path = [
a_pos,
Vector2(3, 0),
Vector2(4, 0),
d_pos,
Vector2(5, 1),
b_pos,
]
var long_path = [
a_pos,
Vector2(1, 1),
Vector2(0, 2),
Vector2(0, 3),
Vector2(0, 4),
Vector2(1, 4),
Vector2(2, 4),
Vector2(3, 3),
b_pos,
]
# The long path is 9 long, the short 6,
# so it should take the long path once d_pos costs more than 3 over default
var tests = {
grid.path_cost_default: short_path,
grid.path_cost_default + 1: short_path,
grid.path_cost_default + 2.9: short_path,
grid.path_cost_default + 3.1: long_path,
grid.path_cost_default + 50: long_path,
0: long_path,
}
for cost in tests:
grid.add_obstacles(d_pos, cost)
check_path(grid.find_path(a_pos, b_pos), tests[cost])
func test_exception():
# D is impassable, so path between A and B should go around the top as well
var path = [
a_pos,
Vector2(1, 1),
Vector2(0, 2),
Vector2(0, 3),
Vector2(0, 4),
Vector2(1, 4),
Vector2(2, 4),
Vector2(3, 3),
b_pos,
]
check_path(grid.find_path(a_pos, b_pos, [d_pos]), path)
func test_exception_hex():
# Same as the above, but providing an exceptional HexCell instance
var path = [
a_pos,
Vector2(1, 1),
Vector2(0, 2),
Vector2(0, 3),
Vector2(0, 4),
Vector2(1, 4),
Vector2(2, 4),
Vector2(3, 3),
b_pos,
]
check_path(grid.find_path(a_pos, b_pos, [HexCell.new(d_pos)]), path)
func test_exceptional_goal():
# If D is impassable, we should path to its neighbour
var path = [
a_pos,
Vector2(3, 0),
Vector2(4, 0),
]
check_path(grid.find_path(a_pos, d_pos, [d_pos]), path)
func test_inaccessible():
# E is inaccessible!
var path = grid.find_path(a_pos, e_pos)
assert_eq(path.size(), 0)
func test_obstacle_neighbour():
# Sometimes we can't get to something, but we can get next to it.
var path = [
a_pos,
Vector2(1, 1),
Vector2(0, 2),
Vector2(0, 3),
]
check_path(grid.find_path(a_pos, f_pos), path)
func test_difficult_goal():
# We should be able to path to a goal, no matter how difficult the final step
grid.add_obstacles(f_pos, 1337)
var path = [
a_pos,
Vector2(1, 1),
Vector2(0, 2),
Vector2(0, 3),
f_pos,
]
check_path(grid.find_path(a_pos, f_pos), path)