GodotComponentTest/addons/gdhexgrid/test/unit/test_pathfinding.gd

332 lines
9.4 KiB
GDScript

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)