A tiny, correct, fast Magic: The Gathering rules engine.
tinymtg is an implementation of the Magic: the Gathering game, written in TypeScript.
The core engine is approximately 10k lines of code in index.ts, meaning it can be read
top-to-bottom and understood completely. It's designed to be supremely readable, with heavy
use of comments, rules references, and type guarantees.
Magic: the Gathering is my favorite game. I was curious how complex the game I loved to play would be to turn into code. there's several existing open-source engines out there, but all of them are quite complex projects.
This codebase was hand-crafted to be a reference for the core rules that make Magic work, and an experiment to fit the entire complex game into a reasonable size, small enough to be understood quickly, without taking shortcuts around correctness.
Currently, approximately 5,000 of Magic's 33,000+ cards are playable in the engine.
It supports these features from Magic the Gathering:
- All core features like attacking, blocking, playing lands, casting spells, and paying mana.
- Activated abilities from any zone.
- Triggered abilities from any zone.
- Extra costs on spells and abilities: pay life or sacrifice permanents.
- Static effects, including correctly implemented layers and copiable characteristics.
- Replacement effects, up to the complexity level of Chains of Mephistopheles.
- Extra turns and phases (Time Walk), and skipped turns and phases.
- Searching, scrying, surveiling.
- Playing from other zones.
- Many keywords, such as trample, deathtouch, flying, reach, indestructible, hexproof, shroud, and prowess.
More advanced features:
- The engine is fully deterministic. It can be rewound and replayed correctly, or forked with new actions.
- The engine can operate fully synchronous or fully async. It uses a React Suspense-like system, to retrieve player responses as either blocking or non-blocking. So, it's suitable for fast, single-threaded rollouts as well as request-response non-blocking live play.
- Cards can be generated en-masse with structured effect representations, or hand-authored with custom callback logic.
- Visibility: revealing a card from your hand, looking at a player's hand to force them to discard, face-down exiled cards
- Planeswalkers.
- Cost reductions or increases.
- Alternate costs, including flashback, madness, foretell, plot, etc.
- Dual-face cards.
- Compound / split cards: Adventures, Omens, Fuse.
- A few evergreen keywords like first strike and double strike.
- Many non-evergreen keywords.
- Player-specific logs.
A playable web client is in progress.
The tinymtg engine code itself is primarily hand-designed and implemented.
AI was used heavily for writing exhaustive correctness tests and for writing the automated Forge parser / importer.
This primarily AI-authored code lives outside the core 10k-line index.ts file.
This codebase owes a huge debt of gratitude to the Forge Engine project. I've used their card definition syntax to power almost all of the 5k currently supported (and constantly growing) cards used by tinymtg.
bun install
bun run index.ts
bun run check # typecheck and test
bun run fix # autofix all files