crystal_quest

A text-based fantasy adventure powered by Claude — Crystal rules engine + anthropic-cr SDK demo

⚔️ Crystal Quest

Crystal Quest — 80s fantasy banner art

“Come closer, traveller — and mind the torch. You’ve stumbled into a dungeon that thinks for itself.

I am your Dungeon Master. I will conjure the dark, the cold stone, the things that slither just beyond the torchlight. I will give you a name, a blade, and a single, fragile thread of hit points. What you do with them… is between you, the dice, and whatever answers when you speak its name.

But heed this: every wound is real. Every coin you pocket is weighed. Every door you open stays open. The world remembers — because beneath the story, cold and impartial code is keeping score.

So. Step through the archway. The adventure is never the same twice, and the only way out is to play.”

The Dungeon Master, powered by Claude


Crystal Quest is a text-based fantasy adventure where Claude is the Dungeon Master and Crystal is the rules engine. It's also a living demonstration of the anthropic-cr SDK — the unofficial Anthropic SDK for the Crystal language.

Every adventure is generative: no two playthroughs produce the same rooms, NPCs, or story. There is no fixed campaign ending — the dungeon keeps going until you die, quit, or save for later. But the mechanics — HP, inventory, dice rolls, companions, remembered lore — live in deterministic Crystal code that Claude drives through real tool calls. The story can invent freely; the numbers can't quietly lie.

[!IMPORTANT] This is a demonstration application for the anthropic-cr SDK. It showcases the Tool Runner, prompt caching, extended thinking, and token-usage tracking in a complete, runnable app — not just a snippet.

✨ What makes it special

  • A real AI Dungeon Master. Claude narrates atmospheric, branching adventures — dark sanctums, rune-lit passages, entities with names and agendas. It controls NPCs, adjudicates uncertain actions with dice, and locks in the lore it establishes.
  • Code, not trust. HP, inventory, companions, and a journal of hard-won facts live in authoritative Crystal state. Claude changes them by calling tools — never by merely saying it did. The story can lie; the rules won't.
  • Save and resume. Mid-run save / load (or --load on the CLI) persist the full game state as JSON, so a long delve doesn't vanish when you close the terminal.
  • Battle-tested consistency. An AI player agent can run dozens or hundreds of turns against the DM while structural invariants (HP bounds, item stacking, name stability) are checked after every turn.
  • An SDK showcase, not a toy. Prompt caching cuts repeat-turn cost, extended thinking is available for richer DM reasoning, and autopilot runs report token usage and cache hit rate.

🎮 A taste of the adventure

╔═════════════════╗
║  CRYSTAL QUEST  ║
╚═════════════════╝

Your eyes snap open to darkness and the cold bite of stone against your back.
Testhero, you have no memory of how you came to be here — only a pounding
headache and the distant drip of water echoing somewhere in the black...
Beside you lies a guttering torch and a rusty dagger clutched loosely in your
hand. Strange runes glow faintly along the walls in sickly blue-green light.

> inspect the runes

The runes pulse with a cold blue light, shifting almost imperceptibly as if
reacting to your gaze... they trace a path along the wall, leading toward a
darker archway at the far end of the chamber.

❤ 20/20  •  📍 Starting Chamber  •  🎒 2 item(s)

Narration is generated live by Claude. The HP, location, and item count come from the game's authoritative state.

🚀 Quick start

Prerequisites: Crystal ≥ 1.18.2 and an Anthropic API key.

git clone https://github.com/amscotti/crystal_quest.git
cd crystal_quest
shards install
export ANTHROPIC_API_KEY=your_api_key_here   # from https://console.anthropic.com/
crystal run src/cli.cr

You're in the dungeon. Type natural-language actions and the DM takes it from there:

  • Move: "go north", "enter the cave", "head to the village"
  • Act: "search the room", "attack the goblin", "open the chest"
  • Survive: "drink the potion", "check my inventory", "how's my health?"
System command Action
stats / status Character sheet
inventory / inv Inventory (authoritative game state)
companions / party Traveling companions
journal / notes Remembered facts
save [slot|path] Save the game (default: saves/autosave.json)
load [slot|path] Load a saved game
help Available commands
debug Full internal game state
quit / exit End the adventure

Resume a previous run from the CLI:

crystal run src/cli.cr -- --load hero1          # loads saves/hero1.json
crystal run src/cli.cr -- --load ./myrun.json   # loads an explicit path

After every DM turn, a compact status line shows the truth of the mechanics:

❤ HP • 📍 location • 🎒 items

Environment variables

Variable Purpose
ANTHROPIC_API_KEY Required. Your Anthropic API key.
CRYSTAL_QUEST_DEBUG=1 Print tool calls, marker processing, and per-turn token usage.
CRYSTAL_QUEST_THINKING=1 Enable extended thinking for the DM (default 2000-token budget, or a number like =4000).

🧪 Autopilot — AI vs. AI stress testing

Hand the controller to an AI player agent and watch it explore on its own. The DM narrates; the agent acts; the engine checks that state stayed consistent after every turn. Runs end on death, quit, or the turn limit.

# 50 turns of AI-vs-AI, with a consistency report at the end
crystal run src/cli.cr -- --autopilot --turns 50 --name "Testhero"

The closing report covers turns played, tool calls per turn, final state, token usage with cache hit rate, and any invariant violations:

═══ Autopilot Stress-Test Report ═══
Turns played: 38
Total tool calls: 105
Avg tool calls/turn: 2.76
Final HP: 0/20
Invariant violations: 0
═══ Token Usage (anthropic-cr SDK) ═══
Cache read: 254904   •   Cache hit rate: 36%
✅ All state invariants held — no consistency bugs detected.

The player agent runs on Haiku so long runs stay affordable. Autopilot makes live, billed API calls — it's a development stress tool, not part of the unit-test suite.

🛠️ The anthropic-cr SDK features on display

Feature How Crystal Quest uses it
Tool Runner Claude drives 15 in-process Crystal tools (roll_dice, modify_hp, move_to_room, remember, …) that mutate authoritative game state. State changes come from real tool calls, not narration.
Prompt caching The tool schemas carry an ephemeral cache breakpoint so Anthropic reuses them turn after turn — visible as cache_read tokens in the report.
Extended thinking Opt-in (CRYSTAL_QUEST_THINKING): the DM reasons about combat math, tool ordering, and name consistency before narrating.
Token-usage tracking Usage from every Tool Runner hop is accumulated and reported (input / output / cache creation / cache read / thinking tokens).
Claude Sonnet 5 The DM runs on Sonnet for strong tool use and instruction-following; the autopilot player agent uses Haiku.

🏗️ Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        AI Dungeon Master                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────┐    ┌──────────────────┐    ┌───────────────┐  │
│  │   Game Loop     │───▶│  System Prompt   │───▶│  Anthropic    │  │
│  │   (game_loop)   │    │    Builder       │    │   Client      │  │
│  └────────┬────────┘    └──────────────────┘    └───────┬───────┘  │
│           │                                             │          │
│           │                                             ▼          │
│           │             ┌──────────────────┐    ┌───────────────┐  │
│           │             │    Game State    │◀───│  Tool Runner  │  │
│           ▼             │  (game_state)    │    │ (game_tools)  │  │
│  ┌─────────────────┐    └────────▲─────────┘    └───────────────┘  │
│  │     Display     │             │                                 │
│  │    (display)    │    ┌────────┴─────────┐                       │
│  └─────────────────┘    │ Marker / Save    │                       │
│                         │ (fallback, I/O)  │                       │
│                         └──────────────────┘                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Each turn, GameLoop builds a system prompt from the current GameState, registers GameTools with the Tool Runner, and lets Claude execute tools automatically. The final narrative is shown with a status line that reflects the resulting state. Optional text markers (MarkerParser) act as a safety net when the DM narrates a change without a tool call; SaveGame persists and restores full state as JSON.

The tools (Claude → Crystal):

Tool Purpose Tool Purpose
roll_dice Dice for checks/combat inspect_item Read a specific item
get_character_stats Read player stats get_current_room Check location
modify_hp Apply damage/healing move_to_room Change location
get_inventory List inventory add_companion Register a named NPC
add_to_inventory Add items update_companion Rename/re-describe
remove_from_inventory Remove items remove_companion Remove a companion
get_companions List companions remember Lock in a fact (lore/names)
get_journal Recall facts

Marker fallback: on the rare turn where Claude narrates a state change without calling a tool, lightweight text markers ([ROLL:1d20+5], [DAMAGE:5], [MOVE:Cave], …) apply the change as a safety net — but only when no state-mutating tool ran, so nothing is ever double-counted.

📦 Development

This project pins Crystal via mise (mise.toml1.20.3). With mise installed:

mise install          # install the pinned Crystal
mise run install      # shards install
mise run build        # release binary → bin/crystal_quest
mise run spec         # test suite
mise run check        # format + lint + type-check + tests
mise run              # play the game (pass CLI args after --)
mise run autopilot -- --turns 20
Command Purpose
shards install / mise run install Install dependencies
crystal run src/cli.cr / mise run Run the game
crystal spec / mise run spec Run the test suite (184 specs)
crystal tool format / mise run format Format code
./bin/ameba / mise run lint Run the Ameba linter
./scripts/check.sh / mise run check Format + lint + type-check + tests
shards build --release / mise run build Build bin/crystal_quest

🤝 Contributing

  1. Fork it (https://github.com/amscotti/crystal_quest/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

📄 License

This project is licensed under the MIT License — see the LICENSE file for details.


Crystal Quest is an unofficial, community demonstration of the anthropic-cr SDK and is not affiliated with or endorsed by Anthropic.

Repository

crystal_quest

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 2
  • about 4 hours ago
  • July 10, 2026
License

MIT License

Links
Synced at

Fri, 10 Jul 2026 02:32:25 GMT

Languages