# tests/engine/test_health_interact_hooks.py """Health, interact dispatch, and check_success wired into the shared turn loop. Uses a tiny Template subclass to exercise health_start at construction, and per-instance method shadowing (the test_turn_order pattern) for the per-turn hooks. Template's predator spawns far from the focal, so a single turn never triggers its predator-overlap elimination. """ import random from proteus.game.engine.difficulty import Difficulty from proteus.game.engine.grid import MotiveGridGame from proteus.game.scenarios.template import Template import proteus.game.scenarios # noqa: F401 class _HP(Template): def health_start(self, difficulty): return 6 def _game(scen): return MotiveGridGame(scen, random.Random(0), Difficulty.EASY, max_steps=10) def test_health_initialized_from_scenario(): assert _game(_HP()).health == 6 def test_damage_reduces_and_clamps_health(): game = _game(_HP()) game.damage(2) assert game.health == 4 game.damage(99) assert game.health == 0 # clamped, never negative def test_on_step_effects_called_each_turn(): scen = _HP() scen.on_step_effects = lambda g: g.damage(1) game = _game(scen) game.apply_motive_action("stay") assert game.health == 5 def test_health_zero_can_drive_elimination(): scen = _HP() scen.on_step_effects = lambda g: g.damage(6) scen.check_elimination = lambda g: g.health <= 0 game = _game(scen) game.apply_motive_action("stay") assert game.eliminated def test_check_success_triggers_win(): scen = _HP() scen.check_success = lambda g: True game = _game(scen) game.apply_motive_action("stay") assert game.survived def test_interact_dispatches_and_does_not_move(): scen = _HP() calls = [] scen.on_interact = lambda g: calls.append(g.step_count) game = _game(scen) fx, fy = game.focal_sprite.x, game.focal_sprite.y game.apply_motive_action("interact") assert calls == [1] # on_interact ran once assert (game.focal_sprite.x, game.focal_sprite.y) == (fx, fy) # no move assert game.step_count == 1 # turn advanced def test_interact_is_noop_dispatch_when_not_overridden(): # Default on_interact is a no-op: interact still advances the turn safely. game = _game(_HP()) game.apply_motive_action("interact") assert game.step_count == 1 def test_unknown_action_still_raises(): import pytest game = _game(_HP()) with pytest.raises(ValueError): game.apply_motive_action("teleport")