crystal_quest
⚔️ Crystal Quest

“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-crSDK. 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--loadon 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.toml → 1.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
- Fork it (https://github.com/amscotti/crystal_quest/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - 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.
crystal_quest
- 0
- 0
- 0
- 0
- 2
- about 4 hours ago
- July 10, 2026
MIT License
Fri, 10 Jul 2026 02:32:25 GMT