Skip to main content

EventCodec

The EventCodec class converts game events and state snapshots into token sequences for training, and decodes token sequences back into structured records for validation.

Constructor

EventCodec(snapshot_interval=16, salience_threshold=Salience.TICK)
snapshot_interval
int
default:"16"
Number of ticks between automatic state snapshots. Snapshots are always taken at tick 0 and when rule-effect events occur.
salience_threshold
Salience
default:"Salience.TICK"
Minimum salience level for events to be encoded. Events below this threshold are filtered out.

Methods

encode_snapshot

def encode_snapshot(state: SnakeState) -> list[int]
Encode a complete game state snapshot into tokens.
state
SnakeState
The game state to encode, including snake position, direction, length, food position, and score.
tokens
list[int]
Token IDs representing the snapshot in format: SNAP PLAYER X_ Y_ DIR_ LEN_ FOOD X_ Y_ SCORE V_
Example:
codec = EventCodec()
state = SnakeState(
    head=(3, 5),
    direction=Action.RIGHT,
    body=[(3, 5), (2, 5), (1, 5)],
    food=(7, 8),
    score=2,
    alive=True,
    tick=0
)
tokens = codec.encode_snapshot(state)
# Returns token IDs for: [SNAP, PLAYER, X3, Y5, DIR_R, LEN3, FOOD, X7, Y8, SCORE, V2]

encode_event

def encode_event(event: Event) -> list[int]
Encode a single game event into tokens.
event
Event
The event to encode. Supports INPUT, MOVE, EAT, GROW, FOOD_SPAWN, DIE_WALL, DIE_SELF, and SCORE events.
tokens
list[int]
Token IDs representing the event. Length varies by event type:
  • INPUT: 1 token (INPUT_U/D/L/R)
  • MOVE: 3 tokens (MOVE, X_, Y_)
  • EAT: 1 token
  • GROW: 2 tokens (GROW, LEN_)
  • FOOD_SPAWN: 3 tokens (FOOD_SPAWN, X_, Y_)
  • DIE_WALL/DIE_SELF: 1 token
  • SCORE: 2 tokens (SCORE, V_)
Examples:
# MOVE event
event = Event(type="MOVE", payload={"pos": (4, 5)}, salience=Salience.TICK)
tokens = codec.encode_event(event)
# Returns: [MOVE, X4, Y5]

# GROW event
event = Event(type="GROW", payload={"length": 5}, salience=Salience.RULE_EFFECT)
tokens = codec.encode_event(event)
# Returns: [GROW, LEN5]

# INPUT event
event = Event(type="INPUT_R", payload={}, salience=Salience.INPUT)
tokens = codec.encode_event(event)
# Returns: [INPUT_R]

encode_tick_events

def encode_tick_events(events: list[Event]) -> list[int]
Encode all events from a single tick that meet the salience threshold.
events
list[Event]
List of events that occurred in a single tick.
tokens
list[int]
Token IDs with TICK prefix followed by encoded events. Returns empty list if no events meet threshold.
Example:
events = [
    Event(type="INPUT_R", payload={}, salience=Salience.INPUT),
    Event(type="MOVE", payload={"pos": (4, 5)}, salience=Salience.TICK),
]
tokens = codec.encode_tick_events(events)
# Returns: [TICK, INPUT_R, MOVE, X4, Y5]

encode_episode

def encode_episode(
    events_by_tick: dict[int, list[Event]],
    states_by_tick: dict[int, SnakeState],
) -> list[int]
Encode a complete episode into a token sequence.
events_by_tick
dict[int, list[Event]]
Dictionary mapping tick numbers to lists of events that occurred in that tick.
states_by_tick
dict[int, SnakeState]
Dictionary mapping tick numbers to game states at those ticks.
tokens
list[int]
Complete token sequence starting with BOS, containing snapshots and tick events, ending with EOS.
Snapshots are automatically inserted at:
  • Tick 0 (episode start)
  • Every snapshot_interval ticks
  • Any tick with a rule-effect salience event (e.g., death, scoring)
Example:
events_by_tick = {
    0: [Event(type="INPUT_R", payload={}, salience=Salience.INPUT)],
    1: [Event(type="MOVE", payload={"pos": (4, 5)}, salience=Salience.TICK)],
}
states_by_tick = {
    0: initial_state,
    1: state_after_move,
}
tokens = codec.encode_episode(events_by_tick, states_by_tick)
# Returns: [BOS, SNAP, ..., TICK, INPUT_R, TICK, MOVE, X4, Y5, EOS]

decode

def decode(tokens: list[int]) -> list[dict]
Decode a token sequence into structured records for validation and analysis.
tokens
list[int]
Token IDs to decode.
records
list[dict]
List of dictionaries, each representing a parsed record with a type field and relevant data fields.
Record types:
  • {"type": "BOS"} - Beginning of sequence
  • {"type": "EOS"} - End of sequence
  • {"type": "TICK"} - Tick boundary
  • {"type": "SNAP", "player_x": "X3", "player_y": "Y5", "direction": "DIR_R", "length": "LEN3", "food_x": "X7", "food_y": "Y8", "score_tok": "V2"} - State snapshot
  • {"type": "INPUT", "direction": "INPUT_R"} - Player input
  • {"type": "MOVE", "x": "X4", "y": "Y5"} - Movement
  • {"type": "EAT"} - Food eaten
  • {"type": "GROW", "length": "LEN4"} - Snake growth
  • {"type": "FOOD_SPAWN", "x": "X2", "y": "Y7"} - Food respawn
  • {"type": "DIE_WALL"} or {"type": "DIE_SELF"} - Death events
  • {"type": "SCORE", "value": "V3"} - Score update
Example:
tokens = [0, 3, 4, 14, 24, 8, 52, 6, 17, 27, 22, 71, 1]  # BOS, SNAP..., EOS
records = codec.decode(tokens)
# Returns:
# [
#     {"type": "BOS"},
#     {"type": "SNAP", "player_x": "X4", "player_y": "Y4", ...},
#     {"type": "EOS"}
# ]
The decode method is primarily for validation. It does not reconstruct full Event or SnakeState objects.

Build docs developers (and LLMs) love