Games with Tracery

NaNoGenMo, tracery, 2019

procedural-generation:

galaxykate0:

Someone online asked if there was a guide to integrating Tracery with games, so I wrote one.

Tracery is named for the architectural term “tracery”, the curly filigree part of gothic cathedrals. Tracery doesn’t hold up the cathedrals: it’s decorative not structural. If you find yourself trying to do very complex data storage and conditionals with Tracery, you might be trying to build a cathedral with filigree. It is best to use your game code (javascript or Unity, or whatever else you use) itself to perform complex tasks like these. Tracery is best for adding decoration afterwards. But there are some good techniques for adding Tracery to games that I’ve encountered.

Common uses of Tracery

Games often have abstract rule systems at their core (see Joris Dormans work on modelling games abstractly http://www.jorisdormans.nl/machinations/). But even for games with identical rule systems, content can create flavor and feelings that go far beyond the meaning of rule systems. Ladykiller in a Bind and Hatoful Boyfriend may have very similar mechanical systems driving them, but what wonderfully different experiences we get from their unique content! From flavortext on Magic: the Gathering cards to story arcs and dialogues of dating sims, or the sprawling poetry of Twine games, content can serve many purposes in a game.

Tracery, and other grammar-based templating languages, are already popular in games to create new content. Dietrich Squinkifer uses it in Interruption Junction for an endless stream of dialogue and in Mr. Darcy’s Dance Challenge uses it for endless insults from Mr. Darcy. Pippin Barr uses it to generate thoughtful frowns and headscratches in It is as if you were playing chess.

Beyond Tracery, there are other templating languages, and many game developers have built their own. Zach Johnson, the creator of Kingdom of Loathing invented a templating language to create game content like combat text and hobo-names (https://youtu.e/X3sqkxedSHQ?t=4m6s). Even the original 1966 ELIZA chatbot used templating in its dialogue generator.

Basic Tracery content in a game

These basic content creation tasks are easy for Tracery! Create a grammar “rpgGrammar” (or several, like “weaponGrammar”, “innNameGrammar” etc if you don’t want to share content between grammars) with your writing. Then call

rpgGrammar.flatten("#innName#") or rpgGrammar.flatten("#NPCName#") or rpgGrammar.flatten("#armorDescription#") or rpgGrammar.flatten("#combatSound#")

to generate whatver content you’ve authored.

Generating parseable data

You may find that you want to generate more complex stuff with a single query, such as generating a sword name and a related description like “General Greenblat’s Blade” “a sword found by General Greenblat while searching for her lost puppy”. In that case, you might have a grammar like

"swordWord": ["blade", "edge", "sword"],

"bowWord": ["aim", "bow", "longbow"],

// This picks out whether we are generating a bow or a sword "setWeaponType": ["[weaponClass:sword][weaponNameType:#swordWord#]","[weaponClass:bow][weaponNameType:#bowWord#]"]

"generateWeaponData": "[character:#name#]#setWeaponType##weaponType# | #character#'s #weaponNameType#" | #character# found this #weaponType# when #doingSomeTask#"]

Expanding “#generateWeaponData#” would generate some data separated by “|” symbols, which you could then split apart with Javascript and use separately in your game.

Generating tagged data

I’ve been working on a hipster chef game, HipChef (for waaayyyy too long). It’s been an exercise in figuring out good tagging practices for using Tracery text in a game while also getting meaning out of that text.

For example, here is a sample of my grammar for generating recipes:

largeFruit : ["kumquat<citrus>", "honeydew<melon>", "bittermelon<melon>", "cherimoya", "peach", "sugar apple", "persimmon", "green apple", "jackfruit", "damson plum", "kiwi", "lime<citrus>", "key lime<citrus>", "meyer lemon<citrus>", "pomegranate", "green apple", "pineapple", "mandarin orange<citrus>", "blood orange<citrus>", "plum", "bosque pear", "fig", "persimmon", "durian", "mango", "lychee"],

preparedMeat : ["duck fat<fowl><game>", "roast duck<fowl><game>", "crispy bacon<pork>", "pancetta<pork>", "salami<pork>", "prosciutto<pork>", "corned beef", "pastrami<beef>", "roast game hen<fowl>", "seared ahi<fish>"],

herb : ["fennel", "cilantro", "mint", "basil", "thyme", "Thai basil", "oregano", "peppermint", "spearmint", "rosemary"],

spice : ["vanilla", "nutmeg", "allspice", "turmeric", "cardamom", "saffron", "cinnamon", "chili powder", "cayenne", "coriander", "black pepper", "white pepper", "ginger", "za’atar"],

"artisanToast": "#bread# with #spice#-spiced #largeFruit# and #meat#"

This might generate some fancy toast descriptions, but in the game, I want to know the game-significant ingredients of this toast. If it has pork and fennel, which are trendy at the moment it scores higher, but if it has duck and melon, which are not, the score is lower. I can search for some ingredients, like “pineapple” by name, but others, like “mint” might be ambiguous. Other queries, like “fowl” or “herb” would need to match many rules.

The fastest way to do this, for me, is to hand-embed these tags inside the content, like kumquat<citrus>. For some content, like herbs and spices, I want to tag all the rules with a single tag. That sounded like work, so I wrote a bit of utility code function autotag(grammar, key, tags) which automatically appends the given tags to all the rules for that key.

Now when the toast generates, it outputs a string like “Ciabatta with turmeric -sprinkled honeydew and roast duck ”. I can strip these tags out with JavaScript, and get and array “spice,melon,fowl,game” (which the game’s rules can use) and a string “Ciabatta with turmeric-sprinkled honeydew and rost duck” which I can display to the player.

You can generate any structure of data this way, even JSON (which you can then use JS’s JSON parser to unpack automatically). In fact, the SVG graphics made with Tracery are an example of this: Tracery generates specially structured text, which a web-page can interpret as image-making commands. But SVG and JSON parsers are just two ways to computationally parse text, you can write your own, as I did with HipChef.

Using world state in Tracery

Your game almost certainly has some world state. For an RPG, this might include the player’s occupation and race, their weapon, their health, a list of skills. Like many games, you might also have a custom name for the player. To use the name in Tracery, you can edit the raw grammar before you use it in Tracery or you can edit the grammar on the fly by pushing new rules to the grammar. This is what Tracery does when you use “[myName:#name#]” in a grammar, but now you’re doing it whenever you want, with whatever data you want.

mygrammar.pushRules(playerName, ["Bobo the Love Clown"]);

mygrammar.pushRules(playerHometown, ["Scranton, NJ"]);

mygrammar.expand("#playerName# left #playerHometown# on an adventure");

A Note: the newest in-progress version of Tracery allows you to pass a world-object to Tracery along with a grammar, so you no longer have to manually update “playerMood”, etc, each time the player’s mood changes and you want to use it in a piece of generated text. But I don’t have an ETA for that.

Seeds: turning commodities into individuals

You’d often want to generate the same content many times in a game. For example, in a text-version of a space game that can generate trillions of planets for you to visit (cough) you might not want to save all the generated tree descriptions, plant descriptions, alien city names, etc. But, if you use some fixed number to set the random seed, you can be certain that Tracery will make the same sequence of “random” choices when picking rules. This will generate the same content, as long as you ask for the content in the same order once you set the seed. For Javascript, I use David Bau’s excellent fix. Conveniently, this requires no changes to Tracery, it just modifies JS’s random number generator.

This is especially fun if you have some huge number represting an in-game commodity, like the population of your city. You can use the index as your seed: “look at citizen #31992” will set the seed to “31992” and each time, the citizen will be “Margarie Tomlinson, age 45, afraid of spiders”.

Further

This may not be as much as your game needs. You may want internal conditionals controlling the grammar’s expansion, or more direct tagging control, such as “give me a conversation tagged ‘aggressive’ and ‘evasive’”. James Ryan’s Expressionist work can do tag-directed generation management to satisfy constraints, and I’ve heard Emily Short is working on something Tracery-like with tags.

I’m also working to include tags and conditionals in the new Tracery, but we’ll see when that ships. Until then, you may get mileage out of the techniques above.

Using Tracery In Larger Systems

Well, this write-up would have come in handy for NaNoGenMo!

I used some variants of a couple of these for my NaNoGenMo project. For my island description generator, I had rules with tags like:

“<+feature do_not_repeat></+><+feature size=small></+>There were two islands there. The distance from one to the other was about one mile. The small island <feature cliffs>rose very abruptly</+> many hundred feet above the sea. At the top was <+feature landmark>a rock with a conical form, which eternally seems on the point of rolling down with a tremendous crash into the sea</+>. The other island was larger, if less remarkable.”

and

“The #inhabitants# use #a_kind_of# <+feature condiment>#condiment#</+> in their cooking.“ 

This produced descriptions like:

They saw The Blue Violet Isle of Eurynome directly ahead, rising like a deep blue cloud out of the sea.

It is a very flat place, made up of several low-lying coral atolls.

The pirates were eager to hunt the mole, which they had great expectations for. Whenever they visit this island, sailors will conduct a kind of ritual, which they claim symbolizes deceit. Around the principle harbor, there were a great many papercrete buildings, forming a small town.

The cuisine of that island is known for something that resembles fresh dijon ketchup.

And the text generator had an additional constraint of only allowing new sentences to be added if they matched the already chosen tags. (Some were complementary, while others were mutually exclusive.) The tags surrounded bits of the text, which were added to the information about the island, so a landmark or kind of cuisine could be referred to by other generators.

Like the character description generator:

“Gull” Sao’s favorite food is fresh dijon ketchup from The Blue Violet Isle of Eurynome. She was dressed in a rusty black suit and wore seafoam green yarn stockings and shoes with brass buckles. She wore a red sash tied around her waist, and, as she pushed back her coat, you might glimpse the glitter of a pistol butt.

Having written this stuff once, I immediately see ways in which Kate’s suggestions above would have improved things. I look forward to other people finding new and better ways to apply Tracery to generating more things.