Skip to main content

SnakeGame

The SnakeGame class implements the core Snake game logic with an event-driven architecture. Each game step produces a stream of events that capture the causal structure of gameplay.

Constructor

SnakeGame(width=10, height=10, seed=None)
Create a new Snake game instance.
width
int
default:"10"
Grid width in cells
height
int
default:"10"
Grid height in cells
seed
int | None
default:"None"
Random seed for reproducible food spawning and initial direction

Methods

reset()

def reset(self) -> SnakeState
Initialize a new game. The snake starts at the center facing a random direction with length 1.
return
SnakeState
Initial game state with:
  • head: Center position (width//2, height//2)
  • body: List containing only the head position
  • direction: Random Action (UP/DOWN/LEFT/RIGHT)
  • food: Random unoccupied position
  • score: 0
  • alive: True
  • tick: 0

step()

def step(self, action: Action) -> tuple[SnakeState, list[Event], bool]
Execute one game tick with the given action.
action
Action
required
Direction to move (Action.UP, Action.DOWN, Action.LEFT, or Action.RIGHT)
return
tuple[SnakeState, list[Event], bool]
Returns a 3-tuple:
  1. SnakeState: New game state after the step
  2. list[Event]: Ordered list of events produced this tick
  3. bool: True if game ended (death), False otherwise
Attempting to move opposite to current direction is ignored - the snake continues in its current direction.
def legal_actions(self, state: SnakeState) -> list[Action]
Get all legal actions for a given state. Returns all directions except the opposite of the current direction.
state
SnakeState
required
Game state to check
return
list[Action]
List of 3 legal actions (all except the reverse direction)

Event Types

Each step() call produces an ordered sequence of events with the following types:

Input Events

INPUT_U
Event
Player pressed UP. Payload: {"action": "UP"}
INPUT_D
Event
Player pressed DOWN. Payload: {"action": "DOWN"}
INPUT_L
Event
Player pressed LEFT. Payload: {"action": "LEFT"}
INPUT_R
Event
Player pressed RIGHT. Payload: {"action": "RIGHT"}

Movement Events

MOVE
Event
Snake moved to a new position. Payload: {"pos": (x, y)}

Collision Events

DIE_WALL
Event
Snake hit a wall and died. Payload: {"pos": (x, y)}
DIE_SELF
Event
Snake hit its own body and died. Payload: {"pos": (x, y)}

Rule Effect Events

EAT
Event
Snake ate food. Payload: {"pos": (x, y)}
GROW
Event
Snake grew longer. Payload: {"length": int}
FOOD_SPAWN
Event
New food appeared. Payload: {"pos": (x, y)}
SCORE
Event
Score increased. Payload: {"score": int}

Event Structure

All events are instances of the Event dataclass:
@dataclass(frozen=True)
class Event:
    type: str           # Event type (e.g., "MOVE", "EAT")
    entity: str         # Entity that triggered it ("player" or "food")
    payload: dict       # Event-specific data
    tick: int           # Game tick when event occurred
    salience: Salience  # Priority level for ordering

Salience Levels

class Salience(IntEnum):
    TICK = 0
    MOVEMENT = 1        # INPUT_*, MOVE
    COLLISION = 2       # DIE_WALL, DIE_SELF
    RULE_EFFECT = 3     # EAT, GROW, FOOD_SPAWN, SCORE
    PHASE = 4

SnakeState

The game state is represented by a dataclass:
@dataclass
class SnakeState:
    head: tuple[int, int]          # Current head position
    body: list[tuple[int, int]]    # All body segments (includes head)
    direction: Action              # Current facing direction
    food: tuple[int, int]          # Food position
    score: int                     # Current score
    alive: bool                    # Whether snake is alive
    tick: int                      # Current game tick

Usage Example

from game_grammar import SnakeGame, Action

# Create a 10x10 game with seed for reproducibility
game = SnakeGame(width=10, height=10, seed=42)

# Initialize game
state = game.reset()
print(f"Starting at {state.head}, facing {state.direction.value}")
print(f"Food at {state.food}")

# Play several steps
for step_num in range(5):
    # Get legal actions
    legal = game.legal_actions(state)
    print(f"\nStep {step_num + 1}: Legal actions: {[a.value for a in legal]}")
    
    # Take an action
    action = Action.RIGHT
    state, events, done = game.step(action)
    
    # Print events
    for event in events:
        print(f"  {event.type}: {event.payload}")
    
    if done:
        print(f"Game over! Final score: {state.score}")
        break
    else:
        print(f"  Snake at {state.head}, score: {state.score}")
Example Output:
Starting at (5, 5), facing UP
Food at (3, 7)

Step 1: Legal actions: ['UP', 'LEFT', 'RIGHT']
  INPUT_R: {'action': 'RIGHT'}
  MOVE: {'pos': (6, 5)}
  Snake at (6, 5), score: 0

Step 2: Legal actions: ['UP', 'DOWN', 'RIGHT']
  INPUT_R: {'action': 'RIGHT'}
  MOVE: {'pos': (7, 5)}
  Snake at (7, 5), score: 0
...

Event Flow Example

When the snake eats food, a typical event sequence is:
[
    Event(type="INPUT_R", entity="player", payload={"action": "RIGHT"}, tick=10, salience=1),
    Event(type="EAT", entity="player", payload={"pos": (5, 3)}, tick=10, salience=3),
    Event(type="MOVE", entity="player", payload={"pos": (5, 3)}, tick=10, salience=1),
    Event(type="GROW", entity="player", payload={"length": 4}, tick=10, salience=3),
    Event(type="FOOD_SPAWN", entity="food", payload={"pos": (8, 2)}, tick=10, salience=3),
    Event(type="SCORE", entity="player", payload={"score": 3}, tick=10, salience=3),
]

Build docs developers (and LLMs) love