DeckBuild: A Declarative Domain-Specific
Language for Card Game Design
Matthew Ahrens, Raoul Veroy, and Karl Cronburg
{mahrens,rveroy,karl}@cs.tufts.edu
Dept. of Computer Science, Tufts University, Medford MA 02155
1.
Introduction - Target Domain & Users
1.1
Domain
DeckBuild’s provided artifacts, such as the game client to
test the cards and the computer-players with custom heuristics. While we hope to make those tools work without manipulating the runtime library behind the scenes to lower the
barrier of entry to our language, if the card game maker
wishes to assess new functionality in their cards and game
rules through these artifacts, then they should be prepared to
provide some implementation of their own. Card game makers who wish to extend only the grammar of existing poplar
deck building games will find all that functionality available
in DeckBuild.
Like most table top or board games, deck building card
games do not have many automated systems or testing tools
for their general use or creation. Only the most popular
games in the genre have software that either simulates game
play or running strategies. Typically the software is closed
or proprietary, and making changes to the software to fit
personal needs or a new game is discouraged. We have
identified a need to facilitate the creation and testing of new
deck building card games.
1.2
1.3
Users
Existing Languages
Presently there are no languages suited to describing features
of deck building card games in general terms. Existing software such as Dominion Online[Rio Grande Games, Making Fun 2014] or Magic the Gathering Online [Wizards of
the Coast 2014] allows for playing officially licensed games
with people or AIs, but does not open up any sort of design API / interface. The closest competitor to DeckBuild
is Dominiate, an open source project by Robert Speer. It is
a Dominion Simulator written in coffeescript.[Robert Speer
2011]
Dominiate provides a way for end users to specify policies
of a Dominion AI in terms of:
The intended audience for DeckBuild is card game makers
who want an easy way to describe and test new cards, card
types, or card game rules. Specifically, DeckBuild supports
describing cards and rules from the sub-genre of deck building card games such as Dominion or Magic the Gathering.
The card game makers we expect to adopt DeckBuild either
have a programming background or will be working closely
with a programmer.
In its current iteration, the DeckBuild suite provides an easy
way to represent cards, consumable by the runtime system
library. The technical designer (programming partner) can
then connect the functionality of the cards to the runtime library in any way they wish. The programmer does this using
• Priority to buy cards in.
• Priority to play cards in.
• Other heuristics available to the player at any game state.
While DeckBuild and Dominiate share a common goal of
letting users evaluate strategies of given combinations of
cards, Dominiate does not include support for new cards.
Logic for cards and rules is spread across its runtime system
making it difficult to add functionality for new cards. Dominiate can then be seen not as a language competitor for
game makers to compare, but rather a runtime system that
DeckBuild would hope to leverage in the future as an out-
[Copyright notice will appear here once ’preprint’ option is removed.]
1
{
mers, having DeckBuild automatically guarantee it is also
important. If a new card requires a specific resource of the
game state or runtime, it should be obvious upon describing
the card. This could be done through object-oriented interfaces or other contractual classification of card objects, but
that can quickly become difficult to manage, especially for
a large number of cards. We hypothesize that by representing cards and rules in a uniform syntax and parsing them in
Haskell, we can easily provide type safety and card requirements to any runtime system that wants to consume DeckBuild’s representation.
name: ’SimpleMoney’
author: ’Andrew’
requires: [’Smithy’]
gainPriority: (state, my) -> [
’Colony’,
’Platinum’
’Province’,
’Gold’,
’Duchy’,
’Silver’,
’Smithy’,
’Estate’,
]
}
2.3
Figure 1. An example heuristic written in Dominiate
The DeckBuild language can better benefit its early adopters
if they have fun tools to play with out-of-the-box to test their
new cards and game rules with. We hope to achieve, for the
programmers working with these card game makers, an easy
way to take the standard game representation and integrate
it in programs. DeckBuild provides a game state format and
runtime system with a basic AI library and client interfaces.
These APIs and features allow for automatic consumption of
card descriptions. DeckBuild also provides an interface for
specifying new runtime system functionality in the host language (Haskell). Eventually, it would fully realize our goal
if the programmer could provide the functionality that corresponded to abstract representation of arbitrary English card
descriptions that DeckBuild parses, and then the language
automatically makes those connections. For example, if a
new simulator were to be released that takes card representations in a novel way, the programmer should be able to
simply write the artifact generator from our abstract representation to that new format. This way both the programmer
and the card game maker gain the benefit of interoperability.
put format. Specifically, it would benefit both communities
to be able to specify cards and rules in DeckBuild, but output
cofeescript source for use in the Dominiate simulator.
2.
Goals
DeckBuild is at its core a way for card game makers to
describe cards for use by general purpose programs or tools.
Our goals reflect this, with more focus on end-user features
than performance or optimization features.
2.1
Readability
Currently two representations for deck building cards are accepted by our target communities. The game maker community represents the card as a physical artifact with plain English functional descriptions of what the card can do. This
can result in ambiguous and difficult to parse descriptions.
The programmer making tools for these games represents
cards in terms of their functionality (how they affect the
game state). While providing concrete benefits (able to be
parsed), in general purpose languages this requires the programmer to design deck building abstractions of their own.
A programmer needs such abstractions if they wish to centralize the card content in a single file. Centralizing this content improves readability by requiring the reader to go to
one place to see exactly how a particular card interacts with
the game engine. The card and rule abstractions in DeckBuild provide a functional (as opposed to imperative) interface to the artifacts DeckBuild provides. This interface is
designed with human readability in mind, making compromises in terms of expressive power. Non-programmers can
read DeckBuild’s card format as if they were holding a physical card on paper.
2.2
Support and Resources
2.4
Metrics and Measurement
Design decisions and what features were relevant made during DeckBuild were derived from the process of usability.
While we did not get to a point to measure our language
against the other software tools available, we conceptualized
it in the following way so that one could measure the effectiveness of our language if they chose to do so. Since the
goals of DeckBuild are user-centric, the successfulness of
our language could be measured using the quantified amount
of effort it takes for the programmer to connect the output of
the defined cards and rules to the runtime implementation
of their choice. Reducing this effort can come about from
meeting any of the following goals:
• Making the code readable, yet unambiguous lets the pro-
grammer reuse functionality since the size of the grammar they must support is smaller.
Consumability
• Making the internal representation of the card type safe
In implementation, programmers wish to describe cards and
rules by how they affect the game state. What these cards
do and how they are used can widely vary. Since functional
correctness is important to game designers and program-
and consumable reduces the amount of checks the programmer must perform before trying to integrate the
functionality of the card with a new system.
2
• Through good tool support and out-of-the-box function-
" +1 Card per card discarded"
} costs 2
ality, we hope to abstract away most of the common
boiler plate the programmer would want to write such as
a generic card game simulator or client-server system.
card Chapel :: Action {
"Trash up to 4 cards from your hand"
} costs 2
For this initial implementation, we assert the effectiveness
of our design in terms of the expressiveness that the card
game designer gets without having to delegate tasks to their
programmer. The card game designer should be able to use
DeckBuild to give a non-ambiguous description of what she
wants without necessarily having to tell the programmer in
English. DeckBuild is therefore a success if one is able to
describe any arbitrary cards from the official Dominion set.
We hope to someday show that these aspects of our language
are complete while still remaining robust enough to describe
any new card. The observable metric of Deckbuild’s robustness can be seen as through minimizing the amount of code
the programmer has to write when providing functionality
for new cards. While we do not have concrete results on running tests against these metrics, a programmer can assess the
effectiveness themselves from trying out the examples of our
base set of standard dominion cards.
3.
card Village :: Action {
+1 cards
+2 actions
} costs 3
card Woodcutter :: Action {
+1 buys
+2 coins
} costs 3
card Copper :: Treasure {
+1 coins
} costs 0
card Silver :: Treasure {
+2 coins
} costs 3
card Gold :: Treasure {
+3 coins
} costs 6
Language Features
card Estate :: Victory {
+1 victory
} costs 2
The current iteration of DeckBuild gives a game designer the
following features:
card Duchy :: Victory {
+3 victory
} costs 5
• Declarative enumeration of the content of a card compris-
ing its name, card type, what the card does when played
(effects), and the cost of the card in order to buy it.
card Province :: Victory {
+6 victory
} costs 8
• Declarative enumeration of the parameters to the me-
chanics of a turn in the game, namely the number of
actions and buys a player starts with on each turn, the
number of cards discarded at the end of a turn, and the
number of cards drawn in-between turns.
turn Dominion_Standard {
action 1
buy 1
discard all
draw 5
}
|]
These features give game designers a way to quickly express new cards and game mechanics. This speed of prototyping new deckbuilding game variants facilitates the exploration process. Namely, a deckbuilding game designer will
likely have a general idea what the cards should do, and have
a feeling that they will interact well together. Coming up
with specific numbers to put on the cards however requires
exploring the state space of possible cards.
This process is indicative of what DeckBuild should give
a game designer. Although this set of features leaves much
to be desired (see Section 5 for full discussion), we believe
it to be appropriate for our goals.
See the last appendix for a complete grammar for DeckBuild.
3.1
Above is a complete example program. The program defines ten cards and one turn mechanics declaration. The ten
cards are parsed and then compiled into a Haskell function kingdomCards :: [RuntimeCard]. The ten
cards are also parsed into an enumeration data type such
as VILLAGE :: CardName. This in particular facilitates the use of host-language features of Haskell when designing cards with decidedly non-declarative effects (see
Appendix B ) for an example). The turn declarations are
parsed and compiled in the same manner as the card declarations, except they are compiled into a Haskell function
turnRules :: [Turn].
The compilation just described is performed using Haskell’s
quasiquotation facilities. As a result, the recommended programming pattern is to define a single [deck| ... |]
quasiquoted program at the beginning of a Haskell module.
Example Program
[deck|
card Cellar :: Action {
+1 actions
"Discard any number of cards."
3
− Can I currently buy card ‘Y’?
The Haskell module is named based on why the cards in the
module are being grouped together. For example, a deckbuilding game based on World War II might have a module
named ‘WWII’ containing cards named after airplanes used
in World War II.
The runtime system then imports this module that contains a complete deck of cards compiled from the DeckBuild program. Presently the runtime system can be interfaced with in three ways:
− Can I currently play card ‘Z’?
− Has the game ended? If so, who won?
The use of Haskell as the host-language of DeckBuild
gave the runtime system a lot of power in being able to make
guarantees about the state of a game. In particular, the firstclass nature of IO lets the runtime system divide up the portions of the system which are and are not allowed to perform
IO. This division makes IO bugs impossible to create when
designing and writing purely functional components of the
runtime system. Similarly, the immutable nature of Haskell
data instances makes shared-memory bugs impossible. This
is important in DeckBuild’s runtime system when communication occurs between the core components of the runtime
system (functions inside the state monad) and the computerplayer & web socket components (and other components not
allowed to directly alter the state of the game).
In the current iteration of our language and the runtime
system, certain concepts such as ACTIONS and COINS are
hard-coded in the runtime system and in the language as
concrete syntax. These names are not inherently common
to all deckbuilding games, and will be abstracted out in a
future iteration of the DeckBuild language. See Section 5
for a discussion of how this and other parts of the runtime
system and language will be given better abstractions in the
future.
• As a Haskell backend API for performing probabilis-
tic inference on the possible states of the game given a
model for the play heuristics of two computer-players
playing the game. This is where the Machine Learning
users (with Haskell experience) interested in deckbuilding games can use DeckBuild.
• As a web socket API for designing web-based deckbuild-
ing games. This is where web programmers (with minimal to no Haskell experience) can make use of DeckBuild to create a deck build game and corresponding web
site tailored to their genre of interest.
• As a command-line interface for humans to play against
one another (making use of the web sockets API). This
is where a game designer can have human subjects playtest their game without having to print out mock-ups of
cards.
The runtime system itself is implemented as a Haskell cabal package separate from (but referencing / importing) the
DeckBuild language package. It is implemented in the state
monad for maintaining a stateful view of the game as turns
progress. We also use the IO monad for executing playerbased events in the game. The core components of the runtime system handle functionality common to all deckbuilding games comprising:
4.
We evaluated the effectiveness of DeckBuild language as a
game design tool for both programmers and non-programmers
by considering game designer productivity.
For this class project, we used ourselves as test subjects
and performed an informal evaluation of how easy it is to
design cards using DeckBuild. We used our DeckBuild language to describe all the existing cards from the base set of
Dominion [Dominion Strategy 2014]. We found that specifying cards in our language was easy and straightforward.
Given the limited scope of the semester, we could not
attempt a more formal evaluation of our language. If we
had the time, there are a couple of possible paths we could
take. First, we could release the language, tools and runtime
system to game designers for them to use. We could gather
an evaluation of our current design and implementation by
soliciting bug reports and feature requests. This method of
evaluation allows us to incrementally improve the design
that using the target users’ wishes. In this scenario, we would
measure the success of our design in terms of the rate of
adoption by our target users.
Another possible evaluation method would be to conduct
more formal user studies. We would identify one or more
competing technologies and use this as a basis for comparison. While this is more principled in theory, in practice we
think that this would be little benefit for a lot of effort. Find-
• Maintaining piles of cards
Moving cards between decks (enforcing no ‘cardduplication-bugs’)
• Library of:
Getters and setters for Game state
Execution of common events:
− draw
:: State Game ()
− shuffle
:: State Game ()
− discard
:: State Game ()
− doPhase
:: State Game ()
− takeTurn :: State Game ()
− runGame
Evaluation
:: State Game ()
Common Game state questions:
− How many cards of type ‘X’ are in my hand?
4
ing suitable subjects would be difficult as DeckBuild has a
very niche target market.
5.
Future Work
We would like to add a number of features to the language.
First, we would like to add syntax for more complicated
effects. Currently, we only support action and buy effects.
The original game of Dominion allowed for more complex
actions such as drawing multiple cards and attacking other
players. This would complicate the language but is necessary
to to capture the essence of the original game.
We would also like to develop a type system that models
the cards and the rules of the game. This would lead to
development of a type-checker that we envision would help
designers catch game design errors earlier.
We also plan to tweak the language with respect to how
much is built into the language and how much is made
generic. For example, COINS are DeckBuild primitives, but
the user may be designing a game that doesn’t use or need
money. One possible solution is to provide common game
artifacts such as money as libraries that use more generic
primitives. For example, we may have a RESOURCE language primitive that may be configured to give the same effect as COINS.
We would also like to have more relevant and useful
debugging information. Currently debugging information is
limited to line numbers in the source file. This would not be
very useful to non-Haskell programmers.
For a better game design experience, we envision an automated card balancer that attempts to balance the different
design axes. While this isn’t related to the language, we think
this would be a useful tool that could drive greater adoption
of DeckBuild.
The runtime system would ultimately be the tipping point
for designers. Even if game designers could easily specify
the cards and game mechanics in DeckBuild, they would
have no incentive to do so if they had to implement the
runtime system too. To start with, we would like to have
graphical client-server support, markup language conversion
tools, and a library of AI players that designers can choose
from.
5
References
Dominion Strategy. Dominion Base Card List, 2014. URL
http://dominionstrategy.com/card-lists/
dominion-card-list/.
Rio Grande Games, Making Fun. Play Dominion Online, 2014.
URL https://www.playdominion.com/Dominion/
gameClient.html.
Robert Speer. Dominiate: Dominion Simulator, 2011. URL http:
//rspeer.github.io/dominiate/play.html.
Wizards of the Coast.
Magic The Gathering Online,
2014. URL http://archive.wizards.com/Magic/
Digital/MagicOnline.aspx.
6
A.
Example Program - Haskell Output
data CardName = CELLAR | CHAPEL | VILLAGE | WOODCUTTER | COPPER
| SILVER | GOLD
| ESTATE | DUCHY
| PROVINCE
deriving (Eq, Typeable, Show, Ord)
data RuntimeCard = RuntimeCard { cID
, cType
, cDescr
, cCost
deriving (Eq, Typeable, Show, Ord)
::
::
::
::
!CardName
!CardType
!CardDescr
!CardCost }
kingdomCards = [ RuntimeCard
{ cID
= CELLAR
, cType = ACTION
, cDescr = CardDescr
{ primary = [Effect {amount = 1, effectType = ACTIONS}]
, other
= "Discard any number of cards. +1 Card per card discarded"
}
, cCost
= 2 }
, ...
, RuntimeCard
{ cID
= PROVINCE
, cType = VICTORY
, cDescr = CardDescr
{ primary = [Effect {amount = +6, effectType = VICTORYPOINTS}]
, other
= []
}
, cCost = 8 }
]
turnRules = [ Turn
{ turnID
= "Dominion_Standard"
, turnPhases =
[ Phase {phaseName = ActionP, phaseInt
, Phase {phaseName = BuyP,
phaseInt
, Phase {phaseName = DiscardP, phaseInt
, Phase {phaseName = DrawP,
phaseInt
] } ]
B.
1
2
3
4
5
6
7
8
9
10
=
=
=
=
PhaseInt 1 }
PhaseInt 1 }
All
}
PhaseInt 5 }
Example Program - Complex Card Effects
-- Discards any number of cards, returning the number of cards discarded
cellarEffect’ :: forall (m :: * -> *). (MonadIO m, MonadState Game m) => m Int
cellarEffect’ = do
g <- get
c’ <- liftIO $ ((mayPick.p1) g) g CELLAR
case c’ of
Just c -> if
elem c ((cards.hand.p1) g)
then discard c >> cellarEffect’ >>= \n -> return $ n + 1
else return 0
Nothing -> return 0
11
12
-- Discard any number of cards, then draw that many cards:
7
13
14
cellarEffect :: forall (m :: * -> *). (MonadIO m, MonadState Game m) => m ()
cellarEffect = addActions 1 >> cellarEffect’ >>= \n -> draw n
The code above defines the state transformation to be performed on the game when a CELLAR is played. The state monad is
required because the effect moves cards around between piles in the state of the game. The IO monad is also required because
the CELLAR effect requires asking the player to pick cards. This example also illustrates the use of:
• cards
:: Pile
• hand
:: Player -> Pile
• p1
:: Game
• mayPick :: Game
-> [CardName],
-> Player
-> CardName -> IO (Maybe CardName)
These are the kinds of operations DeckBuild will support natively in the future when it has support for function declarations
and expressions.
C.
Runtime System - State Format
λ> runGreedy (0.5, 0.5)
Player1:
name
hand
inPlay
deck
=
=
=
=
Player2:
name
hand
inPlay
deck
=
=
=
=
"Greedy1"
[ESTATE, GOLD, PROVINCE, PROVINCE, SILVER]
[]
[SILVER,
PROVINCE, COPPER, SILVER, COPPER
,ESTATE,
COPPER,
COPPER, VILLAGE, VILLAGE
,PROVINCE, ESTATE,
SILVER, COPPER]
dscrd = [SILVER, SILVER, SILVER, COPPER, COPPER, PROVINCE]
buys=1, actions=1, money=0
"Greedy2"
[COPPER, COPPER, COPPER,
[]
[SILVER,
SILVER, GOLD,
,GOLD,
ESTATE, GOLD,
,PROVINCE, GOLD]
dscrd = [SILVER, COPPER, SILVER,
buys=1, actions=1, money=0
Trash: []
Supply: [(COPPER,60),
,(SILVER,27),
,(MILITIA,10),
,(MINE,10),
Turn #: 30
(CELLAR,10),
(VILLAGE,6),
(REMODEL,10),
(DUCHY,8),
COPPER, VILLAGE]
COPPER, COPPER,
ESTATE
ESTATE, PROVINCE, VILLAGE
SILVER, SILVER, PROVINCE]
(MOAT,10),
(WOODCUTTER,10),
(SMITHY,10),
(GOLD,25),
(ESTATE,8)
(WORKSHOP,10)
(MARKET,10)
(PROVINCE,0)]
Above is a snapshot of the state of the runtime system after two probabilistic computer-player models played a game against
each other. The player models are given direct access to this game-state information on each turn of the game. In future
iterations of the runtime system their will likely be a feature whereby the players can ‘remember’ what they did on previous
turns.
In the snapshot, the Supply :: [(CardName,Int)] where the Int corresponds to the number of copies of that
particular card in the supply. The runtime system is implemented in this manner so as to reduce the space-complexity constant.
In a future iteration of the runtime system, game designers will be able to turn on and off these kinds of optimizations on a
per-Pile basis.
Similarly, a game designer may wish to specify the order in which cards appear in a Pile. In the current implementation of
the runtime system, ordering of most piles defaults to LIFO queue order (except a player’s hand which is shown alphabetically).
8
Deck-Building Card Game Grammar
December 7, 2014
hdeckDeclsi
::= hdeckDecl i*
hdeckDecl i
::= hcardDecl i | hturnDecl i
hcardDecl i
::= card hcardIDi :: hcardTypei { hcardDescr i } costs hintLiti
hcardDescr i
::= heffectDescr i* henglishDescr i
henglishDescr i
::= hstringLiti*
heffectDescr i
::= hplusOrMinusihintLiti heffectTypei
hplusOrMinusi
::= + | -
hturnDecl i
::= turn hturnIDi { hphaseDescr i* }
hphaseDescr i
::= hphaseNamei hphaseInti
hphaseInti
::= hintLiti | all
heffectTypei
::= actions | coins | buys | cards | victory
hcardTypei
::= Treasure | Action | Victory
hphaseNamei
::= action | buy | discard | draw
h*IDi
::= hidentifier i
h*Liti
::= As defined in Text.Parsec.Token
hidentifier i
::= As defined in Text.Parsec.Token
© Copyright 2026 Paperzz