Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ecs/components/game_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
1 change: 1 addition & 0 deletions src/ecs/entities/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 40 additions & 4 deletions src/ecs/systems/apple_spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 9 additions & 11 deletions src/ecs/systems/collision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/game/game_modes_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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!",
},
Expand Down Expand Up @@ -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",
Expand Down
49 changes: 49 additions & 0 deletions src/game/services/game_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
TRAIL_MODE_NAME,
LIGHTS_OUT_MODE_NAME,
PLAYER_VS_PLAYER_MODE_NAME,
TELEPORT_MODE_NAME,
MIRRORED_MODE_NAME,
)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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.

Expand Down