Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: procedural fairy tales  (Read 1107 times)

PopTart

  • Bay Watcher
    • View Profile
procedural fairy tales
« on: April 08, 2016, 05:40:16 pm »

From what I've heard, Zach and Tarn are busy now incorporating myth into Dwarf Fortress. This is a cool thing. I was wondering if they are familiar with Vladimir Propp's analysis of Russian folktales in his Morphology of the Folktale. In that book, he combs over (IIRC) hundreds of fairy tales and isolates 31 narrative elements.

Here they are:
Spoiler (click to show/hide)

Not all the elements occur in every story, but what's fascinating is that they don't occur out of order. Since Propp's time, other theorists have improved on the theory by showing how different elements are really just transformations of each other. For example, the hero begins a quest <-> the hero returns from the quest.

Pierre and Elli Maranda pick up on this formalist analysis in works such as "Semiography and artificial intelligence" (1985) and "Mother Culture is watching us: Probabilistic structuralism" (1992). From what I gather, they link up Propp's narrative elements probabilistically: Element A is followed by Element B1 by a probability of .205, or Element B3 by .117, or skip B and go to C1 by .235, etc. There's a set of probabilistic laws that govern tendencies in narrative progression. This concept would lend itself interestingly, I think, to procedurally generated stories. I wonder if anyone has tried it.

PopTart

  • Bay Watcher
    • View Profile
Re: procedural fairy tales
« Reply #1 on: April 08, 2016, 06:13:16 pm »

A more sophisticated generator would look at higher-order rules. So, it would have to construct a probability chart for every event from A up to x. For example, if the story starts out A1 -> B3 -> C2, then what's the probability of D1, D2, etc.? Not just looking at what follows C2 in isolation.

PopTart

  • Bay Watcher
    • View Profile
Re: procedural fairy tales
« Reply #2 on: April 08, 2016, 06:16:15 pm »

And, after setting this up, congrats, you can make an "authentic" Russian fairy tale. So how can it be generalized? Would dwarfy folktales and myths fall into some other, characteristically dwarfy paradigm?

Dirst

  • Bay Watcher
  • [EASILY_DISTRA
    • View Profile
Re: procedural fairy tales
« Reply #3 on: April 08, 2016, 07:12:41 pm »

A more sophisticated generator would look at higher-order rules. So, it would have to construct a probability chart for every event from A up to x. For example, if the story starts out A1 -> B3 -> C2, then what's the probability of D1, D2, etc.? Not just looking at what follows C2 in isolation.
If the transition probability is "memoryless" (that is, depends only on which element is currently under consideration) then it is called a Markov Chain.  Markov Chains have some nice mathematical properties, for example being represented by a simple matrix.  Given that these elements always occur in a fixed order, the matrix would be upper-triangular.

You would need Step 0 as a starting point, and its transition probabilities represent the starting point of the narrative.

Interestingly, you could associate these steps to entity traits to fiddle with the probabilities.  That way different cultures would have different emphasis to their fairytales.

The complicated bit will be procedurally generating the narrative at each step.  Although the probability of landing on 20 (Return) only depends on the previous step, what that entails depends on if there was an 11 (Departure) and where the main story took place.  A story without a Departure simply takes place soup-to-nuts in a single location.

An dorfy tale might skip from 16 right to 30... that is, the hero died achieving the objective.
Logged
Just got back, updating:
(0.42 & 0.43) The Earth Strikes Back! v2.15 - Pay attention...  It's a mine!  It's-a not yours!
(0.42 & 0.43) Appearance Tweaks v1.03 - Tease those hippies about their pointy ears.
(0.42 & 0.43) Accessibility Utility v1.04 - Console tools to navigate the map

PopTart

  • Bay Watcher
    • View Profile
Re: procedural fairy tales
« Reply #4 on: April 08, 2016, 08:09:58 pm »

An dorfy tale might skip from 16 right to 30... that is, the hero died achieving the objective.
Yes!!!

I love the idea of linking the probabilities to traits. So Urist McProtagonist will do what he is expected to do... Or he might surprise you with a twist!

Actually, I left out that now people do postulate a Step 0 in Propp's morphology: the story's initial conditions.

Yeah, if you link return and departure, you have constructed a "place" in which 11-20 happen. This concept of place would have to be part of the analysis, to see what tends to happen there. Other states could be constructed too besides place. (Strange?) moods or states of mind perhaps.

Maybe a narrative model that weights the probabilities toward more recent events (to make them more "Markov-like") would be good.

Dirst

  • Bay Watcher
  • [EASILY_DISTRA
    • View Profile
Re: procedural fairy tales
« Reply #5 on: May 13, 2016, 12:05:39 am »

Mild necro...

Was fiddling around and wrote some code for the transition matrix.  Right now it will pick a plausible set of "story steps" but there are no actual characters, locations or events.  A real version would weight the steps by cultural factors (e.g., a skulking civ would be much more likely to have a Reconnaissance step).

Code: (tale.lua) [Select]
-- Tale generator

local steps = {[0]="Start", "Absentation", "Interdiction", "Violation of interdiction", "Reconnaissance", "Delivery", "Trickery", "Complicity", "Villainy and lack", "Mediation", "Counteraction", "Departure", "Testing", "Reaction", "Acquisition", "Guidance", "Struggle", "Branding", "Victory", "Resolution", "Return", "Pursuit", "Rescue", "Arrival", "Claim", "Task", "Solution", "Recognition", "Exposure", "Transfiguration", "Punishment", "Wedding", "End"}

--[[
The transition matrix has a default probability of 25% for going to the next
step, 20% for skipping one step, then continuing with 15%, 10%, 5%, 5%, 4%,
4%, 3%, 3%, 2%, 2%, 1% and 1%.  This is modified by forcing the tale to take
at least one step in Phase 1, at least one step in Phase 2, and at least one
step in Phase 3.  Probabilities "too far ahead" are added again from the
first valid target step.

Phase 4 is made optional by halving the probability of landing on each step,
with the leftovers added directly to step 32 "End".
]]

local transitionMatrix = {[0]={0.29, 0.23, 0.18, 0.12, 0.07, 0.06, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.27, 0.22, 0.16, 0.11, 0.05, 0.05, 0.04, 0.04, 0.03, 0.03, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.28, 0.22, 0.17, 0.11, 0.06, 0.05, 0.04, 0.04, 0.03, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.28, 0.23, 0.17, 0.12, 0.06, 0.06, 0.04, 0.04, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.29, 0.23, 0.18, 0.12, 0.07, 0.06, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.30, 0.25, 0.18, 0.13, 0.07, 0.07, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.32, 0.26, 0.20, 0.14, 0.08, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.34, 0.29, 0.21, 0.16, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.27, 0.21, 0.16, 0.10, 0.05, 0.05, 0.04, 0.04, 0.03, 0.03, 0.02, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.27, 0.22, 0.16, 0.11, 0.05, 0.05, 0.04, 0.04, 0.03, 0.03, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.28, 0.22, 0.17, 0.11, 0.06, 0.05, 0.04, 0.04, 0.03, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.28, 0.23, 0.17, 0.12, 0.06, 0.06, 0.04, 0.04, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.15, 0.10, 0.05, 0.05, 0.04, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.08},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.15, 0.10, 0.05, 0.05, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.10},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.15, 0.10, 0.05, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.13},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.15, 0.10, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.15},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.15, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.20},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.20, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.27},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.25, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.37},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.50},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.51},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.52},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.02, 0.53},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.02, 0.55},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.02, 0.57},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.02, 0.59},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.03, 0.61},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.05, 0.64},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.08, 0.69},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.10, 0.77},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.13, 0.87},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 1.00},
                              {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 1.00}}

local rng = dfhack.random.new()
local currentStep = 0

while currentStep < 32 do
local jump = rng:drandom()
local nextStep = currentStep
while jump > 0 do
nextStep = nextStep + 1
jump = jump - transitionMatrix[currentStep][nextStep]
end
currentStep = nextStep
print(steps[currentStep])
end

Just put tale.lua in your hack/scripts folder and generate skeletal story-lines.
Logged
Just got back, updating:
(0.42 & 0.43) The Earth Strikes Back! v2.15 - Pay attention...  It's a mine!  It's-a not yours!
(0.42 & 0.43) Appearance Tweaks v1.03 - Tease those hippies about their pointy ears.
(0.42 & 0.43) Accessibility Utility v1.04 - Console tools to navigate the map

PopTart

  • Bay Watcher
    • View Profile
Re: procedural fairy tales
« Reply #6 on: May 13, 2016, 12:30:26 am »

Sweet. I ran tale.lua a couple times. My favorite story went:

Absentation
Reconnaissance
Trickery
Villainy and lack
Counteraction
Branding
Resolution
Rescue
End


I could really feel the singe of that branding, haha. Interestingly, not every absentation results in a rescue, and not every villainy results in a resolution. One tale ending had a villainy that led to counteraction and acquisition, and then it was over. The hero chose the positive action, acquired the magical item, and that was it. I'm not sure ol' Propp would consider this a "legal" plot, but it seems super dwarfy.

All the stories I generated felt about right. Maybe the probabilities should be lower for skipping. I'm getting about 7-10 steps out of 31. Is that normal?

Next: Could the hero possibly acquire an artifact from my artifact list? Could a noble personage from a worldgen get absentated?
« Last Edit: May 13, 2016, 01:19:40 am by PopTart »
Logged

Dirst

  • Bay Watcher
  • [EASILY_DISTRA
    • View Profile
Re: procedural fairy tales
« Reply #7 on: May 13, 2016, 12:39:16 am »

Interestingly, not every absentation results in a rescue, and not every villainy results in a resolution.
This is a byproduct of the "memoryless" transition matrix.  I suppose certain steps could hike a certain later step to certainty (probability 1).  The numbers in a row don't actually have to sum to 1, they just need to sum to at least 1 for the code to work.
Next: Could the hero possibly acquire an artifact from my artifact list? Could a noble personage from a worldgen get absentated?
That's certainly possible, but a bit beyond what I could whip up without a major time investment.

As for being too skippy, I don't have any real knowledge of Russian folklore to know what the right number of steps would be.  Can certainly pile the weight toward fewer skips.  That will lead to longer but perhaps slightly predictable story-lines.
Logged
Just got back, updating:
(0.42 & 0.43) The Earth Strikes Back! v2.15 - Pay attention...  It's a mine!  It's-a not yours!
(0.42 & 0.43) Appearance Tweaks v1.03 - Tease those hippies about their pointy ears.
(0.42 & 0.43) Accessibility Utility v1.04 - Console tools to navigate the map

IndigoFenix

  • Bay Watcher
  • All things die, but nothing dies forever.
    • View Profile
    • Boundworlds: A Browser-Based Multiverse Creation and Exploration Game
Re: procedural fairy tales
« Reply #8 on: May 13, 2016, 11:02:58 am »

I once tried to put together a program for generating plotlines using the Hero's Journey as a template, with each scene using a similar system of rising and falling tension.

It's...harder than it looks.  Even if it does look pretty hard.  Basically the hardest part is that you have to figure out a way of reusing earlier elements for various possible purposes in later scenes, Cheklov's Gun style, or it just winds up with a whole disjointed feel.  The complexity becomes overwhelming about three 'layers' down.

Of course, if it's anything like the song generation, they don't have to be detailed epics that are actually written, just described.  That will make things a LOT simpler.  Or maybe they'll just be a bunch of basic templates with plot elements replaced by in-game characters, artifacts, etc.