Making a Propp-inspired story generator in Tracery

By Allison Parrish

This tutorial assumes you know the basics of Tracery. In particular, I assume you've already gone through this introductory material.

In this tutorial, I want to show how to use Tracery's action syntax, which allows you to add new rules to the grammar "on-the-fly" during the expansion process.

The examples in this tutorial are static (i.e., you can't change them, or re-generate from the grammar). Feel free to copy/paste the examples into a tool like Tracery Writer if you want to make modifications!

Creating new rules while expanding the grammar

The basic syntax for an action looks like this:

#[new_rule:value]existing_rule#

Where new_rule is the name of the rule you want to define, and value is the expansion you want to assign to that rule. In this example, Tracery will expand existing_rule, but in the process of that expansion, the rule new_rule will expand to value.

Here's an example:

In [27]:
{
    "origin": "#[name:Allison]greeting#",
    "greeting": "Hi, my name is #name#!"
}
Hi, my name is Allison!

Note that there's no top-level rule name—it's created by the action in the origin rule.

One of the main benefits of the action syntax is that you can use the same rule more than once and have the same result from the expansion both times:

In [28]:
{
    "origin": "#[name:Allison]greeting#",
    "greeting": "Hi, my name is #name# and #name# is my name!"
}
Hi, my name is Allison and Allison is my name!

This happens even when there are multiple possibilities for expansion:

In [3]:
{
    "origin": "#[name:Allison]quotation#",
    "quotation": "'#greeting#', said #name#.",
    "greeting": ["Hi, my name is #name# and #name# is my name!", "I'm #name#, who are you?"]
}
'I'm Allison, who are you?', said Allison.

To create more than one new rule, put two actions next to each other:

In [4]:
{
    "origin": "#[name:Allison][adverb:cheerfully]quotation#",
    "quotation": "'#greeting#', said #name# #adverb#.",
    "greeting": ["Hi, my name is #name# and #name# is my name!", "I'm #name#, who are you?"]
}
'I'm Allison, who are you?', said Allison cheerfully.

Actions themselves can use a rule expansion as their value. This is actually where actions start to get really powerful! You can select a random expansion from a list, and then use that same expansion in multiple places in the rest of the grammar.

In [11]:
{
    "origin": "#[name:#names#][adverb:#adverbs#]quotation#",
    "quotation": "'#greeting#', said #name# #adverb#.",
    "greeting": ["Hi, my name is #name# and #name# is my name!", "I'm #name#, who are you?"],
    "names": ["Allison", "Bethany", "Caroline", "Dolores", "Esther"],
    "adverbs": ["cheerfully", "quietly", "yesterday"]
}
'Hi, my name is Allison and Allison is my name!', said Allison yesterday.

Compare this to just using normal expansion syntax. You don't get a consistent value, since Tracery selects randomly each time:

In [20]:
{
    "origin": "#quotation#",
    "quotation": "'#greeting#', said #names# #adverbs#.",
    "greeting": ["Hi, my name is #names# and #names# is my name!", "I'm #names#, who are you?"],
    "names": ["Allison", "Bethany", "Caroline", "Dolores", "Esther"],
    "adverbs": ["cheerfully", "quietly", "yesterday"]
}
'Hi, my name is Allison and Bethany is my name!', said Caroline quietly.

To summarize, the action syntax allows you to "store" the result of an expansion for later reference.

Often when you're writing a Tracery grammar, you want to select expansions at random, but also have the expansions for certain other rules to be consistent or related in some way to that expansion. This can be accomplished using action syntax. If you write:

#[#rule1#]rule2#

... then Tracery will look for further actions in the expansion of rule2. In that expansion, you can have a list of altenatives that define actions, potentially multiple actions in each alternative. For example, in the following grammar, the setColor rule expands randomly to one of the items in the list. Each of these items defines two actions that set rules simultaneously for color and feeling, which makes it possible to have random outputs where the values of two (or more) rules are nonetheless related:

In [24]:
{
    "origin": "#[#setColor#]sentence#",
    "setColor": [
        "[color:red][feeling:warm]",
        "[color:blue][feeling:cool]",
        "[color:green][feeling:cheerful]",
        "[color:grey][feeling:boring]",
        "[color:burnt umber][feeling:autumnal]",
        "[color:magenta][feeling:weird]"
    ],
    "sentence": "My favorite color is #color# because it's so #feeling#."
}
My favorite color is burnt umber because it's so autumnal.

A simple story

A story often contains one or more characters, objects, or settings that remain consistent from one part of the story to the next. As such, Tracery actions are very handy when trying to write grammars that generate story-like artifacts.

The following grammar writes a very simple romantic comedy starring two animals. In the origin rule, two characters are selected, along with their species. The story rule defines a simple structure for the story: a series of events, consisting of a preface (Once upon a time...), the meeting of the two characters, a conflict between the characters, their eventual reconciliation and romantic overtures, and a happy ending.

In [7]:
{
    "origin": "#[nameA:#names#][speciesA:#animals#][nameB:#names#][speciesB:#animals#]story#",
    "story": "#once#\n#meet#\n#conflict#\n#love#\n#end#",
    "once": "Once upon a time, there was #speciesA.a# named #nameA#.",
    "meet": [
        "One day, #nameA# accidentally spilled coffee on #speciesB.a# named #nameB#.",
        "One day, #nameA# took the last cookie from the cookie jar of #speciesB.a# named #nameB#.",
        "One day, #nameA# cut in line at the bank in front of #speciesB.a# named #nameB#."
    ],
    "conflict": "'Why'd you do that?' said #nameB#. 'I don't like you.' But #nameB# couldn't help but smile.",
    "love": "Weeks later, #nameA# and #nameB# ran into each other again. 'I'm sorry about what happened the other day,' said #nameA#.",
    "end": "The two ended up going on a date and falling in love. The end!",
    "names": ["Alex", "Cameron", "Casey", "Elliott", "Jesse", "Logan", "Rory", "Toby"],
    "animals": ["aardvark", "alpaca", "capybara", "chipmunk", "hedgehog", "koala", "marmoset", "otter", "raccoon", "sloth"]
}
Once upon a time, there was an alpaca named Rory.
One day, Rory accidentally spilled coffee on a hedgehog named Casey.
'Why'd you do that?' said Casey. 'I don't like you.' But Casey couldn't help but smile.
Weeks later, Rory and Casey ran into each other again. 'I'm sorry about what happened the other day,' said Rory.
The two ended up going on a date and falling in love. The end!

Exercise: The meet rule already has multiple options for how it plays out. Add more options to each of the other story event rules (once, conflict, love, end).

Exercise 2: Stories belonging to the same genre often have the same general outline: there is a set of events considered obligatory for that genre. But one story can be distinguished from another based on the events it contains that are not obligatory. Abbott uses the terms "constituent event" and "supplementary event" to talk about these kinds of events, respectively. Add one or more "supplementary" events to your story—an event that sometimes happens when the grammar is expanded, but not always, and doesn't otherwise affect the flow of the story.

A more advanced story

There are entire fields of study concerning the structure of narratives. Scholars in literary theory, anthropology, sociology, linguistics and folkloristics analyze large numbers of stories (in the form of novels, films, transcribed speech, etc.) to determine what makes them work. Some scholars use narrative as a way to understand the human mind and culture at large: what does the structure of stories tell us about who we are? Other scholars are concerned with identifying differences between kinds of stories. What are the constituent elements that define a science-fiction story, or a tragedy? Collectively these lines of research are often grouped under the heading of narratology. (Artists and writers are often concerned with the related challenge of creating stories that achieve particular rhetorical or aesthetic ends, and their research and practice often overlaps with narratology.)

An important figure in narratology is Vladimir Propp, a Soviet scholar who studied Russian folktales in particular. In his book The Morphology of the Folktale describes a framework that he claims accounts for the structure of a large number of Russian folktales. The framework defines a sequence of thirty-one "functions," or events, that comprise all of these folktales. Some functions may be absent from any individual story, but when they do occur, they always occur in order. Propp additionally proposes seven "character functions," or character categories, into which any character in these folktales falls. Propp's Wikipedia page has a good list of the narrative structure functions and the character functions..

For reference, the 31 functions are: Absentation, Interdiction, Violation of interdiction, Reconnaissance, Delivery, Trickery, Complicity, Villainy or lacking, Mediation, Beginning counteraction, Departure, First function of the donor, Hero's reaction, Receipt of a magical agent, Guidance, Struggle, Branding, Victory, Liquidation, Return, Pursuit, Rescue, Unrecognized arrival, Unfounded claims, Difficult task, Solution, Recognition, Exposure, Transfiguration, Punishment, and Wedding.

Many computational creative scholars have been inspired by Propp's framework and some have even treated it as a kind of grammar for generating stories. (See Gervás for a good historical overview of computational frameworks inspired by Propp, along with Gervás' own ingenious system for using the functions as a grammar directly.) We won't approach the rigorousness of those approaches, but we can still use Propp as a starting point for story generation.

The following grammar defines a few items from a folktale storyworld: hero, a love interest, a "dispatcher" (the person who initiates the hero's quest), a quest (with destination), a "donor" (the character that gives the hero a magical item to aid in their quest) and a villain (with a corresponding challenge to set for the hero). The grammar implements eleven of Propp's 31 functions, along with textual variants for each. For a handful of functions, there is an "empty" variant, since that function can be considered "supplementary" to the story.

In [26]:
{
    "origin": "#[#setHero#][#setLove#][#setQuest#][#setDonor#][#setVillain#][#setReasons#]story#",
    "setHero": [
        "[hero:Garfield][heroDesc:a large cat][conveyance:magic lasagna pan]",
        "[hero:Hermione Granger][heroDesc:a famous Gryffindor][conveyance:broomstick]",
        "[hero:some guy][heroDesc:a guy I know][conveyance:rusty Toyota Corolla]"
    ],
    "setLove": [
        "[loveInterest:the princess][dispatcher:the king][gift:splendid silver crown]",
        "[loveInterest:Reginald][dispatcher:Reginald's cousin][gift:delicious pie]",
        "[loveInterest:Sonic the Hedgehog][dispatcher:Dr. Robotnik][gift:bagful of golden rings]"
    ],
    "setQuest": [
        "[questObject:magic ring][destination:Mordor]",
        "[questObject:ear of corn][destination:Nebraska]",
        "[questObject:academic transcript][destination:the NYU Registrar's Office]"
    ],
    "setDonor": [
        "[donor:sickly bird][donorHelp:gives the #donor# a healing salve][magicItem:single glistening feather]",
        "[donor:thirsty talking flower][donorHelp:waters the #donor# with a nearby watering can][magicItem:glowing pink petal]",
        "[donor:hungry fox][donorHelp:feeds the #donor# some beef jerky][magicItem:shiny pendant]"
    ],
    "setVillain": [
        "[villain:an old man][challenge:dancing contest]",
        "[villain:an evil witch][challenge:magic duel]",
        "[villain:a malevolent cloud of glowing fog][challenge:philosophical debate about the nature of existence]"
    ],
    "setReasons": [
        "[needReason:an illness][solutionAction:heals]",
        "[needReason:great poverty][solutionAction:enriches]",
        "[needReason:an ancient Egyptian curse][solutionAction:lifts the curse from]"
    ],
    "story": [
        "Once upon a time...\n#lacking#\n#mediation#\n#donorFunction#\n#receipt#\n#guidance#\n#struggle#\n#victory#\n#return#\n#solution#\n#transfiguration#\n#wedding#\nThe end."
    ],
    "lacking": [
        "Because of #needReason#, #loveInterest# needs #questObject.a# from distant #destination#.",
        "'I am stricken with #needReason#,' says #loveInterest#. 'I need #questObject.a# to lift this burden.'"
    ],
    "mediation": [
        "#dispatcher.capitalize# sends #hero# (#heroDesc#) to #destination# in order to find the #questObject# for #loveInterest#.",
        "'Who is brave enough to go to #destination# to fetch the mystical #questObject#?' asked #dispatcher#. 'I am,' replied #hero#, #heroDesc#."],
    "donorFunction": [
        "Along the way to #destination#, #hero# encounters #donor.a#, who asks #hero# for help.",
        "On the road to #destination#, #hero# hears a voice. #hero.capitalize# looks down and sees #donor.a#. 'Please, help me,' says the #donor#."
    ],
    "receipt": [
        "#hero.capitalize# #donorHelp#. In gratitude, the #donor# gives #hero# #magicItem.a#.",
        "'I will help,' replies #hero#, who then #donorHelp#. 'Thank you,' replies the #donor#. 'As a token of gratitude, have this #magicItem#.'"
    ],
    "guidance": ["", 
                 "#hero.capitalize# is taken to #destination# by means of #conveyance.a#."
    ],
    "struggle": [
        "Upon arrival in #destination#, #hero# encounters #villain#, who challenges #hero# to #challenge#.",
        "#hero.capitalize# arrives at #destination# and hears a voice nearby. 'At last we meet,' says #villain#. 'If you want the #questObject#, you must first defeat me in #challenge.a#.'"
    ],
    "victory": [
        "#hero.capitalize# prevails over #villain# by use of the #magicItem# and receives the #questObject#.",
        "'I accept your challenge,' says #hero#. Using the #magicItem#, #hero# easily defeats #villain#."
    ],
    "return": [
        "#hero.capitalize# makes their way home with the #questObject#.",
        "'At last, I have obtained the #questObject#,' says #hero#. 'Now I will return home.'"
    ],
    "solution": [
        "#hero.capitalize# brings the #questObject# to #loveInterest#. The #questObject# #solutionAction# #loveInterest#.",
        "'My hero!' says #loveInterest# as #hero# hands over the #questObject#. The #questObject# #solutionAction# #loveInterest#."
    ],
    "transfiguration": ["", "As a reward, #dispatcher# gives #hero# #gift.a#."],
    "wedding": [
        "#hero.capitalize# and #loveInterest# are married and become monarchs over the land.",
        "'Will you marry me?' says #hero# to #loveInterest#. 'Yes,' replies #loveInterest#. And they lived happily ever after."
    ]
}
Once upon a time...
Because of an illness, Sonic the Hedgehog needs an ear of corn from distant Nebraska.
Dr. Robotnik sends Garfield (a large cat) to Nebraska in order to find the ear of corn for Sonic the Hedgehog.
On the road to Nebraska, Garfield hears a voice. Garfield looks down and sees a thirsty talking flower. 'Please, help me,' says the thirsty talking flower.
'I will help,' replies Garfield, who then waters the thirsty talking flower with a nearby watering can. 'Thank you,' replies the thirsty talking flower. 'As a token of gratitude, have this glowing pink petal.'

Garfield arrives at Nebraska and hears a voice nearby. 'At last we meet,' says an old man. 'If you want the ear of corn, you must first defeat me in a dancing contest.'
Garfield prevails over an old man by use of the glowing pink petal and receives the ear of corn.
'At last, I have obtained the ear of corn,' says Garfield. 'Now I will return home.'
Garfield brings the ear of corn to Sonic the Hedgehog. The ear of corn heals Sonic the Hedgehog.

Garfield and Sonic the Hedgehog are married and become monarchs over the land.
The end.

References

Abbott, H. Porter. The Cambridge Introduction to Narrative. 2 edition, Cambridge University Press, 2008.

Gervás, Pablo. “Propp’s Morphology of the Folk Tale as a Grammar for Generation.” OASIcs-OpenAccess Series in Informatics, vol. 32, Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik, 2013.

Propp, Vladimir. Morphology of the Folktale. University of Texas Press, 2010.