mage-go

module
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: GPL-2.0

README

mage-go

A Magic: The Gathering rules engine written in Go, inspired by XMage's approach to open-source rules enforcement. The engine is a ground-up reimplementation — not a port — using idiomatic Go patterns: flat interfaces, composition over inheritance, and functional options instead of deep class hierarchies.

The engine is 2-player only. The card pool targets early Magic, from Alpha through roughly the Odyssey/Onslaught block era. Mechanics and rules from later sets (planeswalkers, transform, energy, sagas, etc.) are not in scope.

How the Engine Works

Effects are the core abstraction. One-shot effects (deal damage, draw cards, destroy target) implement a simple Effect interface and resolve immediately from the stack. Continuous effects (lord boosts, aura enchantments, control-change) re-apply from scratch every cycle in MTG layer order (copy → control → type → color → ability → P/T), ensuring correct interaction regardless of timestamp.

Abilities come in three flavors — activated ({cost}: effect), triggered (when/whenever...), and static (continuous effects that apply while the source is on the battlefield). Triggered abilities listen for typed game events and filter via condition predicates, so new triggers don't require engine changes.

The attr system unifies keywords, capabilities, and type identity into additive integer counts on each permanent. HasAttr(Flying) replaces scattered boolean flags; granting and revoking compose naturally (base flying + "loses flying" = net zero). The entire granted-attr map resets and recomputes each effect cycle.

GameMutator is the interface boundary between effects and game state. Effects never touch *Game directly — they call explicit mutation methods, keeping the engine's internals shielded and the set of legal mutations auditable.

The stack is a standard LIFO queue unifying spells and abilities. Priority passes between players; when both pass with objects on the stack, the top resolves. State-based actions run between each resolution.

Combat handles banding, first/double strike, trample, and damage assignment with prevention/redirection/reflection via the DamageSystem subsystem.

Status

The rules engine (pkg/mage/) is relatively stable and heavily tested — 800+ tests cover card interactions, combat, triggered abilities, continuous effects, banding, protection, and more. Every card is implemented test-first.

The TUI and SSH server (cmd/tui/, cmd/server/) are rough prototypes. They work well enough to play games against the AI or another human over SSH, but the interface is bare-bones and the AI is basic.

Package Layout

pkg/mage/              # engine (package mage)
pkg/mage/core/         # enums, value types (package core)
pkg/mage/gametest/     # test harness DSL (package gametest)
pkg/mage/interactive/  # TUI/AI player layer
cards/limited/         # Alpha (Limited Edition)
cards/arabian/         # Arabian Nights
cards/antiquities/     # Antiquities
cards/legends/         # Legends
cards/custom/          # custom/test cards
cmd/tui/               # terminal UI
cmd/server/            # SSH multiplayer server
cmd/fetchset/          # set data fetcher
cmd/genset/            # stub generator from Scryfall JSON
data/                  # Scryfall JSON card data per set
.claude/skills/        # Claude Code skills for card implementation

Adding a New Set

The project includes a pipeline for implementing entire card sets:

  1. Fetch card data from Scryfall:

    go run ./cmd/fetchset -o data/DRK.json DRK
    
  2. Generate Go stubs from the JSON. This creates one file per card type (creatures.go, spells.go, etc.) with full Oracle text as comments and // TODO: implement markers. Reprints already registered in other sets are automatically skipped.

    go run ./cmd/genset "The Dark" data/DRK.json cards/thedark/
    
  3. Implement cards. Each card is implemented TDD-style: write a failing test that exercises the card's behavior, then implement the card to make it pass. The pkg/mage/doc.go file is the comprehensive API reference for all available effects, triggers, targets, and costs.

  4. Validate completeness by checking every implementation against Scryfall Oracle text, auditing test coverage, and running the full test suite.

Claude Code skills (.claude/skills/) automate steps 3 and 4 — /implement-set handles the full implementation workflow and /validate-set audits the result.

Build & Test

go test ./...    # all tests
go build ./...   # build check
go vet ./...     # vet

The only external dependency is github.com/google/uuid.

Credits

This project is inspired by XMage, which pioneered open-source MTG rules enforcement in Java. The engine's architectural bones — layered continuous effects, event-driven triggers, stack-based resolution — are drawn from XMage's design, reimplemented from scratch in idiomatic Go. XMage's card implementations were consulted as a reference for rules correctness throughout development.

Magic: The Gathering is a trademark of Wizards of the Coast LLC.

License

GPL-2.0. See LICENSE.txt for full text.

Directories

Path Synopsis
Package cards imports all card set packages to register every card.
Package cards imports all card set packages to register every card.
cmd
cardart command
Package cardart generates unique deterministic pixel art for Magic: The Gathering cards.
Package cardart generates unique deterministic pixel art for Magic: The Gathering cards.
fetchcatalog command
fetchcatalog fetches card metadata from the Scryfall API for all sets from Alpha through Onslaught block and writes per-set JSON files to data/catalog/.
fetchcatalog fetches card metadata from the Scryfall API for all sets from Alpha through Onslaught block and writes per-set JSON files to data/catalog/.
fetchset command
fetchset fetches all cards in an MTG set from the Scryfall API and writes a JSON array to stdout.
fetchset fetches all cards in an MTG set from the Scryfall API and writes a JSON array to stdout.
forge command
cmd/forge generates complete, balanced Magic: The Gathering card sets.
cmd/forge generates complete, balanced Magic: The Gathering card sets.
gametest command
Command gametest runs two AI players against each other, printing the game state at the start of each turn and every decision each AI makes along with the choices it had available.
Command gametest runs two AI players against each other, printing the game state at the start of each turn and every decision each AI makes along with the choices it had available.
genset command
genset generates stub Go files for a card set from its JSON data.
genset generates stub Go files for a card set from its JSON data.
oracle-replay command
oracle-replay reads an XMage SelfPlayJsonRecorder JSONL file and replays the game in mage-go, validating state at every recorded priority point.
oracle-replay reads an XMage SelfPlayJsonRecorder JSONL file and replays the game in mage-go, validating state at every recorded priority point.
pylib command
Package main exposes the mage engine as a C shared library for use from Python (via cffi) or any other language with a C FFI.
Package main exposes the mage engine as a C shared library for use from Python (via cffi) or any other language with a C FFI.
scenarioanalyze command
Command scenarioanalyze reads JSONL output from scenariotest and flags potential engine bugs.
Command scenarioanalyze reads JSONL output from scenariotest and flags potential engine bugs.
scenariotest command
Command scenariotest runs batches of AI vs AI games using Shandalar rogue decks and emits structured JSONL results for analysis.
Command scenariotest runs batches of AI vs AI games using Shandalar rogue decks and emits structured JSONL results for analysis.
server command
tui command
wasm command
Command wasm builds the mage engine as a WebAssembly module, exposing game-loop functions to JavaScript via syscall/js.
Command wasm builds the mage engine as a WebAssembly module, exposing game-loop functions to JavaScript via syscall/js.
internal
scenario
Package scenario provides types and utilities for running AI vs AI scenario tests using Shandalar rogue decks.
Package scenario provides types and utilities for running AI vs AI scenario tests using Shandalar rogue decks.
tui
pkg
catalog
Package catalog provides a Scryfall-sourced card metadata catalog covering sets from Alpha through Onslaught block.
Package catalog provides a Scryfall-sourced card metadata catalog covering sets from Alpha through Onslaught block.
mage
Package mage implements the two-player Magic: The Gathering rules engine.
Package mage implements the two-player Magic: The Gathering rules engine.
mage/core
Package core defines the fundamental value types, enums, and constants used throughout the MTG game engine.
Package core defines the fundamental value types, enums, and constants used throughout the MTG game engine.
mage/dsl
Package dsl is the card-author-facing surface of the mage engine.
Package dsl is the card-author-facing surface of the mage engine.
mage/interactive
Package interactive provides the interactive player layer for the MTG engine: human players driven by a TUI over channels, computer players driven by pluggable heuristic strategies, position evaluation, and game-loop runners for both single-player (human vs.
Package interactive provides the interactive player layer for the MTG engine: human players driven by a TUI over channels, computer players driven by pluggable heuristic strategies, position evaluation, and game-loop runners for both single-player (human vs.
mage/interactive/ai
Package ai provides computer-controlled players with pluggable strategies for the MTG engine.
Package ai provides computer-controlled players with pluggable strategies for the MTG engine.
mage/interactive/ai/combatsolver
Package combatsolver computes joint-optimal combat decisions for the AI: which attackers to declare, which blocks to assign, and which combat-eligible instants and activated abilities to fire in the post-blockers response window.
Package combatsolver computes joint-optimal combat decisions for the AI: which attackers to declare, which blocks to assign, and which combat-eligible instants and activated abilities to fire in the post-blockers response window.
mage/interactive/ai/heuristic
Package heuristic implements a fast, personality-driven AI strategy that makes decisions via local heuristics (without search).
Package heuristic implements a fast, personality-driven AI strategy that makes decisions via local heuristics (without search).
mage/interactive/ai/search
Package search implements a full-turn minimax AI strategy.
Package search implements a full-turn minimax AI strategy.
mage/interactive/eval
Package eval provides position evaluation and scoring for the MTG engine.
Package eval provides position evaluation and scoring for the MTG engine.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL