diff --git a/src/ecs/components/game_state.py b/src/ecs/components/game_state.py index f4f9d9c7..ceea8025 100644 --- a/src/ecs/components/game_state.py +++ b/src/ecs/components/game_state.py @@ -51,6 +51,7 @@ class GameState: trail_mode_enabled: bool = False shrinking_mode_enabled: bool = False lights_out_enabled: bool = False + teleport_mode_enabled: bool = False game_started: bool = False # Set to True after first input final_score: int = 0 # score at time of death trail_obstacles: List[Tuple[int, int]] = field(default_factory=list) diff --git a/src/ecs/entities/apple.py b/src/ecs/entities/apple.py index 4e71fe1e..0d5aded8 100644 --- a/src/ecs/entities/apple.py +++ b/src/ecs/entities/apple.py @@ -43,6 +43,7 @@ class Apple(Entity): edible: Edible renderable: Renderable moving_apple: Optional[MovingApple] = None + linked_apple_id: Optional[int] = None def get_type(self) -> EntityType: """Get the type of this entity. diff --git a/src/ecs/systems/apple_spawn.py b/src/ecs/systems/apple_spawn.py index 35cb8195..1312bd16 100644 --- a/src/ecs/systems/apple_spawn.py +++ b/src/ecs/systems/apple_spawn.py @@ -63,16 +63,52 @@ def update(self, world: World) -> None: Args: world: ECS world containing entities and components """ - # Get desired apple count from config - desired_count = self._get_desired_apple_count(world) - if desired_count <= 0: - return + # Check if Teleport mode is enabled + game_state_entities = world.registry.query_by_component("game_state") + teleport_mode = False + if game_state_entities: + entity = next(iter(game_state_entities.values())) + if hasattr(entity, "game_state"): + teleport_mode = entity.game_state.teleport_mode_enabled # Count current apples current_apples = world.registry.query_by_type(EntityType.APPLE) current_count = len(current_apples) + if teleport_mode: + if len(current_apples) >= 2: + return + + grid_size = world.board.cell_size + + pos_a = self._find_valid_position(world) + pos_b = self._find_valid_position(world) + + if not pos_a or not pos_b: + return + + x_a, y_a = pos_a + x_b, y_b = pos_b + id_a = create_apple(world, x=x_a, y=y_a, grid_size=grid_size, color=None) + id_b = create_apple( + world, x=x_b, y=y_b, grid_size=grid_size, color=(128, 0, 128) + ) + + apple_a = world.registry.get(id_a) + apple_b = world.registry.get(id_b) + + apple_a.linked_apple_id = id_b + apple_b.linked_apple_id = id_a + + return + + # Get desired apple count from config + desired_count = self._get_desired_apple_count(world) + + if desired_count <= 0: + return + # Spawn new apples if we're below desired count apples_to_spawn = desired_count - current_count diff --git a/src/ecs/systems/collision.py b/src/ecs/systems/collision.py index a3446057..f683051c 100644 --- a/src/ecs/systems/collision.py +++ b/src/ecs/systems/collision.py @@ -39,11 +39,13 @@ from ecs.systems.scoring import ScoringSystem from game.settings import GameSettings from game.services.audio_service import AudioService + from game.game_modes_registry import ( - GAME_MODE_TELEPORT, + TELEPORT_MODE_NAME, PLAYER_VS_PLAYER_MODE_NAME, MIRRORED_MODE_NAME, ) + from game.services.game_over_service import GameOverService @@ -1262,18 +1264,14 @@ def _check_apple_collision(self, world: World) -> None: # reset current time to (possibly updated) max he.hunger.current_time = he.hunger.max_time - # Special handling for TELEPORT mode - if game_state and game_state.game_mode == GAME_MODE_TELEPORT: + # # Special handling for TELEPORT mode + if game_state and game_state.game_mode == TELEPORT_MODE_NAME: # Find another active apple on the board - other_apple_id = None + other_apple_id = apple.linked_apple_id other_apple = None - for other_id, other in apples.items(): - if other_id == entity_id: - continue - if hasattr(other, "position"): - other_apple_id = other_id - other_apple = other - break + + if other_apple_id is not None: + other_apple = world.registry.get(other_apple_id) if other_apple is not None: # Teleport snake head to the other apple's position diff --git a/src/game/game_modes_registry.py b/src/game/game_modes_registry.py index 9872da66..fd63ccd4 100644 --- a/src/game/game_modes_registry.py +++ b/src/game/game_modes_registry.py @@ -27,7 +27,7 @@ CHEESE_MODE_NAME = "Cheese Mode" AUTOPLAY_MODE_NAME = "AutoPlay" SHRINKING_MODE_NAME = "Shrinking Mode" -GAME_MODE_TELEPORT = "Teleport" +TELEPORT_MODE_NAME = "Teleport" BOX_MODE_NAME = "Box Mode" TRAIL_MODE_NAME = "Trail Mode" LIGHTS_OUT_MODE_NAME = "Lights Out" @@ -66,7 +66,7 @@ "detailed_info": "Sit back and watch! The snake uses AI to play itself automatically. No controls needed - just observe as the AI tries to survive and score points. Great for relaxing or studying AI pathfinding behavior. You can still access settings to customize the visual experience!", }, { - "name": GAME_MODE_TELEPORT, + "name": TELEPORT_MODE_NAME, "description": "Collect an apple to warp to the other one, maintaining your direction.", "detailed_info": "Quantum snake mechanics! Two apples appear on the board. When you eat one, you instantly teleport to where the other apple was, maintaining your current direction. The eaten apple respawns at a new location. This creates unique strategic opportunities and escape routes!", }, @@ -105,7 +105,7 @@ "MOVING_APPLE_MODE_NAME", "CHEESE_MODE_NAME", "AUTOPLAY_MODE_NAME", - "GAME_MODE_TELEPORT", + "TELEPORT_MODE_NAME", "BOX_MODE_NAME", "TRAIL_MODE_NAME", "LIGHTS_OUT_MODE_NAME", diff --git a/src/game/services/game_initializer.py b/src/game/services/game_initializer.py index d91aeef8..1f9eac6c 100644 --- a/src/game/services/game_initializer.py +++ b/src/game/services/game_initializer.py @@ -38,6 +38,7 @@ TRAIL_MODE_NAME, LIGHTS_OUT_MODE_NAME, PLAYER_VS_PLAYER_MODE_NAME, + TELEPORT_MODE_NAME, MIRRORED_MODE_NAME, ) @@ -162,6 +163,8 @@ def create_initial_entities(self, world: World) -> None: # create initial apples (2 for PvP, normal count for others) if self._game_mode == PLAYER_VS_PLAYER_MODE_NAME: self._create_pvp_apples(world, grid_size) + elif self._game_mode == TELEPORT_MODE_NAME: + self._create_teleport_apples(world, grid_size) else: self._create_initial_apples(world, grid_size) @@ -199,6 +202,7 @@ def _create_game_state(self, world: World) -> None: trail_mode_enabled = current_mode == TRAIL_MODE_NAME shrinking_mode_enabled = current_mode == SHRINKING_MODE_NAME lights_out_enabled = current_mode == LIGHTS_OUT_MODE_NAME + teleport_mode_enabled = current_mode == TELEPORT_MODE_NAME class GameStateEntity: def __init__(self): @@ -214,6 +218,7 @@ def __init__(self): trail_mode_enabled=trail_mode_enabled, shrinking_mode_enabled=shrinking_mode_enabled, lights_out_enabled=lights_out_enabled, + teleport_mode_enabled=teleport_mode_enabled, ) def get_type(self): @@ -492,6 +497,50 @@ def _create_pvp_apples(self, world: World, grid_size: int) -> None: attempts += 1 + def _create_teleport_apples(self, world: World, grid_size: int) -> None: + """Create 2 apples for Teleport mode. + + Args: + world: ECS world instance + grid_size: Size of grid cells in pixels + """ + from ecs.prefabs.apple import create_apple + from ecs.entities.entity import EntityType + + # Get occupied positions (both snakes) + occupied_positions = set() + snakes = world.registry.query_by_type(EntityType.SNAKE) + for _, snake in snakes.items(): + if hasattr(snake, "position"): + occupied_positions.add((snake.position.x, snake.position.y)) + if hasattr(snake, "body"): + for segment in snake.body.segments: + occupied_positions.add((segment.x, segment.y)) + + attempts = 0 + max_attempts = 1000 + while attempts < max_attempts: + x_a = random.randint(0, world.board.width - 1) + y_a = random.randint(0, world.board.height - 1) + x_b = random.randint(0, world.board.width - 1) + y_b = random.randint(0, world.board.height - 1) + + if (x_a, y_a) not in occupied_positions and (x_a, y_a) != (x_b, y_b): + id_a = create_apple( + world, x=x_a, y=y_a, grid_size=grid_size, color=None + ) + id_b = create_apple( + world, x=x_b, y=y_b, grid_size=grid_size, color=(128, 0, 128) + ) + + apple_a = world.registry.get(id_a) + apple_b = world.registry.get(id_b) + + apple_a.linked_apple_id = id_b + apple_b.linked_apple_id = id_a + break + attempts += 1 + def _create_apple_config(self, world: World) -> None: """Create AppleConfig entity to track desired apple count.