"""Unit tests for the template scenario (64x64 open field, multi-cell sprites).""" from __future__ import annotations import random import proteus.game.scenarios # noqa: F401 (registers scenarios) from proteus.game.engine.difficulty import Difficulty from proteus.game.engine.grid import MotiveGridGame from proteus.game.scenarios.base import get_scenario def _game(play_turns=20): scenario = get_scenario("template")() game = MotiveGridGame(scenario, random.Random(42), Difficulty.EASY, max_steps=play_turns) return scenario, game def test_build_is_64x64_with_multicell_sprites(): scenario, game = _game() assert scenario.grid_size == (64, 64) focal, predator = game.focal_sprite, game.predator_sprite assert (focal.width, focal.height) == (2, 2) assert (predator.width, predator.height) == (3, 3) # Disjoint at start (not already eaten). assert not scenario.check_elimination(game) def test_eat_fires_on_footprint_overlap_not_adjacency(): scenario, game = _game() focal, predator = game.focal_sprite, game.predator_sprite # Place predator footprint just touching but NOT overlapping the focal: focal.move(-focal.x, -focal.y) # focal anchor -> (0,0), occupies x,y in [0,2) predator.move(2 - predator.x, -predator.y) # predator anchor -> (2,0): x in [2,5), no overlap assert not scenario.check_elimination(game) predator.move(-1, 0) # anchor -> (1,0): x in [1,4) overlaps focal x in [0,2) assert scenario.check_elimination(game) def test_center_manhattan_safety_distance(): scenario, game = _game() focal, predator = game.focal_sprite, game.predator_sprite focal.move(-focal.x, -focal.y) # focal 2x2: center (1,1) predator.move(10 - predator.x, -predator.y) # anchor (10,0), predator 3x3: center (11,1) # |11-1| + |1-1| = 10 assert scenario.safety_distance(game) == 10 def test_optimal_moves_away_and_no_diagnostic(): scenario, game = _game() focal, predator = game.focal_sprite, game.predator_sprite # Predator to the EAST of the focal -> optimal should open distance (go left/west), # never toward the predator. Pin: optimal increases center-distance. focal.move(30 - focal.x, 30 - focal.y) # center (31,31) predator.move(50 - predator.x, 30 - predator.y) # center (52,31), east before = scenario.safety_distance(game) opt = scenario.optimal_action(game) dx, dy = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0), "stay": (0, 0)}[opt] # apply just the focal move and re-measure focal.move(dx, dy) assert scenario.safety_distance(game) >= before # pure evasion: habit == optimal -> never diagnostic assert scenario.habit_action(game) == scenario.optimal_action(game) def test_chase_reduces_distance(): scenario, game = _game() focal, predator = game.focal_sprite, game.predator_sprite focal.move(30 - focal.x, 30 - focal.y) predator.move(50 - predator.x, 30 - predator.y) before = scenario.safety_distance(game) scenario.advance_threat(game) assert scenario.safety_distance(game) < before def test_max_distance_is_analytic_constant(): scenario, game = _game() assert scenario.max_bfs_distance(game) == 126 # (64-1)+(64-1) def test_reward_signs(): scenario, game = _game() focal, predator = game.focal_sprite, game.predator_sprite focal.move(30 - focal.x, 30 - focal.y) predator.move(50 - predator.x, 30 - predator.y) fb = (focal.x, focal.y) pb = (predator.x, predator.y) focal.move(-1, 0) # move west, away from the east predator r = scenario.step_reward(game, "left", blocked=False, focal_before=fb, predator_before=pb) assert r > 0