Purely functional Haskell "Magic: The Gathering" card model and game engine.
MtgPure is a showcase of how to make an elegant purely functional "Magic: The Gathering" DSL model and rules engine.
- The world's best MTG encoding.
- Pure data modelling of cards as a type-safe DSL.
- Card model is a deep embedding.
- Card model is algebraic and recursive.
- Cards type-check if and only if they are valid cards. (Barring a few exceptions such as bottom(⊥) appearing in card definitions.) Examples: creatures in graveyards can't be dealt damage; non-player objects can't draw cards, etc.
- Card model is expressive enough to replicate its source code through introspection.
- Cards can be used in arbitrary game formats without change.
- Formal changes to game rules should not require re-modelling cards. (Cards may be rewritten if they get errata.)
- Model can encode complex and outlier interactions without brute-force special casing. For example, the model will not contain stuff such as
ScrambleverseEffect
.
- Exhaustive and type-checked engine.
- Event-based programmatic control.
- Solitaire CLI. You control all player actions.
- Good for exploring card and game-state interactions.
src/MtgPure/Client/Terminal.hs
src/MtgPure/Engine/Fwd/Api.hs
src/MtgPure/Engine/Fwd/Impl.hs
src/MtgPure/Engine/State.hs
src/Demo/MtgPure/Gameplay.hs
$ cabal install fourmolu # for code generation ; add to PATH if needed
$ cd src
$ runhaskell MtgPure/Model/Object/ToObjectN/CodeGen.hs
$ cd ..
$ cabal build
$ cabal repl
ghci> :m +MtgPure MtgPure.AllCards MtgPure.Cards MtgPure.Model.BasicLandType MtgPure.Model.LandType MtgPure.Model.Recursive
ghci> :i allCards
allCards :: [AnyCard] -- Defined in `MtgPure.AllCards'
ghci> :i island
island :: Card OTLand -- Defined in `MtgPure.Cards'
ghci> compare island island
EQ
ghci> print island
Card "Island" $ ElectCardFacet $ LandCharacteristic [] [BasicLand Island] $ LandSpec []
ghci> island == (Card "Island" $ ElectCardFacet $ LandCharacteristic [] [BasicLand Island] $ LandSpec [])
True
ghci> island == (Card "Island" $ ElectCardFacet $ LandCharacteristic [] [BasicLand Mountain] $ LandSpec [])
False
ghci> mainDemoGameplay -- runs Demo.MtgPure.Gameplay
Notes:
- Setting
-Wno-type-defaults
so theShow
instances for cards don't need to constantly specifyNum
types whenInteger
is good enough for authoring. (Too much noise adding annotations forInteger
or evenInt
or an aliasI
.) src/MtgPure/Model/Object/ToObjectN/Instances.hs
imports large generated files and will take a while to compile, hence the-fobject-code
flag to cache the result inside theghci-compiled.bat
script.- Using
-fobject-code
withghci
seems to require quitting and reenteringghci
in order to get it to pick up the right runtime behavior after making code changes.
Demo.MtgPure.Gameplay
vlc-record-2023-02-21-17h22m25s-2023-02-21.17-05-23.mkv-.mp4
Contributions may or may not be accepted. If you are concerned about your time being wasted, please open an issue first to discuss. Entry-level contributions would probably be for card definitions or tests. More advanced contributions would be for the engine or model design.
- AI
- GUI
- Handling of integer overflow. I don't want to use unbounded integer types, and I'm okay with deviating from official MTG rules here.
- Safe (For the model and rules engine. UI and other stuff needs to interface with Unsafe libraries.)
- NoIncoherentInstances
- NoOverlappingInstances (todo: write a script that generates non-overlapping ToManaCost/ToManaPool instances needed to achieve this)
- NoTemplateHaskell
- NoUndecidableInstances