Procedural Generation in Ink: Part 2: Encounters and Tables

Procedural Generation in Ink

Ink is a narrative scripting language for creating interactive fiction and other vast, branching digital projects.


Encounters and Tables

In the previous part, random entries from lists were used to build a story using parts. This same technique was used to then build an example plot arc using before, after, and closure actions between two parties.

While additional LISTs could be used to build more complex projects, there exists a better solution in Ink: functions.

Functions act like knots in Ink, but can also return a value. Instead of keeping all of the possible values in a LIST and then setting a variable’s values, a function can serve as a way to get a new random entry from an alternative multiple times.



{getPronoun()} had {getHair()} hair in a {getHairStyle()} look.

=== function getHair() ===
 ~ return "{~black|blond|brown|gray|red|white}"

=== function getHairStyle() ===
 ~ return "{~high-and-tight|fade|buzz cut|pixie cut|layered cut}"
 
=== function getPronoun() ===
 ~ return "{~He|She|They}"
 

Using functions in Ink also allows for abstracting out the “getting values” part of the calculations. Any of the actions that were performed on the LIST or VAR values can become part of the functions themselves.

Encounter System

While the table-top role-playing game D&D may not seem like a procedural generation system, many of its mechanics actually are! Random encounters (an element carried over into digital games) can be mirrored as part of an encounter system where different possibilities are acted on based on a dice (random) roll.



LIST animals = (crab), (seagull), (shark)

You encounter a {getRandomListEntry(animals)}!

=== function getRandomListEntry(list) ===
~ temp length = LIST_COUNT(list)
~ temp randomNumber = RANDOM(1, length)
~ return LIST_RANGE(list, randomNumber, randomNumber)
 

Through using a LIST and the use of the getRandomListEntry() function, a random entry from its contents can be retrieved.

Tables

In a role-playing setting, a table, as a very loose term, often refers to a collection of values that happen if a certain dice number is hit.

In Ink, this can be replicated through using different VAR and LIST values. Using the above two approaches, shuffling the alternatives for a VAR or getting a random LIST value, a new random entry can be retrieved.

Note: While LIST can be very useful, it is also limited to single words. For multiple words or longer use of letters and numbers, the shuffled alternative approach is usually better.

These can also be combined together through functions. Instead of needing to write the same code multiple times, it can be written once and called multiple times. Its return value can also be saved like any other variable.


LIST animals = (crab), (seagull), (shark)
VAR pronoun = ""
~ pronoun = getPronoun()

{pronoun} had {getHair()} hair in a {getHairStyle()} look.

{pronoun} encountered a {getRandomListEntry(animals)}!

=== function getHair() ===
 ~ return "{~black|blond|brown|gray|red|white}"

=== function getHairStyle() ===
 ~ return "{~high-and-tight|fade|buzz cut|pixie cut|layered cut}"
 
=== function getPronoun() ===
 ~ return "{~He|She|They}"

=== function getRandomListEntry(list) ===
~ temp length = LIST_COUNT(list)
~ temp randomNumber = RANDOM(1, length)
~ return LIST_RANGE(list, randomNumber, randomNumber)