Skip to main content

Overview

The game engine implements the core rules of Codenames, optimized specifically for LLM agents. Instead of a visual grid layout, words are represented as plain lists—making them easier to include in prompts and process by language models.
The game engine is located in the game/ directory and includes Board, GameState, and supporting types.

Core Components

Board Structure

The Board class manages the word-to-color mapping for all 25 cards on the board.
game/board.py
from game import Board, CardColor

words = ["apple", "banana", "car", ...]  # 25 words
board = Board(words)

# Query the board
blue_words = board.get_words_by_color(CardColor.BLUE)
color = board.get_color("apple")
all_words = board.all_words

Card Colors

Each word on the board has one of four colors:
game/board.py
class CardColor(Enum):
    RED = "red"        # Red team's words
    BLUE = "blue"      # Blue team's words
    NEUTRAL = "neutral" # Neutral words (no points)
    BOMB = "bomb"       # The assassin (instant loss)

Blue Team

9 words (starting team gets +1)

Red Team

8 words

Neutral

7 words (no effect)

Bomb

1 word (instant game over)

Game State Management

The GameState class tracks the current game status, turn history, and win conditions.
game/state.py
from game import GameState, Team

game = GameState(board)

# Play a turn
game.start_turn("animal", 2)  # Hint: "animal", count: 2
result = game.make_guess("dog")
game.end_turn()

# Check game status
print(game.is_game_over)      # False
print(game.current_team)       # Team.RED
print(game.get_team_scores())  # (blue_remaining, red_remaining)

Team Enumeration

game/state.py
class Team(Enum):
    RED = "red"
    BLUE = "blue"
Blue team always starts first by default. The starting team gets one extra word (9 vs 8).

Game Flow

A typical game follows this pattern:
1

Initialize Board

Create a Board with 25 words. Colors are randomly assigned according to the standard distribution.
from game import Board
words = load_words(25)  # Your word list
board = Board(words)
2

Create Game State

Initialize GameState to track turns and revealed words.
from game import GameState
game = GameState(board)
3

Play Turns

Each turn consists of three phases:
  1. Start Turn: Spymaster gives a hint
  2. Make Guesses: Field operative guesses words
  3. End Turn: Switch teams
# Spymaster gives hint
game.start_turn(hint_word="animal", hint_count=2)

# Field operative makes guesses
result1 = game.make_guess("dog")   # Correct!
result2 = game.make_guess("cat")   # Correct!
result3 = game.make_guess("tree")  # Wrong - neutral word

# Turn ends (automatically switches teams)
game.end_turn()
4

Check Win Condition

The game automatically detects when a team has won.
if game.is_game_over:
    print(f"Game over! Winner: {game.game_outcome}")

Turn Structure

Each turn is represented by a Turn object that records the complete history:
game/state.py
@dataclass
class Turn:
    team: Team              # Which team took this turn
    turn_number: int        # Sequential turn number
    hint_word: str          # The hint given
    hint_count: int         # How many words the hint relates to
    guesses: List[TurnResult]  # All guesses made
    invalid_guess_word: Optional[str] = None
    invalid_guess_reason: Optional[str] = None

Turn Results

Each guess produces a TurnResult with detailed information:
game/state.py
@dataclass
class TurnResult:
    word: str          # The word that was guessed
    color: CardColor   # The actual color revealed
    correct: bool      # Was it the team's color?
    hit_bomb: bool     # Did they hit the assassin?

Win Conditions

The game ends when one of three conditions is met:
A team wins when all their words have been revealed.
# Blue team reveals all 9 blue words
if game.game_outcome == GameOutcome.BLUE_WIN:
    print("Blue team wins!")
If a team guesses the bomb word, they lose immediately and the other team wins.
result = game.make_guess("bomb_word")
if result.hit_bomb:
    print("Game over! Hit the bomb!")
# Game automatically sets outcome to opponent's win
Games automatically end after 50 turns to prevent infinite loops (configurable in config.py).
game/state.py
class GameOutcome(Enum):
    RED_WIN = "red_win"
    BLUE_WIN = "blue_win"
    IN_PROGRESS = "in_progress"

Game Configuration

You can customize game parameters using GameConfig:
config.py
from config import GameConfig

# Default configuration (25 words)
config = GameConfig()

# Custom board size
config = GameConfig.custom(board_size=49)  # Larger board
mini = GameConfig.custom(board_size=9)     # Quick games

# Create board with custom config
board = Board(words, config=config)

Configuration Options

ParameterDefaultDescription
BOARD_SIZE25Total number of words
BLUE_WORDS9Blue team word count
RED_WORDS8Red team word count
NEUTRAL_WORDS7Neutral word count
BOMB_COUNT1Number of bomb words
MAX_TURNS50Turn limit before draw
STARTING_TEAM”BLUE”Which team goes first

LLM-Optimized Design

The game engine includes several features specifically designed for language model agents:

Plain Word Lists

No grid coordinates—just lists of words. Easier to include in prompts and process.

Full History Tracking

Complete turn history with hints, guesses, and outcomes for analysis.

Automatic Validation

Catches common LLM errors like hallucinated words or duplicate guesses.

JSON Snapshots

Serializable game state for logging and debugging.
snapshot = game.get_snapshot()
# Returns dict with complete game state

Error Handling

The game engine validates all actions and provides clear error messages:
try:
    result = game.make_guess("invalid_word")
except ValueError as e:
    print(e)  # "Word 'invalid_word' not on board"

try:
    result = game.make_guess("already_revealed")
except ValueError as e:
    print(e)  # "Word 'already_revealed' already revealed"
All words are automatically normalized to lowercase. The engine performs case-insensitive lookups.

Example: Complete Game

from game import Board, GameState, CardColor

# Create board
words = ["dog", "cat", "house", "tree", "car", ...]  # 25 words
board = Board(words)

# Initialize game
game = GameState(board)

# Blue team's turn
game.start_turn("animal", 2)
result1 = game.make_guess("dog")   # Blue word - correct!
result2 = game.make_guess("cat")   # Blue word - correct!
game.end_turn()

# Red team's turn
game.start_turn("building", 1)
result3 = game.make_guess("house")  # Red word - correct!
game.end_turn()

# Check scores
blue_remaining, red_remaining = game.get_team_scores()
print(f"Blue: {blue_remaining}, Red: {red_remaining}")  # Blue: 7, Red: 7

# Get full game state
snapshot = game.get_snapshot()
print(snapshot['turn_history'])  # All turns with hints and guesses

Next Steps

Agents

Learn how AI agents play the game using the HintGiver and Guesser interfaces

BAML Integration

Understand how BAML enables type-safe LLM interactions

Build docs developers (and LLMs) love