Cards are for Storytelling
This essay is deprecated. It’s worth reading through for context, but it has been superseded for now by “Ink for show control”. More details on this approach are also found in “Cards: A technical breakdown”.
Table of Contents
I’m not sure when the first card was invented, but people have been using cards and card-like tools for millenia to record history, tell stories, and organize information. Playing cards make for fun magic tricks or as a way to pass time with friends. Tarot cards are like a more complex “magic eight ball”, where people project themselves into archetypal images and read out potential insights.1
I believe that the thread through all of these examples is that a deck of cards is a powerful tool for storytelling. This essay explores how a deck of cards metaphor can model non-linear storytelling primitives in powerful ways. With a few simple rules, we can build emergent gameplay around a table, or on a bridge simulator. I believe the metaphor is flexible enough to model a wide range of storytelling scenarios, making it pluggable into single player games, a dynamic bridge simulator environment, or a VR multiplayer game.
Our metaphor will be built out of three primitives: A table, one or more decks of cards, and the cards themselves.
It starts with a table
Imagine we are going to run through a Space Center flight, but without a flight director or a simulator. We’ll just sit down at a table and talk our way through the story. This doesn’t sound much different than a game of Dungeons and Dragons, but instead of a GM we’re going to use a deck of cards.
In the simulation software, the “table” is just another part of the flight. It’s job is to keep track of narrative game state.
One special property of this game table is that any decks of cards can self-organize and self-shuffle, adding and removing cards from the deck instantly. Players drawing from the deck will always get a card that is possible at that moment. Possible cards are determined not only by mechanics (e.g. you can’t transport something if you don’t have transporters), but also by previously played cards (you can’t draw a card where a hostile alien asks you if you are going to stay and engage in combat or run away if you haven’t drawn the card where you meet that alien in the first place).
Which is really all that a computer storyteller is - a very fast card shark! If we’ve broken down the concepts in storytelling enough to where the only operations we need to perform are things computers are good at, like rapidly sorting and filtering records, then we might have a fully autonomous flight director on our hands. Really, all that our autonomous flight director needs to do is:
- Sort and filter cards based on current game state
- Simulate game mechanics (somethings video games have been doing since they were invented)
- Remember which cards have been played, in what order, how frequently, and all the timings between each card drawn
- Execute whatever actions are on each card played
This is in contrast to, say, expecting the software to infer human intent. I don’t expect that to be reasonable or perhaps even possible. This is why automated storytelling is not an exercise in designing a perfect piece of software, but rather an exercise in writing a profound amount of content to draw from that is always contextually relevant. The mechanics of dealing out the next piece of that content become just as magical as a movie playing at 30 frames per second. The miracle of motion pictures is synchronizing audio and video that plays at frame rates so fast, it creates the illusion of reality. This same miracle extends to things like virtual reality: the computer can draw faster than we can perceive, and so it transports us into an illusion that, when slowed down, appears rather mundane.
Preparing the table
Before we can start the story, we need to know what cards are possible in the first place. Players would choose the story deck they want to play in the same way that you choose which movie you want to watch. The machine handles loading the relevant data and getting ready to play from there.
Because we’re dealing with a virtual table with virtual cards, the cards can be copied, destroyed, exist multiple times in the same deck or different decks, and more. Creating or destroying cards is free, which also contributes to the illusion.
Later on, we’ll mention how we may start with more than one deck on the table. For now, let’s just suppose we have a single deck on the table: The story deck. The computer prepares for the game by looking through all of the cards in play and looking for cards whose preconditions have been met.
Cards, preconditions, choice conditions, and actions
If we were to look at one of these virtual cards, we’d see a few things:
- A unique name (so we can jump to this card by name from another card)
- A list of zero or more things that must be true in order for the card to be played (preconditions)
- A list of conditions that, if true, will activate the card when used as a choice (choice conditions)
- A list of zero or more actions to perform in order when the card is played2
Preconditions are the heart of making the whole thing work. Anything can be a precondition: did we just play card X? Do we have more than 30 minutes left to finish the story? Do we have a functioning transporter?
Some of the questions will be answered by checking game simulation state (e.g. in the entity component system, if we’re using that). Others will be answered by checking the state we’re keeping track of on the game table: Which cards have been played, how many times has a card been played, etc. More advanced state tracking for narratives could be incorporated, based on state of the art computer narrative techniques.3
When building a deck, we can look for cards whose preconditions have been met. If met, the card becomes playable. When drawn, the game executes each action on the card in order.
Actions generally modify game state in some way. It might tell the sound system to play a sound effect, or put a cinematic on the screen. In a bridge simulator, it could tell the lights to shake, put an alien ship in range on the sensors grid, or mess with power distribution levels.4
Starting a game
To begin, the players draw a card from the first deck on the table: the story deck. This deck is chosen by the players up front, not unlike choosing between different game decks off a shelf. We’ll eventually get to the idea of “expansion packs”, but for now just suppose that the only deck on the table has all the cards that are currently playable. At the start of a mission, if we want there to be only one entrypoint into the story, that might be just one card. Once that card is played, the deck is rebuilt with whatever the current preconditions might be, and we draw the next card.
At its core, that mechanic is actually enough to tell any branching story. Preconditions will gate access to different branches of story, and you can linearly traverse from card to card. If we have an action that jumps you back to a specific card, then we can create loops and cycles within a story. Build up enough of these loops, and you have a complex narrative simulation.
Choices
A card can have an action to add a choice to a temporary deck that players can then choose from. Each choice is a potential card in its own right with preconditions and actions, but there would also be choice conditions attached to these choice cards. When choice conditions evaluate to true on a single card, the chosen card would be played and the other potential choices discarded.
In this way, we might draw a card from the story deck, and that card might have a few nested choice cards inside of it. Those nested choice cards might have their own nested choice cards, and so on. This allows for a deep and complex branching structure within a single story or even a single card. To exit from that main story deck, one would either return to the main story deck (after running out of choices), or a card may run an action that jumps you to a specific, named card.
Multiple decks
With one story deck in play, it’s not hard to imagine throwing another deck into the mix that plays out a sub story happening elsewhere on a bridge simulator. Drawing from this deck would happen independently of the main story deck, allowing for parallel narratives or side quests.
Some game mechanics could rely on their own unique decks as well, such as a deck for handling damage control activity. Because these decks could weave in with other decks, you can create complex, emergent storylines that are unpredictable and even replayable along the way.
Extending a story
This gives rise to how we might extend a story. In a typical linear script, adding new content in requires splicing that content into the right place, and then reconsidering affected elements. If we have a simple scenario of standing on a beach, and you can choose between going into a dark cave or walking down the beach, those two choices could lead to different story branches.
Let’s suppose later on, we want to add another option, like hopping on a boat. The boat isn’t there in the original scenario, but if we have preconditions like “This card is playable when we are choosing where to go on the beach”, then we can start to construct an “expansion deck” of cards that can arbitrarily modify other existing stories.
One of the key insights here is replayability. As content authors dream up new possibilities for stories, they don’t have to figure out how to shoehorn those ideas into existing story structures. Instead, they can just create new cards, slap preconditions and choice conditions on them, and add them to the deck. Players can choose which expansion packs they want to play a game with, and the game can naturally fold those cards into the rest of the system.
Parallel lines of play
In a game like go fish, players take turns: Only one player draws from the deck at a time. Other games like Throw Throw Burrito are built around multiple parallel decks, with lots of concurrent activity. If we have multiple decks in play, however, we need a way to decide which deck ought to be drawn from at a given moment.
Because we rebuild each deck every time we draw a card (evaluating preconditions along the way for all possible cards), and because cards can be trivially copied as much as we like across decks, it doesn’t really make sense for draw from the same conceptual deck multiple times.
This means that for parallel play (e.g. having a main story and zero or more side stories), we can keep track of drawing from multiple decks at the same time.
This primitive is called a thread. A thread belongs to a deck. A table can have multiple concurrent threads. A thread’s job is to draw a card and execute it. Most of a thread’s time is spent waiting for actions to complete or conditions to be met (e.g. a story deck might start with a card that is about undocking from a space station, then the next card becomes available once you travel to a distant system. In between, the thread is waiting for that next card to become available.)
Epilogue
All of this was born out of my own efforts at trying to craft an Ink-based HyperTalk language that can basically do all of the above. Although I have a working prototype for parsing the language, it was becoming clear that there needs to be an easy way to extend the language or DSL in ways that would be quite tedious for programming anything, nevermind a bridge sim. I think the thing that really sunk it in was realizing that complex lighting configurations programmed in Ink would really stink. Ink is great at doing the deck management, the preconditions, the post-conditions, and even some of the actions, but then you get into specific game actions that need to happen, and it starts to get a lot more rough. Might as well just paste in ``` quoted blocks with JSON that can be pasted in from an editor or something, for the amount of extra work that would be involved in defining a parsing language and extensible grammar for everything involved.
However, it’s not hard to imagine how one might build and edit card definitions, action schemas, etc. as a set of plain files on disk that can be read, parsed, and built into a narrative engine that is both flexible and at the same time legible.
- ↩
For me, cards are quite personal. My first substantial programming experience came with HyperCard, a programming environment built on a metaphor of a stack of cards. Each card had buttons and fields that you could script to do interactive things. The first version of Myst was built using HyperCard, as was the first set of Space Center controls in the 1990s.
- ↩
Why zero? What good is a card that does nothing? If you recall from the bit about a Table, we track every card played: how many times it’s been played and how long it’s been since it was last seen. A card with zero actions will implicitly bump that card tracking. This allows other cards to then ask, “Have we seen this card before?” or “How long ago did we see this card?” which could shape preconditions and choice conditions.
- ↩
This example from Ink’s manual uses the
LIST
data structure to track what a player knows about a crime scene. This GDC talk from Jon Ingold (mentioned in the essay on Automating Resonance) explores how a mundane fetch quest can be spiced up with just two such linear state machineLIST
s, giving rise to multiple paths through what is otherwise a rather straightforward scenario involving a village, a peasant, a chicken, and a wolf. - ↩
Really, the card part in a bridge sim is that the number of verbs is extremely wide. So much state is in play at any given time, that in order to pick the piece of state you want to mess with, you have to get really specific, which implies that things are really verbose.
One of the important challenges of building a useful system out of these components is coming up with reusable patterns to effortlessly express what you want to read from or write to in preconditions or action steps.