amogus/tests/test_game.py
Antigravity 071906df59 feat: Complete LLM agent framework with fog-of-war, meeting flow, and prompt assembly
- Core engine: simulator, game mechanics, triggers (138 tests)
- Fog-of-war per-player state tracking
- Meeting flow: interrupt, discussion, voting, consolidation
- Prompt assembler with strategy injection tiers
- LLM client with fallbacks for models without JSON/system support
- Prompt templates: action, discussion, voting, reflection
- Full integration in main.py orchestrator
- Verified working with free OpenRouter models (Gemma)
2026-02-01 00:00:34 -05:00

484 lines
17 KiB
Python

"""
Tests for the game engine.
"""
import unittest
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.engine.game import GameEngine, GameConfig
from src.engine.types import Player, Position, Role, GamePhase, Body
from src.map.graph import GameMap, Room, Edge, Task, Vent
def create_simple_map():
"""Create a simple test map."""
game_map = GameMap()
# Cafeteria with task
game_map.add_room(Room(
id="cafeteria",
name="Cafeteria",
tasks=[Task(id="wires_cafe", name="Fix Wiring", duration=3.0)]
))
# Electrical with vent
elec_vent = Vent(id="vent_elec", connects_to=["vent_security"])
game_map.add_room(Room(
id="electrical",
name="Electrical",
vent=elec_vent,
tasks=[Task(id="wires_elec", name="Fix Wiring", duration=3.0)]
))
# Security with vent
sec_vent = Vent(id="vent_security", connects_to=["vent_elec"])
game_map.add_room(Room(
id="security",
name="Security",
vent=sec_vent
))
# Admin
game_map.add_room(Room(id="admin", name="Admin"))
# Connect rooms
game_map.add_edge(Edge(id="cafe_elec", room_a="cafeteria", room_b="electrical", distance=5.0))
game_map.add_edge(Edge(id="cafe_admin", room_a="cafeteria", room_b="admin", distance=3.0))
game_map.add_edge(Edge(id="elec_sec", room_a="electrical", room_b="security", distance=4.0))
return game_map
class TestGameConfig(unittest.TestCase):
"""Tests for GameConfig."""
def test_default_config(self):
config = GameConfig()
self.assertEqual(config.num_impostors, 2)
self.assertEqual(config.kill_cooldown, 25.0)
self.assertEqual(config.emergencies_per_player, 1)
def test_config_to_dict(self):
config = GameConfig()
data = config.to_dict()
self.assertIn("num_impostors", data)
self.assertIn("kill_cooldown", data)
class TestGameEngineSetup(unittest.TestCase):
"""Tests for game engine initialization and player management."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
def test_engine_initialization(self):
self.assertIsNotNone(self.engine.simulator)
self.assertIsNotNone(self.engine.triggers)
self.assertEqual(len(self.engine.impostor_ids), 0)
def test_add_crewmate(self):
player = self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
self.assertEqual(player.role, Role.CREWMATE)
self.assertEqual(player.position.room_id, "cafeteria")
self.assertIn("p1", self.engine.simulator.players)
self.assertNotIn("p1", self.engine.impostor_ids)
def test_add_impostor(self):
player = self.engine.add_player("p1", "Red", "red", Role.IMPOSTOR)
self.assertEqual(player.role, Role.IMPOSTOR)
self.assertIn("p1", self.engine.impostor_ids)
self.assertEqual(player.kill_cooldown, self.config.kill_cooldown)
def test_custom_player_speed(self):
player = self.engine.add_player("p1", "Red", "red", speed=3.0)
self.assertEqual(player.speed, 3.0)
def test_impostor_context(self):
self.engine.add_player("p1", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("p2", "Blue", "blue", Role.IMPOSTOR)
self.engine.add_player("p3", "Green", "green", Role.CREWMATE)
context = self.engine.get_impostor_context("p1")
self.assertIn("fellow_impostors", context)
self.assertEqual(len(context["fellow_impostors"]), 1)
self.assertEqual(context["fellow_impostors"][0]["id"], "p2")
def test_impostor_context_for_crewmate(self):
self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
context = self.engine.get_impostor_context("p1")
self.assertEqual(context, {})
class TestActionQueue(unittest.TestCase):
"""Tests for action queueing and resolution."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
self.engine.add_player("p2", "Blue", "blue", Role.IMPOSTOR)
def test_queue_action(self):
pos = self.engine.queue_action("p1", "MOVE", {"destination": "electrical"})
self.assertEqual(pos, 0)
self.assertEqual(len(self.engine._action_queue), 1)
def test_action_priority(self):
# Queue in wrong priority order
self.engine.queue_action("p1", "MOVE", {"destination": "electrical"})
self.engine.queue_action("p2", "KILL", {"target_id": "p1"})
self.engine.queue_action("p2", "SABOTAGE", {"system": "lights"})
# Resolve - should process in priority order
results = self.engine.resolve_actions()
# SABOTAGE should be first, then KILL, then MOVE
self.assertEqual(results[0]["action"], "SABOTAGE")
self.assertEqual(results[1]["action"], "KILL")
self.assertEqual(results[2]["action"], "MOVE")
def test_queue_clears_after_resolve(self):
self.engine.queue_action("p1", "MOVE", {"destination": "admin"})
self.engine.resolve_actions()
self.assertEqual(len(self.engine._action_queue), 0)
class TestMovement(unittest.TestCase):
"""Tests for player movement."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
def test_move_to_adjacent_room(self):
self.engine.queue_action("p1", "MOVE", {"destination": "electrical"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_move_same_room(self):
self.engine.queue_action("p1", "MOVE", {"destination": "cafeteria"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_move_no_path(self):
# Add isolated room
self.game_map.add_room(Room(id="isolated", name="Isolated"))
self.engine.queue_action("p1", "MOVE", {"destination": "isolated"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_dead_player_cannot_move(self):
player = self.engine.simulator.get_player("p1")
player.is_alive = False
self.engine.queue_action("p1", "MOVE", {"destination": "electrical"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestKill(unittest.TestCase):
"""Tests for kill mechanics."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("crew", "Blue", "blue", Role.CREWMATE)
# Reset kill cooldown for tests
imp = self.engine.simulator.get_player("imp")
imp.kill_cooldown = 0
def test_successful_kill(self):
self.engine.queue_action("imp", "KILL", {"target_id": "crew"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_crewmate_cannot_kill(self):
self.engine.queue_action("crew", "KILL", {"target_id": "imp"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_cannot_kill_different_room(self):
crew = self.engine.simulator.get_player("crew")
crew.position = Position(room_id="electrical")
self.engine.queue_action("imp", "KILL", {"target_id": "crew"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_kill_cooldown_blocks(self):
imp = self.engine.simulator.get_player("imp")
imp.kill_cooldown = 10.0
self.engine.queue_action("imp", "KILL", {"target_id": "crew"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_kill_creates_body(self):
self.engine.queue_action("imp", "KILL", {"target_id": "crew"})
self.engine.resolve_actions()
# Process kill event
self.engine.simulator.run_until_empty()
self.assertEqual(len(self.engine.simulator.bodies), 1)
self.assertFalse(self.engine.simulator.get_player("crew").is_alive)
class TestVenting(unittest.TestCase):
"""Tests for vent mechanics."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("crew", "Blue", "blue", Role.CREWMATE)
# Place impostor in electrical (has vent)
imp = self.engine.simulator.get_player("imp")
imp.position = Position(room_id="electrical")
def test_impostor_can_vent(self):
self.engine.queue_action("imp", "VENT", {"destination": "security"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_crewmate_cannot_vent(self):
crew = self.engine.simulator.get_player("crew")
crew.position = Position(room_id="electrical")
self.engine.queue_action("crew", "VENT", {"destination": "security"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_cannot_vent_unconnected(self):
# Cafeteria has no vent
imp = self.engine.simulator.get_player("imp")
imp.position = Position(room_id="cafeteria")
self.engine.queue_action("imp", "VENT", {"destination": "security"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestTasks(unittest.TestCase):
"""Tests for task mechanics."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
player = self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
player.tasks_assigned = ["wires_cafe", "wires_elec"]
def test_start_task_in_current_room(self):
self.engine.queue_action("p1", "TASK", {"task_id": "wires_cafe"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_cannot_do_task_in_wrong_room(self):
# wires_elec is in electrical, player is in cafeteria
self.engine.queue_action("p1", "TASK", {"task_id": "wires_elec"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_cannot_do_unassigned_task(self):
self.engine.queue_action("p1", "TASK", {"task_id": "nonexistent"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestReporting(unittest.TestCase):
"""Tests for body reporting."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
# Create a body in cafeteria
body = Body(
id="body1",
player_id="dead",
player_name="Blue",
position=Position(room_id="cafeteria"),
time_of_death=0.0
)
self.engine.simulator.bodies.append(body)
def test_report_body_in_room(self):
self.engine.queue_action("p1", "REPORT", {"body_id": "body1"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_cannot_report_body_in_different_room(self):
player = self.engine.simulator.get_player("p1")
player.position = Position(room_id="electrical")
self.engine.queue_action("p1", "REPORT", {"body_id": "body1"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_cannot_report_already_reported(self):
self.engine.simulator.bodies[0].reported = True
self.engine.queue_action("p1", "REPORT", {"body_id": "body1"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestEmergency(unittest.TestCase):
"""Tests for emergency button."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("p1", "Red", "red", Role.CREWMATE)
def test_call_emergency_in_cafeteria(self):
self.engine.queue_action("p1", "EMERGENCY", {})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_cannot_call_emergency_outside_cafeteria(self):
player = self.engine.simulator.get_player("p1")
player.position = Position(room_id="electrical")
self.engine.queue_action("p1", "EMERGENCY", {})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_emergency_limit_per_player(self):
# Use up emergency
self.engine.queue_action("p1", "EMERGENCY", {})
self.engine.resolve_actions()
# Try again
self.engine.queue_action("p1", "EMERGENCY", {})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestSabotage(unittest.TestCase):
"""Tests for sabotage mechanics."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
def test_impostor_can_sabotage(self):
self.engine.queue_action("imp", "SABOTAGE", {"system": "lights"})
results = self.engine.resolve_actions()
self.assertTrue(results[0]["success"])
def test_cannot_double_sabotage(self):
self.engine.active_sabotage = "lights"
self.engine.queue_action("imp", "SABOTAGE", {"system": "o2"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
def test_invalid_sabotage_system(self):
self.engine.queue_action("imp", "SABOTAGE", {"system": "invalid"})
results = self.engine.resolve_actions()
self.assertFalse(results[0]["success"])
class TestWinConditions(unittest.TestCase):
"""Tests for win condition checking."""
def setUp(self):
self.config = GameConfig()
self.game_map = create_simple_map()
self.engine = GameEngine(self.config, self.game_map)
def test_no_win_yet(self):
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("c1", "Blue", "blue", Role.CREWMATE)
self.engine.add_player("c2", "Green", "green", Role.CREWMATE)
self.assertIsNone(self.engine.check_win_condition())
def test_impostor_wins_by_parity(self):
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("crew", "Blue", "blue", Role.CREWMATE)
self.assertEqual(self.engine.check_win_condition(), "impostor")
def test_crewmate_wins_all_impostors_dead(self):
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("crew", "Blue", "blue", Role.CREWMATE)
imp = self.engine.simulator.get_player("imp")
imp.is_alive = False
self.assertEqual(self.engine.check_win_condition(), "crewmate")
def test_crewmate_wins_all_tasks(self):
self.engine.add_player("imp", "Red", "red", Role.IMPOSTOR)
self.engine.add_player("c1", "Blue", "blue", Role.CREWMATE)
self.engine.add_player("c2", "Green", "green", Role.CREWMATE)
c1 = self.engine.simulator.get_player("c1")
c2 = self.engine.simulator.get_player("c2")
c1.tasks_assigned = ["t1"]
c1.tasks_completed = ["t1"]
c2.tasks_assigned = ["t2"]
c2.tasks_completed = ["t2"]
self.assertEqual(self.engine.check_win_condition(), "crewmate")
if __name__ == "__main__":
unittest.main()