332 lines
9.4 KiB
GDScript
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)
|
||
|
|