123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from engine.ai import AIController
|
|
from engine.character import Character
|
|
from engine.defs import DefRegistry
|
|
from engine.entity import EntityPosition
|
|
from engine.map import Map
|
|
from engine.tile import Tile, TileLayerInstance
|
|
from engine.world import World
|
|
from resources.logic.ai import run_ai_tick
|
|
|
|
DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs"
|
|
|
|
|
|
@pytest.fixture()
|
|
def registry():
|
|
return DefRegistry.load(DEFS_DIR)
|
|
|
|
|
|
def make_world(registry):
|
|
field = Map("field", 5, 5, 1)
|
|
for x in range(5):
|
|
for y in range(5):
|
|
field.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass")))
|
|
world = World(registry=registry)
|
|
world.add_map(field)
|
|
return world
|
|
|
|
|
|
def make_human(registry, **kwargs) -> Character:
|
|
return Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"), **kwargs)
|
|
|
|
|
|
class FixedRng:
|
|
"""A random.Random look-alike that always picks the same direction/interval."""
|
|
|
|
def __init__(self, choice_index: int = 0, uniform_value: float = 0.5):
|
|
self.choice_index = choice_index
|
|
self.uniform_value = uniform_value
|
|
|
|
def randint(self, a, b):
|
|
return a
|
|
|
|
def choice(self, seq):
|
|
return seq[self.choice_index]
|
|
|
|
def uniform(self, a, b):
|
|
return a + (b - a) * self.uniform_value
|
|
|
|
|
|
def test_aicontroller_defaults_to_empty_scratch_state():
|
|
controller = AIController(behavior_id="wander")
|
|
assert controller.state == {}
|
|
|
|
|
|
def test_two_aicontrollers_have_independent_state_dicts():
|
|
a = AIController(behavior_id="wander")
|
|
b = AIController(behavior_id="wander")
|
|
a.state["next_move_at"] = 5.0
|
|
assert b.state == {}
|
|
|
|
|
|
def test_run_ai_tick_is_a_noop_without_an_ai_slot(registry):
|
|
world = make_world(registry)
|
|
human = make_human(registry, position=EntityPosition("field", 2, 2, 0))
|
|
world.add_entity(human)
|
|
|
|
run_ai_tick(human, world, registry, now=0.0, rng=FixedRng())
|
|
|
|
assert human.ai is None
|
|
assert (human.position.x, human.position.y) == (2, 2)
|
|
|
|
|
|
def test_run_ai_tick_is_a_noop_for_an_unregistered_behavior_id(registry):
|
|
world = make_world(registry)
|
|
human = make_human(
|
|
registry, position=EntityPosition("field", 2, 2, 0), ai=AIController(behavior_id="does_not_exist")
|
|
)
|
|
world.add_entity(human)
|
|
|
|
run_ai_tick(human, world, registry, now=0.0, rng=FixedRng())
|
|
|
|
assert (human.position.x, human.position.y) == (2, 2)
|
|
|
|
|
|
def test_wander_moves_the_character_on_its_first_tick(registry):
|
|
world = make_world(registry)
|
|
human = make_human(registry, position=EntityPosition("field", 2, 2, 0), ai=AIController(behavior_id="wander"))
|
|
world.add_entity(human)
|
|
|
|
run_ai_tick(human, world, registry, now=0.0, rng=FixedRng(choice_index=1)) # (1, 0)
|
|
|
|
assert (human.position.x, human.position.y) == (3, 2)
|
|
assert human.ai.state["next_move_at"] > 0.0
|
|
|
|
|
|
def test_wander_does_nothing_before_its_cooldown_elapses(registry):
|
|
world = make_world(registry)
|
|
human = make_human(registry, position=EntityPosition("field", 2, 2, 0), ai=AIController(behavior_id="wander"))
|
|
world.add_entity(human)
|
|
|
|
run_ai_tick(human, world, registry, now=0.0, rng=FixedRng(choice_index=1))
|
|
moved_to = (human.position.x, human.position.y)
|
|
|
|
run_ai_tick(human, world, registry, now=0.1, rng=FixedRng(choice_index=0)) # still on cooldown
|
|
|
|
assert (human.position.x, human.position.y) == moved_to
|
|
|
|
|
|
def test_wander_moves_again_once_its_cooldown_expires(registry):
|
|
world = make_world(registry)
|
|
human = make_human(registry, position=EntityPosition("field", 2, 2, 0), ai=AIController(behavior_id="wander"))
|
|
world.add_entity(human)
|
|
|
|
run_ai_tick(human, world, registry, now=0.0, rng=FixedRng(choice_index=1)) # -> (3, 2)
|
|
next_move_at = human.ai.state["next_move_at"]
|
|
|
|
run_ai_tick(human, world, registry, now=next_move_at, rng=FixedRng(choice_index=3)) # (0, 1)
|
|
|
|
assert (human.position.x, human.position.y) == (3, 3)
|