A headless, deterministic game engine for trading card games (TCGs). Define your game rules in TOML, and Cardinal handles validation, state management, triggers, and event emission.
- README_DETAILED.md — Complete guide (start here if you're new)
- ARCHITECTURE.md — Deep dive into design principles
- crates/cardinal/README.md — API documentation
- crates/cardinal-cli/README.md — Interactive game guide
- docs/CLI_USAGE_GUIDE.md — CLI tools for validation, compilation, and testing
✅ Fully Deterministic — Same seed + actions = identical outcome
✅ Data-Driven Rules — Define cards in TOML (no code changes)
✅ 14 Builtin Effects — Create cards without scripting (draw partially implemented)
✅ Hybrid Card System — TOML builtins + Rhai scripts for flexibility
✅ Headless — Embed in any interface (web, mobile, terminal, AI)
✅ Event-Based — Complete game log for replays and debugging
✅ Well-Tested — 67 tests covering core systems and scripting
✅ Clean Architecture — Clear separation of concerns
✅ Production-Ready Tooling — Comprehensive validation, compilation, and testing tools
# Run the minimal example game
cargo run --bin test-gameThe test-game folder contains a complete minimal example showing how to build a game with Cardinal. It's fully functional and ready to copy and customize!
cargo run --bin cardinal-cliInteractive terminal game with colored output and real-time state rendering.
# Validate rules
cardinal-cli validate rules rules.toml
# Validate cards
cardinal-cli validate cards-dir cards/
# Validate an entire pack
cardinal-cli validate pack my-pack/See CLI_USAGE_GUIDE.md for complete CLI documentation.
# Compile a pack with validation
cardinal-cli compile pack my-pack/ output/game.ccpack --verbose
# Test the pack
cardinal-cli test pack output/game.ccpackcargo test19 integration tests covering triggers, initialization, card abilities, and turn progression. All passing.
use cardinal::{GameEngine, Action, PlayerId};
let engine = GameEngine::new_from_file("rules.toml", seed)?;
engine.start_game(deck_0, deck_1)?;
let result = engine.apply_action(player_id, action)?;
for event in &result.events {
// Process event...
}Cardinal is a game engine referee:
Player: "I want to play card #1"
↓
Cardinal: Validates action, applies effects, evaluates triggers
↓
Returns: Events describing what happened
↓
UI: Reads events and updates display
Cardinal enforces:
- Turn structure — Phases, steps, priority rules
- Zone management — Hand, field, graveyard, stack, deck
- Action validation — Legality checks before applying
- Card abilities — Data-driven triggers and effects
- State consistency — GameState is the single source of truth
Same starting state + same actions + same seed = identical outcome every time.
Cardinal has no knowledge of screens or graphics. Any interface can use it.
Simple, unidirectional interface. Player sends action → Cardinal emits events.
One struct holds all truth. Everything else is derived from it.
| Document | Audience | Content |
|---|---|---|
| README_DETAILED.md | Everyone | Overview, concepts, quick start |
| BUILTIN_EFFECTS.md | Card designers | Complete reference for TOML-only card effects |
| SCRIPTING_GUIDE.md | Advanced card designers | Rhai scripting for custom card effects |
| ARCHITECTURE.md | Developers | Design principles, game flow, data structures |
| crates/cardinal/README.md | API users | Usage guide, integration examples, concepts |
| crates/cardinal-cli/README.md | Players | Terminal game guide, controls, examples |
| crates/cardinal/explanation.md | Code explorers | Design patterns, module layout, architecture |
crates/
cardinal/ — The game engine library
cardinal-cli/ — Interactive terminal game
rules.toml — Game definitions (cards, abilities, phases)
README_DETAILED.md — Complete guide
ARCHITECTURE.md — Design deep dive
You play "Goblin Scout":
1. Validation
✓ Your turn?
✓ Main phase?
✓ Own the card?
✓ In your hand?
✓ Have mana?
2. Apply Effect
- Remove from hand
- Add to field
- Subtract mana
→ Event: CardPlayed, CardMoved
3. Evaluate Triggers
- "enters the battlefield" trigger matches
→ Command: "deal 1 damage"
4. Resolve Stack
- Opponent takes 1 damage
→ Event: LifeChanged
5. Return to Caller
- Here are all the events that happened
- UI renders them
| Concept | Meaning |
|---|---|
| Zone | Where a card is (hand, field, graveyard, stack, etc.) |
| Phase/Step | Turn structure (start, main, combat, end) |
| Priority | Whose turn to act (determines action order) |
| Trigger | Card ability that fires on events |
| Stack | Where spells/abilities wait to resolve |
| Event | Something that happened (recorded for replay) |
| Command | Intermediate effect awaiting validation |
All tests passing:
cargo test31 tests covering:
- Game initialization
- Turn progression
- Action validation
- Card abilities & triggers
- Builtin effect execution
- Scripted effect execution
- State consistency
- Determinism
Edit rules.toml to:
- Define new cards using builtin TOML effects (no scripting required!)
- Add scripted cards (Rhai scripts in
examples/scripts/) - Change mana costs
- Add card abilities
- Customize phases
- Change game constants
No code changes needed. Cardinal supports both TOML-only cards and Rhai-scripted cards.
1. TOML-Only Cards (Recommended for most cards) Use builtin effects without any scripting. See BUILTIN_EFFECTS.md for the complete reference.
Example:
[[cards]]
id = "lightning_bolt"
name = "Lightning Bolt"
card_type = "spell"
cost = "1"
[[cards.abilities]]
trigger = "on_play"
effect = "damage"
[cards.abilities.params]
amount = "3"2. Scripted Cards (For complex custom behavior) Use Rhai scripts for unique mechanics. See SCRIPTING_GUIDE.md for details.
Cardinal supports 14 builtin effect types (1 partially implemented):
- Life & Damage:
damage,gain_life,lose_life,set_life - Card Draw:
draw(⚠️ not yet fully implemented),move_card - Creature Stats:
set_stats - Keywords:
grant_keyword,remove_keyword - Resources:
gain_resource,spend_resource,set_resource - Counters:
add_counter,remove_counter - Tokens:
create_token
See BUILTIN_EFFECTS.md for complete documentation and examples.
Cardinal includes JSON schemas for validating TOML files in VS Code:
- Install the "Even Better TOML" extension (recommended automatically)
- Open any TOML file — invalid fields or values are highlighted
- Get autocomplete — Press Ctrl+Space for valid field suggestions
- See exact errors — Hover over red squiggles for validation messages
Schemas validate:
- Individual card files (
cards/*.toml) - Card array files (
cards.toml) - Rules definitions (
rules.toml) - Pack metadata (
pack.toml)
See schemas/README.md for details.
- test-game/ — See a complete minimal game example
- README_DETAILED.md — Understand the system
cargo run --bin cardinal-cli— Play the game- BUILTIN_EFFECTS.md — Design cards without scripting
- crates/cardinal/README.md — Learn the API
- ARCHITECTURE.md — Deep dive into design
- Edit rules.toml — Customize your game
Cardinal is a clean, deterministic, reusable game engine:
- One engine for any turn-based TCG
- Many interfaces (terminal, web, mobile, AI)
- Hybrid card system (TOML builtins + Rhai scripts)
- Data-driven rules (TOML configuration)
- Full determinism (perfect replays)
- Well-tested (31 tests passing)
It's designed to be embedded, extended, and understood.