Skip to content

Latest commit

 

History

History
513 lines (383 loc) · 14.3 KB

File metadata and controls

513 lines (383 loc) · 14.3 KB

Internal Game API Documentation

Overview

Terminal Survival now provides a UI-agnostic Internal Game API that allows programmatic interaction with the game. This API enables:

  • Automated testing without UI dependencies
  • AI agents that can play the game autonomously
  • Alternative UIs built on the same core logic
  • Clean separation between game logic and presentation

Architecture

Core Principles

  1. Single Command Handler: All commands flow through process_command()
  2. Structured Results: Commands return CommandResult objects
  3. Observable State: Full game state available via export_state()
  4. No UI Dependencies: Core logic is completely UI-agnostic

Components

┌─────────────────────────────────────────────┐
│           UI Layer (Textual/etc)            │
│  ┌─────────────────────────────────────┐   │
│  │   User Input / Display Rendering    │   │
│  └──────────────┬──────────────────────┘   │
└─────────────────┼──────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│         Internal Game API                   │
│  ┌─────────────────────────────────────┐   │
│  │   process_command(cmd) → Result     │   │
│  │   export_state() → dict             │   │
│  └─────────────────────────────────────┘   │
└─────────────────┼──────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────┐
│         Game Logic (game.py)                │
│  ┌─────────────────────────────────────┐   │
│  │  GameSession.step(text) → bool      │   │
│  │  Combat, Inventory, World, etc.     │   │
│  └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

API Reference

CommandResult

Structured result from command execution.

from terminal_survival.types import CommandResult

@dataclass
class CommandResult:
    messages: List[str]      # Game messages produced
    should_exit: bool        # True if game should terminate
    game_over: bool          # True if player died
    victory: bool            # True if player won

GameSession.process_command()

The centralized command handler - all commands must flow through this.

def process_command(self, command: str) -> CommandResult:
    """Execute a command and return structured result.
    
    Args:
        command: Command string (e.g., "go north", "take bandage")
        
    Returns:
        CommandResult with messages and state flags
    """

Example:

from terminal_survival.game import GameSession

session = GameSession.load()
result = session.process_command("go north")

for msg in result.messages:
    print(msg)

if result.game_over:
    print("Game Over!")
elif result.victory:
    print("Victory!")

GameSession.export_state()

Export complete game state as JSON-serializable dict.

def export_state(self) -> dict:
    """Get complete game state snapshot.
    
    Returns:
        Dict with player, location, game_state, messages
    """

Example:

state = session.export_state()

print(f"Health: {state['player']['health']}")
print(f"Location: ({state['location']['x']}, {state['location']['y']})")
print(f"Enemies: {len(state['location']['enemies'])}")
print(f"Game Over: {state['game_state']['is_game_over']}")

State Structure:

{
    "player": {
        "name": str,
        "x": int, "y": int,
        "health": int, "hunger": int, "thirst": int,
        "armor": int, "turn": int, "kills": int,
        "farthest_distance": int,
        "inventory": dict[str, int],
        "equipped": str | None,
        "equipped_armor": str | None,
        "visibility": int,
        "spotted": bool,
        "victory": bool,
        "known_npcs": dict[str, str],  # "x,y" → NPC role
    },
    "location": {
        "x": int, "y": int,
        "kind": str,
        "description": str,
        "items": dict[str, int],
        "enemies": list[dict],  # Only living enemies
        "npcs": list[dict],     # NPCs at location
        # Each NPC: {"name": str, "role": str, "inventory": dict[str, int]}
    },
    "game_state": {
        "is_game_over": bool,
        "is_victory": bool,
        "combat_started": bool,
        "time_of_day": str,
    },
    "messages": list[str],  # Last 20 messages
}

Hybrid Agent Loop

A Hybrid Agent is an autonomous player that uses the Internal Game API to play the game programmatically.

Basic Agent

from terminal_survival.game import GameSession
from terminal_survival.agent import HybridAgent

# Create session
session = GameSession.load()

# Create agent
agent = HybridAgent(session)

# Run agent
stats = agent.run(max_steps=100)

print(f"Steps: {stats['steps']}")
print(f"Kills: {stats['kills']}")
print(f"Distance: {stats['distance']}")

Custom Agent

from terminal_survival.game import GameSession

session = GameSession.load()

while True:
    # 1. Observe
    state = session.export_state()
    
    # 2. Check terminal conditions
    if state['game_state']['is_game_over']:
        break
    
    # 3. Decide action
    if state['player']['health'] < 30:
        command = "use bandage"
    elif state['location']['enemies']:
        command = "attack"
    else:
        command = "go north"
    
    # 4. Execute
    result = session.process_command(command)
    
    # 5. Evaluate
    for msg in result.messages:
        if "killed" in msg.lower():
            print(f"Combat: {msg}")
    
    if result.should_exit:
        break

Agent Strategies

The included HybridAgent uses simple heuristics:

  1. Survival: Use healing/food/water if needed
  2. Combat: Attack if enemy present and spotted
  3. Looting: Take useful items (food, water, ammo, medicine)
  4. Exploration: Move in a pattern to explore the world

You can create sophisticated agents by:

  • Implementing pathfinding algorithms
  • Building combat strategy engines
  • Creating resource management optimizers
  • Using machine learning for decision-making

Usage Examples

Automated Testing

def test_combat_mechanics():
    """Test combat without UI."""
    session = GameSession.placeholder()
    session.save.started_at = 1
    
    # Setup scenario
    session.process_command("admin spawn enemy Raider")
    session.process_command("admin spawn item [rifle] ak-47")
    session.process_command("admin spawn item rifle ammo 100")
    session.process_command("equip [rifle] ak-47")
    
    # Execute combat
    result = session.process_command("attack")
    
    # Verify results
    assert any("attack" in msg.lower() for msg in result.messages)
    state = session.export_state()
    assert state['player']['health'] > 0

NPC Interaction Testing

def test_trader_buy():
    """Test buying from a trader NPC."""
    session = GameSession.placeholder()
    session.save.started_at = 1
    
    # Spawn trader and give player money
    session.process_command("admin spawn npc trader")
    session.process_command("admin spawn item dollars 500")
    
    # Check trader exists
    state = session.export_state()
    assert len(state['location']['npcs']) == 1
    assert state['location']['npcs'][0]['role'] == 'trader'
    
    # Interact with trader (shows greeting + inventory)
    result = session.process_command("interact")
    assert len(result.messages) > 0
    
    # Buy an item (item name from trader inventory)
    trader_inv = state['location']['npcs'][0]['inventory']
    item_name = list(trader_inv.keys())[0]
    result = session.process_command(f"interact {item_name}")
    # Confirm purchase
    session.process_command("y")

AI Agent with Strategy

class SmartAgent:
    def __init__(self, session):
        self.session = session
        self.explored_tiles = set()
    
    def decide_action(self, state):
        player = state['player']
        location = state['location']
        
        # Priority: Don't die
        if player['health'] < 20 and 'medkit' in player['inventory']:
            return "use medkit"
        
        # Priority: Combat
        if location['enemies'] and state['game_state']['combat_started']:
            if player['equipped']:
                return "attack"
            else:
                return "equip [melee] crowbar"
        
        # Priority: Loot
        if location['items']:
            valuable = ['medkit', 'water', 'canned food']
            for item in valuable:
                if item in location['items']:
                    return f"take {item}"
        
        # Priority: Explore new tiles
        current = (player['x'], player['y'])
        for direction in ['north', 'east', 'south', 'west']:
            # Calculate next tile
            next_tile = self.calculate_next(current, direction)
            if next_tile not in self.explored_tiles:
                return f"go {direction}"
        
        return "go north"  # Default
    
    def run(self, max_steps=1000):
        for step in range(max_steps):
            state = self.session.export_state()
            
            if state['game_state']['is_game_over']:
                break
            
            action = self.decide_action(state)
            result = self.session.process_command(action)
            
            current = (state['player']['x'], state['player']['y'])
            self.explored_tiles.add(current)

Performance Benchmarking

def benchmark_agent_performance(num_runs=10):
    """Benchmark agent performance."""
    results = []
    
    for i in range(num_runs):
        session = GameSession.placeholder()
        session.save.started_at = 1
        agent = HybridAgent(session)
        
        stats = agent.run(max_steps=100)
        results.append(stats)
    
    avg_kills = sum(r['kills'] for r in results) / num_runs
    avg_distance = sum(r['distance'] for r in results) / num_runs
    
    print(f"Average Kills: {avg_kills}")
    print(f"Average Distance: {avg_distance}")

UI Integration

Textual UI (Current)

The Textual UI now uses process_command():

# In terminal_survival/tui/app.py
def execute_command(self, command: str):
    result = self.session.process_command(command)
    
    # Update UI with messages
    for msg in result.messages:
        self.log_message(msg)
    
    # Handle game over
    if result.game_over:
        self.show_game_over_screen()

Custom UI Example

import tkinter as tk
from terminal_survival.game import GameSession

class CustomUI:
    def __init__(self):
        self.session = GameSession.load()
        self.root = tk.Tk()
        
        # Create widgets
        self.text = tk.Text(self.root)
        self.entry = tk.Entry(self.root)
        self.entry.bind('<Return>', self.on_command)
        
        self.update_display()
    
    def on_command(self, event):
        command = self.entry.get()
        self.entry.delete(0, tk.END)
        
        result = self.session.process_command(command)
        
        for msg in result.messages:
            self.text.insert(tk.END, msg + "\n")
        
        if result.should_exit:
            self.root.quit()
    
    def update_display(self):
        state = self.session.export_state()
        self.root.title(f"Health: {state['player']['health']}")

Benefits

For Testing

  • No UI Dependencies: Tests run in milliseconds
  • Deterministic: Control RNG seed for reproducible tests
  • Observable: Full state access for assertions
  • Scenario Setup: Use admin commands to create test cases

For AI/ML

  • State Representation: Complete observable state
  • Action Space: All valid commands
  • Reward Signal: Health, kills, distance, victory
  • Episode Management: Game over / victory detection

For Development

  • Rapid Prototyping: Test game logic without UI
  • Debugging: Inspect state at any point
  • Regression Testing: Verify behavior programmatically
  • Performance: Benchmark mechanics at scale

Migration Guide

Before (UI-Coupled)

# Direct step() calls from UI
should_exit = session.step(user_input)
if should_exit:
    self.quit()

After (API-Based)

# Use process_command() for structured results
result = session.process_command(user_input)

for msg in result.messages:
    self.display_message(msg)

if result.game_over:
    self.show_game_over()
elif result.should_exit:
    self.quit()

Best Practices

  1. Always use process_command() - Never call step() directly from UI
  2. Use export_state() for rendering - Don't access internals
  3. Handle CommandResult properly - Check all flags
  4. Test via API - Write tests using process_command() and export_state()
  5. Agent safety - Always set max_steps to prevent infinite loops

Future Extensions

Potential API enhancements:

  • Action validation: can_execute(command) -> bool
  • Undo/Redo: State snapshots for turn rewind
  • Replay system: Record and replay command sequences
  • Parallel execution: Run multiple agents simultaneously
  • Event hooks: Subscribe to game events
  • State diffing: Track changes between turns

Support

For questions or issues:

  • Check the test suite: test_all_features.py
  • Review the agent implementation: terminal_survival/agent.py
  • Examine the API implementation: terminal_survival/game.py