""" Tests for the game engine. Updated for pixel-based Position system. """ 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 with pixel coordinates.""" game_map = GameMap() game_map.spawn_points["cafeteria"] = Position(x=500, y=200) # Cafeteria with task game_map.add_room(Room( id="cafeteria", name="Cafeteria", center=Position(x=500, y=200), bounds=(Position(x=400, y=100), Position(x=600, y=300)), tasks=[Task(id="wires_cafe", name="Fix Wiring", duration=3.0, position=Position(x=450, y=150))] )) # Electrical with vent elec_vent = Vent(id="vent_elec", connects_to=["vent_security"], position=Position(x=200, y=450)) game_map.add_room(Room( id="electrical", name="Electrical", center=Position(x=200, y=500), bounds=(Position(x=100, y=400), Position(x=300, y=600)), vent=elec_vent, tasks=[Task(id="wires_elec", name="Fix Wiring", duration=3.0, position=Position(x=250, y=550))] )) # Security with vent sec_vent = Vent(id="vent_security", connects_to=["vent_elec"], position=Position(x=400, y=450)) game_map.add_room(Room( id="security", name="Security", center=Position(x=400, y=500), bounds=(Position(x=300, y=400), Position(x=500, y=600)), vent=sec_vent )) # Admin game_map.add_room(Room( id="admin", name="Admin", center=Position(x=700, y=200), bounds=(Position(x=600, y=100), Position(x=800, y=300)) )) # Connect rooms with waypoints game_map.add_edge(Edge( id="cafe_elec", room_a="cafeteria", room_b="electrical", waypoints=[Position(x=500, y=300), Position(x=300, y=400), Position(x=200, y=500)] )) game_map.add_edge(Edge( id="cafe_admin", room_a="cafeteria", room_b="admin", waypoints=[Position(x=600, y=200), Position(x=700, y=200)] )) game_map.add_edge(Edge( id="elec_sec", room_a="electrical", room_b="security", waypoints=[Position(x=300, y=500), Position(x=400, y=500)] )) 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_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", center=Position(x=1000, y=1000))) 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(x=200, y=500, 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(x=200, y=500, 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(x=200, y=500, 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(x=500, y=200, 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(x=500, y=200, 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(x=200, y=500, 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(x=200, y=500, 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()