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
- Single Command Handler: All commands flow through
process_command() - Structured Results: Commands return
CommandResultobjects - Observable State: Full game state available via
export_state() - No UI Dependencies: Core logic is completely UI-agnostic
┌─────────────────────────────────────────────┐
│ 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. │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
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 wonThe 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!")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
}A Hybrid Agent is an autonomous player that uses the Internal Game API to play the game programmatically.
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']}")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:
breakThe included HybridAgent uses simple heuristics:
- Survival: Use healing/food/water if needed
- Combat: Attack if enemy present and spotted
- Looting: Take useful items (food, water, ammo, medicine)
- 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
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'] > 0def 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")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)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}")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()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']}")- 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
- State Representation: Complete observable state
- Action Space: All valid commands
- Reward Signal: Health, kills, distance, victory
- Episode Management: Game over / victory detection
- Rapid Prototyping: Test game logic without UI
- Debugging: Inspect state at any point
- Regression Testing: Verify behavior programmatically
- Performance: Benchmark mechanics at scale
# Direct step() calls from UI
should_exit = session.step(user_input)
if should_exit:
self.quit()# 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()- Always use
process_command()- Never callstep()directly from UI - Use
export_state()for rendering - Don't access internals - Handle CommandResult properly - Check all flags
- Test via API - Write tests using
process_command()andexport_state() - Agent safety - Always set
max_stepsto prevent infinite loops
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
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