177 lines
6.7 KiB
GDScript
177 lines
6.7 KiB
GDScript
extends GutTest
|
|
|
|
# =============================================================================
|
|
# Test: Bot AI — Sticky Avoidance & Pathfinding [Candy Survival]
|
|
# Verifies the bot's strategic planner correctly:
|
|
# • Detects Candy Survival mode and exposes helpers.
|
|
# • Rejects sticky / telegraphed cells in _is_valid_move_target.
|
|
# • Uses CandySurvivalManager.is_sticky_cell() authority.
|
|
# =============================================================================
|
|
|
|
const BotStrategicPlanner = preload("res://scripts/bot_strategic_planner.gd")
|
|
const GridMapMock = preload("res://tests/helpers/gridmap_mock.gd")
|
|
|
|
# ---- Mock actors and managers ------------------------------------------------
|
|
|
|
class StubActor extends Node3D:
|
|
var peer_id: int = 7
|
|
var current_position: Vector2i = Vector2i(5, 5)
|
|
var enhanced_gridmap: Node = null
|
|
var movement_range: int = 4
|
|
var movement_manager: Node = null
|
|
var goals: Array = []
|
|
var use_diagonal_movement: bool = false
|
|
func is_position_occupied(_p: Vector2i) -> bool:
|
|
return false
|
|
|
|
class StubCandySurvivalManager extends Node:
|
|
var sticky_map: Dictionary = {}
|
|
func is_sticky_cell(pos: Vector2i) -> bool:
|
|
return sticky_map.get(pos, false)
|
|
|
|
# ---- Test fixture -----------------------------------------------------------
|
|
|
|
var main_node: Node
|
|
var gridmap: Node
|
|
var candy_survival_manager: StubCandySurvivalManager
|
|
var actor: StubActor
|
|
var planner: RefCounted
|
|
|
|
func before_each():
|
|
main_node = Node.new()
|
|
main_node.name = "BotTestMain_%d" % Time.get_ticks_usec()
|
|
get_tree().get_root().add_child(main_node)
|
|
|
|
gridmap = GridMapMock.new()
|
|
gridmap.name = "EnhancedGridMap"
|
|
var nwi: Array[int] = [4]
|
|
gridmap.non_walkable_items = nwi
|
|
for x in range(20):
|
|
for z in range(20):
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), 1)
|
|
main_node.add_child(gridmap)
|
|
|
|
candy_survival_manager = StubCandySurvivalManager.new()
|
|
candy_survival_manager.name = "CandySurvivalManager"
|
|
main_node.add_child(candy_survival_manager)
|
|
|
|
actor = StubActor.new()
|
|
actor.enhanced_gridmap = gridmap
|
|
actor.name = "Bot7"
|
|
main_node.add_child(actor)
|
|
|
|
planner = BotStrategicPlanner.new(actor, gridmap)
|
|
planner.candy_survival_manager_override = candy_survival_manager
|
|
|
|
LobbyManager.game_mode = "Candy Survival"
|
|
|
|
func after_each():
|
|
if is_instance_valid(main_node):
|
|
main_node.queue_free()
|
|
actor = null
|
|
planner = null
|
|
candy_survival_manager = null
|
|
gridmap = null
|
|
LobbyManager.game_mode = "Freemode"
|
|
|
|
# =============================================================================
|
|
# Mode detection
|
|
# =============================================================================
|
|
|
|
func test_is_candy_survival_mode_true_when_set():
|
|
assert_true(planner.is_candy_survival_mode(), "Detects Candy Survival via LobbyManager")
|
|
|
|
func test_is_candy_survival_mode_false_in_other_modes():
|
|
LobbyManager.game_mode = "Stop n Go"
|
|
assert_false(planner.is_candy_survival_mode(), "Stop n Go is not Candy Survival")
|
|
LobbyManager.game_mode = "Freemode"
|
|
assert_false(planner.is_candy_survival_mode(), "Freemode is not Candy Survival")
|
|
|
|
func test_get_candy_survival_manager_resolves_from_main():
|
|
var gm = planner._get_candy_survival_manager()
|
|
assert_not_null(gm, "Resolves CandySurvivalManager under /root/Main")
|
|
assert_eq(gm, candy_survival_manager, "Same instance as the one we added")
|
|
|
|
# =============================================================================
|
|
# Overlay detection
|
|
# =============================================================================
|
|
|
|
func test_overlay_unsafe_false_on_empty_layer2():
|
|
assert_false(planner._is_overlay_unsafe(Vector2i(3, 3)),
|
|
"Empty layer 2 -> safe")
|
|
|
|
func test_overlay_unsafe_true_for_sticky_tile():
|
|
gridmap.set_cell_item(Vector3i(3, 2, 3), 17)
|
|
assert_true(planner._is_overlay_unsafe(Vector2i(3, 3)),
|
|
"Sticky overlay is unsafe")
|
|
|
|
func test_overlay_unsafe_true_for_telegraph_tile():
|
|
gridmap.set_cell_item(Vector3i(4, 2, 4), 18)
|
|
assert_true(planner._is_overlay_unsafe(Vector2i(4, 4)),
|
|
"Telegraph overlay is unsafe")
|
|
|
|
func test_overlay_unsafe_ignores_layer0_and_layer1():
|
|
gridmap.set_cell_item(Vector3i(3, 0, 3), 17)
|
|
gridmap.set_cell_item(Vector3i(3, 1, 3), 17)
|
|
assert_false(planner._is_overlay_unsafe(Vector2i(3, 3)),
|
|
"Only layer 2 overlay matters for Candy Survival safety")
|
|
|
|
# =============================================================================
|
|
# _is_valid_move_target integration
|
|
# =============================================================================
|
|
|
|
func test_valid_move_target_rejects_sticky_in_candy_survival():
|
|
gridmap.set_cell_item(Vector3i(3, 2, 3), 17)
|
|
assert_false(planner._is_valid_move_target(Vector2i(3, 3)),
|
|
"Sticky cell rejected in Candy Survival mode")
|
|
|
|
func test_valid_move_target_rejects_telegraphed_in_candy_survival():
|
|
gridmap.set_cell_item(Vector3i(5, 2, 5), 18)
|
|
assert_false(planner._is_valid_move_target(Vector2i(5, 5)),
|
|
"Telegraphed cell rejected in Candy Survival mode")
|
|
|
|
func test_valid_move_target_accepts_clean_cells():
|
|
assert_true(planner._is_valid_move_target(Vector2i(8, 8)),
|
|
"Clean cell accepted")
|
|
|
|
func test_valid_move_target_ignores_players_when_requested():
|
|
gridmap.set_cell_item(Vector3i(3, 2, 3), 17)
|
|
assert_false(planner._is_valid_move_target(Vector2i(3, 3), true),
|
|
"Safety check still active with ignore_players=true")
|
|
|
|
func test_valid_move_target_outside_candy_survival_allows_sticky():
|
|
LobbyManager.game_mode = "Freemode"
|
|
gridmap.set_cell_item(Vector3i(3, 2, 3), 17)
|
|
assert_true(planner._is_valid_move_target(Vector2i(3, 3)),
|
|
"Sticky overlay ignored in non-Candy Survival modes")
|
|
|
|
# =============================================================================
|
|
# Sticky-cell awareness via CandySurvivalManager authority
|
|
# =============================================================================
|
|
|
|
func test_valid_move_target_uses_candy_survival_manager_sticky_map():
|
|
candy_survival_manager.sticky_map[Vector2i(7, 7)] = true
|
|
assert_false(planner._is_valid_move_target(Vector2i(7, 7)),
|
|
"Manager's sticky map blocks moves")
|
|
|
|
# =============================================================================
|
|
# _count_unsafe_neighbors
|
|
# =============================================================================
|
|
|
|
func test_count_unsafe_neighbors_zero_in_open_field():
|
|
assert_eq(planner._count_unsafe_neighbors(Vector2i(5, 5)), 0,
|
|
"Open field has zero unsafe neighbors")
|
|
|
|
func test_count_unsafe_neighbors_four_when_surrounded():
|
|
for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]:
|
|
var n = Vector2i(5, 5) + d
|
|
gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17)
|
|
assert_eq(planner._count_unsafe_neighbors(Vector2i(5, 5)), 4,
|
|
"All four neighbors sticky")
|
|
|
|
func test_count_unsafe_neighbors_partial_box():
|
|
gridmap.set_cell_item(Vector3i(6, 2, 5), 17)
|
|
gridmap.set_cell_item(Vector3i(5, 2, 6), 17)
|
|
assert_eq(planner._count_unsafe_neighbors(Vector2i(5, 5)), 2,
|
|
"Two unsafe neighbors")
|