Designing a card game has honestly called on more of my programmer skills than thinking about video games. Card games are surprisingly a lot like enterprise web applications. Cards go through lifecycles and have callback functions and methods similar to an object going through its lifecycle in any enterprise application.
Lifecycle Hooks & Callback Functions
Objects in an enterprise software application, such as components in Angular or React, go through a lifecycle as they are instantiated, go through state changes, and are eventually destroyed. Each of these has a “lifecycle hook”, which is a function that is called when that particular lifecycle event happens.
In card game terms, these are similar to effects that trigger when a card enters the battlefield, leaves the battlefield, is tapped, or attacks or blocks. Trigger conditions are lifecycle hooks for cards.
A callback function is one that triggers when another object goes through some type of change. It’s subscribing to another object’s state, then doing something when that object does something.
In card games many cards have triggers for when another card performs one of its lifecycle hooks. For example, triggering when another creature enters the battlefield, or dies.
White decks in Magic the Gathering love effects that heal you. This isn’t amazingly good as an effect by itself, but white decks also have effects that trigger when you are healed. For example, Lunarch Veteran heals you for +1 life each time a creature enters the battlefield under your control and Hallowed Priest gains a +1/1 counter each time you gain life. Each of these has a callback for when a common game action occurs, and when both of these are on the field, it effectively means Hallowed Priest gains +1/1 each time a creature enters the battlefield.
White creatures also commonly have a keyword ability called Lifelink, which heals you for each point of damage inflicted by that creature. This keyword is effectively attaching to the damage hook, and in turn triggering the callback function on Hallowed Priest. This means that effectively, Hallowed Priest is now gaining stats every time a creature enters the battlefield OR that creature with Lifelink attacks. Magic Decks tend to build up combos between attaching these different hooks and callback functions together. Combo decks are oriented around finding infinite loops in these functions, to instantaneously or quickly win the game.
Types & Interfaces
Another functionality of card games is type keywords. These are perhaps best compared to Interfaces. Interfaces are a concept in programming that are very similar to Classes, except a little more loose. When an object implements an interface, it’s setting up an expectation that it will have certain properties and can be used in certain ways. For example, a creature type is setting up the expectation that it has a power and toughness statline; that it can only be played on your turn; and that it enters the battlefield, and can engage in battle as an attacker or blocker. Card effects can also reference this creature type, making it clear that they affect creatures. Types can be combined on a single card, following a compositional design pattern, though some supertypes cannot be combined, such as sorcery and instant. Artifact, Enchantment, Creature, Land, Saga, and Planeswalker have all been shared on various cards before however.
Similarly Sorcery establishes that a card can only be played on your turn when the stack is empty. Instant establishes that it can be played on either player’s turn at any point in the stack. Enchantments establish that a card sticks around permanently on the field; where auras are a subtype of enchantment that are attached to cards.
Most colorless permanent cards in Magic are given the artifact type, which means they accept interaction from anything that references the artifact type, which means that while colorless artifacts can be “splashed” into any deck, they’re susceptible to interaction from a lot of different cards that target artifacts. Thus, they have a weakness that counterbalances their versatility.
Since a card can have multiple of these types applied to it, it’s like an object that implements multiple interfaces. Under certain circumstances cards can even have new types applied to them, thus having new interfaces implemented. Effects can add keywords or effects to other cards, changing the lifecycle hooks or callback subscriptions of those cards. Keywords and effects can also act like methods that a card is granted access to when written on the card, combining in a compositional design pattern.
Types for the Types
Importantly, these sorts of super-type interfaces help divide cards into a few reliable categories of interaction and targetability. This means that adding new super-types can be very dangerous, because then anything that doesn’t specifically mention that supertype leaves it exempt. That means either errata needs to be issued, or cards are left broken against newer ones.
For example, the older Yugioh card, Gravity Bind, prohibits monsters level 6 or higher from declaring attacks, but many of the game’s powerful monsters no longer use levels. Many older tokens, such as Scapegoat, indicate that they cannot be used as Tributes, but modern extra deck monsters use monsters as Material rather than tributes. Many other cards have specified they’re not eligible to be used for specific types of extra deck summons, but end up exploitable for extra deck summons added to the game later.
Dimensional Barrier and Ultimate Slayer both reference the 4 types of extra deck monsters that currently exist in the game, but if another were added, they would be useless against that. This could be remedied if Yugioh had a conjoined term for all extra deck monster types, but it currently doesn’t.
Similarly in Magic the Gathering, there was the recent decision to introduce the “Battle” super type. This means that most cards in the game cannot directly interact with these battle cards. However Magic also has a large category called, “permanent,” for every card that stays persistently on the field, of which Battles are a member. This means that a significant number of existing cards can specifically affect Battles without needing to specifically name them. This is something that would not be possible if they introduced a new non-permanent supertype besides instant and sorcery, because most cards referring to non-permanents say “instant and sorcery”.
Tactical State Management
Interfaces and Inheritance are strategies for dividing “state” in computer applications so that it can be ensured that code is only interacting with objects in ways that are intended and there are no run-away side effects. In a card game, dividing the possibility space in this way is important for creating differentiation between cards and card types, while allowing them to interact with one another in a predictable and prescriptive manner. In software applications, dividing and limiting access to state is a means of avoiding code corruption or memory leaks. In card games, it’s more about strategy and game design, but it can also prevent awkward situations. Many cards in Magic will exile a card until they are removed from the field, but there are some means to recover an exiled card, which can mean that now the original enchantment is sitting around uselessly, much like a container with a null pointer to the object it’s meant to contain.
An effect in Magic that reads, “Exile target permanent” is an INCREDIBLY powerful effect, because it has such unrestricted access to so much of the game state, it bypasses many of the hooks that normally occur for a card being destroyed, sacrificed, or dying, and it leaves the targeted card in a state which is extremely difficult to interact with. This is especially scary, because it includes Lands, which normally have special exceptions carved out for them to make them immune to removal. Lands are not counted as Spells like every other card in the game, and thus cannot be counterspelled. And most effects that target permanents or other spells include the text, “non-land” to protect lands from removal.
Keeping careful access on which parts of your game can affect what other parts is also a good way of preventing run-away combo effects from being possible. Yu-gi-oh splits its game into archetypes precisely to prevent this type of run-away complexity overload. However I also believe that more game elements touching one another in more distinct ways is a big part of cultivating a larger possibility space, and ultimately more depth. There are a crazy number of rogue (not meta, but threatens the meta) decks in the Modern format for Magic The Gathering that very few people know how to counter, precisely because there are 20,000 cards in the game, and you can mix any two cards into the same deck, and find a way to make it function. A larger possibility space means you can potentially make a game more deep than a game with a smaller possibility space, but it’s also a balance nightmare. Dividing the game more strictly keeps the state space more manageable, allowing you to focus only on what you’re currently working with, and allowing you to easily look up other cards that might affect.
For obvious reasons, it’s helpful to maintain a card database as you develop your game, and to include filtering that can isolate cards by their various attributes, and look up common card effects, as well as see every card that could affect a hypothetical new card.
From all these examples it should make sense that the same type of system architecture that goes on in programming is very applicable to card games. In my game, Charmed Chains, I’ve decided to divide all cards into “Charms” and “Familiars”, similar to how Yugioh divides cards into Monsters and Spells/Traps without exception (so there’s technically 3 super types of cards, but only 2 designations most of the time). And even monsters that are somehow in the spell/trap zone, or spells/traps in the monster zones have rules on whether they’re treated as a monster, or a spell/trap, or both.
So I’ve decided to conjoin all non-creature effect cards into one category called Charms, then create keyword subcategories to address different effect types. I’ll have “PureCast” for ones you can only use on your own turn, “QuickCast” for ones you use on your opponent’s turn, “SetPlay” for ones you need to set face down, then can use during your opponent’s next turn or later; and “SetDelay” for ones you need to set, then can only activate on your next turn. And this means that effects can specifically reference these effect styles, the same way Yugioh effects can reference spells or traps to deal with specific effect styles.
Practical Data Structures
In a similar way, in attempting to create a solution for Yugioh equip spells versus Magic’s auras and equipment, I’ve arrived at a system I’m calling the “pocket”, which is an array that any card could potentially have. And if the card has no pocketed cards under it, that array is “null”, and the card has no pocket for the purpose of effects. That means I can also create keywords like, “equipped” and “Pocket Recovery Cost”, which create hooks for when a card has been pocketed, or in the event of a pocket being “emptied”, which itself is a keyword.
In Yugioh, Equip Spells are infamously awful for how easily they’re stripped from monsters, and how easy it is to destroy them (Auras in Magic get a similarly bad rap). They’re allowed to have ridiculously powerful effects and support unlike any other card type on the basis that they’re so easily gotten rid of.
Equipment in Magic has a slightly better reputation, because it’s not destroyed when the monster it’s attached to is, it just dequips and stays on standby for you to attach it to something else, for a small fee. Many of the best equip spells in Yugioh, such as Moon Mirror Shield, are similarly sticky, letting you pay a fee when they’re destroyed to return them to the hand and use them all over again.
Yugioh also has a similar Equip-like system in the form of Xyz Monsters and Xyz material. Xyz Material is stored under an Xyz monster, and can be consumed as a cost for effects. It’s also significantly harder to remove than Equip Spells, and doesn’t consume a spell/trap slot in your backrow. Some monsters have effects that are passed up to Xyz monsters when equipped as Xyz material (such as many Mathmech monsters). It’s harder to remove than equip spells, because almost nothing interacts with the Xyz Material zone, and Xyz monsters won’t lose their material from being flipped down the way an equip spell will be destroyed.
So in my case, I have a limited number of charm slots like how Yugioh has a limited number of spell/trap slots. I could have decided that when something with a pocket is removed, that its pocketed charms just get bounced out to the charm zones and reattach, but I think that’s rather clunky and inconvenient. Instead, I created the Pocket Recovery Cost keyword, so when the card holding a pocket is removed, and the pocket is “emptied”, you can pay a cost on the spot to return the card to the hand or deck, much like Moon Mirror Shield, except as a standardized element of design. Pocketed cards similarly are treated as charms, and can be targeted by effects that target charms, rather than being in the “Xyz Dimension” that Yugioh Xyz monsters have.
The end result is that if there’s a problematic card in a monster’s pocket, you can target that card directly and destroy it, much like in Magic, and if one of your monsters is removed, you can recycle its equipment, rather than losing it all to the ether. Pocketed cards are also treated as being present within a line, cards can mention destroying all charms in a line, and include pocketed ones. In my playtests, this has created the intended result where shoving something into a pocket is an effective form of non-destructive removal, but nothing in a pocket is completely out of reach.
It should go without saying that this strategy of cards holding a pocket of other cards is very similar to the way that a lot of objects in programming frequently hold arrays consisting of other objects, even of the same type as the parent object (though in my case, if an object has a parent, it’s also specifically forbidden from holding any children).
Stacks & Chains
Another popular data structure that is common to card games is the Stack data structure, which Magic The Gathering calls out by name, and Yugioh refers to as a “chain” (and Yugioh also prohibits things from being added to a chain once it starts resolving, whereas magic is content with letting the stack build up and deplete and build up again before completely resolving). In both games, you’re allowed to activate certain effects during your opponent’s turn. These get added to a stack or chain in a “first in, last out” order. That means you’re allowed to react to your opponent doing things with actions of your own, and those will resolve before the previous effect. So, your opponent might cast a spell that destroys your creature, but you can react with one that makes your creature indestructible. From there, the stack resolves the last thing added to it: Indestructibility, and then resolves the destruction; which fizzles due to the creature being rendered indestructible first. Magic even has cards that change the stack into a queue, meaning spells resolve first-in, first-out, making interaction very difficult.
In Yugioh, quickspells and trap cards are allowed to be cast on the stack, along with monster effects appended with (Quick Effect) in their conditions. Magic only allows Instant spells and creature abilities to be cast on the stack, but has the Flash keyword, which allows any spell to be cast as if it were an instant. In my game, I’ve clearly delineated that only chain linkers and breakers can be added to a stack (called a chain in my game, hence the title Charmed Chains); linkers only on your turn and breakers during anyone’s turn. This way I have an icon that clearly indicates when something is a (quick effect), saving text, and standardizing notation for less confusion (some older Yugioh cards say, “once per turn, during anyone’s turn”, which has the same meaning as quick effect).
Many card games avoid this kind of thing by outright forbidding interaction during the opponent’s turn, or only allowing you to set up effects that are triggered during specific circumstances during the opponent’s turn. Magic the Gathering allows you to target other spells in the current stack with counterspells, where Yugioh effects either require you to specify a target in a specific zone, or have a condition mandating they must be in response to a specific event or effect, thus not targeting at all. I’m divided on what approach I want to take for my game. Magic’s is certainly more flexible and easily understandable, and Yugioh’s more reliant on sequencing, which is a skill component, and not a particularly unintuitive one either.
I have also certainly joked about effect resolution through a hashtable, binary search tree, or similarly obtuse data structure, but I’m not crazy enough to actually try and do that, and it’s absolutely outside the design goals for this particular game.
These types of interactions are common across all sorts of games, especially board games, but rarely do games need to create interoperability and extendability in the way that trading card games do. Since trading card games need to account for future cards, they need to consider categories in much the same way that software architects need to consider future developers. A board game can have a special exception rule for a specific circumstance without too much incident, but a trading card game needs to consider the side effects of their design decisions into perpetuity.
This perpetuity constraint informs a style of design for card games that demands rigor from designers, which is a large part of why card games end up being so deep so frequently, in a way that only some other competitive genres reliably achieve, such as fighting games and RTS. You can compare this to my article on Kusoge and the worst possible fighting game. Card games have such a solid foundation set forth, that it is almost impossible to create a truly bad one as long as you follow their design trends through to their logical ends.