Examples

plotknot ships with example stories in the examples/ directory. Each demonstrates specific features. All examples are used in the test suite — they compile, load, and run through the runtime.

New to plotknot? Start with the Game Designer Tutorial (no code) or the Programmer Tutorial (variables, conditions, game interop) before diving into these examples.

hello.plotknot

Demonstrates: sections, choices, conditional choices, variables, call, if/else, goto.

A simple forest branching story — the “hello world” of plotknot:

# The Forest

set courage = 5

You stand at the edge of a dark forest. The path ahead splits in two.

choice:
- Take the left path -> left_path
- Take the right path -> right_path
- Turn back -> turn_back [if courage < 3]

## Left Path

You follow the mossy trail deeper into the woods.

call check_weather() -> weather

if weather == "rain":
  Rain patters on the canopy above.
else:
  Sunlight filters through the leaves.

-> ending

## Right Path

The right path leads to a sunlit clearing. A deer watches you pass.

-> ending

## Turn Back

Courage fails you today. The forest can wait.

-> ending

## Ending

Your journey through the forest ends here.

Key concepts:

  • set initializes a variable
  • Conditional choice [if courage < 3] only appears when the condition passes
  • call invokes a game function and stores the result
  • if/else shows different text based on runtime state
  • -> navigates between sections

characters.plotknot

Demonstrates: character metadata, dialogue attribution.

A tavern scene with named speakers:

character: Mara
character: Aldric
character: Narrator

# The Tavern

Narrator: The tavern is warm and loud. Mara sits alone.

Mara: You look lost, traveler.
Aldric: Just passing through.
Mara: Nobody just passes through Thornwall.

choice:
- Ask about the road -> road_info
- Ask about the town -> town_info

Key concepts:

  • character: declarations register speakers
  • Name: prefix attributes dialogue lines
  • The runtime delivers speaker fields for UI binding

variation.plotknot

Demonstrates: variation blocks, cycle and once modes.

A crossroads that changes description on revisit:

# The Crossroads

variation: cycle

You arrive at a weathered crossroads.
The crossroads again. The signpost's paint is peeling.
You know this crossroads by heart now.

choice:
- Take the forest path -> forest
- Take the river path -> river
- Rest here -> rest

## Rest

variation: once

You sit on the milestone and catch your breath.

-> the_crossroads

Key concepts:

  • variation: cycle rotates through text lines on each visit
  • variation: once shows text only the first time
  • The runtime tracks visit counts per section

conditions.plotknot

Demonstrates: built-in functions, visited tracking, resource management.

A dungeon crawl with state-dependent content:

# Dungeon Entrance

set torches = 3
set depth = 0

The dungeon mouth yawns before you.

choice:
- Descend -> descend
- Turn back -> turn_back

## Descend

set depth = depth + 1

if torches > 0:
  set torches = torches - 1
  Your torch flickers, casting long shadows.
else:
  Darkness presses close.

if visited("descend"):
  You recognize this stretch of corridor.
else:
  The corridor is new. Every shadow could hide danger.

choice:
- Go deeper -> descend
- Search for treasure -> treasure [if depth >= 3]
- Retreat to surface -> surface

Key concepts:

  • visited("section") checks if a stage was previously entered
  • Variables track resources (torches, depth)
  • Conditional choices gate content behind progress

functions.plotknot

Demonstrates: custom functions, built-in math functions.

An alchemist shop with calculations:

function: calculate_dosage(2)

# Alchemist Shop

set gold = 50
set reputation = 10
set herb_count = 5
set potency = 3

call calculate_dosage(herb_count, potency) -> dose

if dose > 10:
  "A potent mix," the alchemist mutters.
else:
  "Mild stuff. Good for beginners."

choice:
- Buy healing potion (20 gold) -> buy_potion [if gold >= 20]
- Ask about rare ingredients -> rare_info [if reputation >= 15]
- Leave -> leave

## Buy Potion

set gold = gold - 20
set health = min(health + 30, 100)

The potion glows faintly blue.

-> alchemist_shop

Key concepts:

  • function: declares a custom function signature
  • call invokes it and stores the result
  • Built-in min() clamps values
  • Choices gate on variable state

interpolation.plotknot

Demonstrates: text interpolation, inline tags, character properties, dismissible choices, nested choice content, stop.

A cafe scene with dynamic dialogue and styling cues:

declare gold = 50
declare visited_cafe = false

# The Cafe

set barista.friendship = 0

<ambient> The cafe hums with quiet conversation. Steam curls from the espresso machine.

cycle
  You push through the door for the first time. A bell chimes.
--
  The bell chimes again. The barista looks up.
--
  You walk in like you own the place.

$barista polishes a glass behind the counter.

choice:
-- Ask about the special blend -> special
  set barista.friendship = barista.friendship + 1
- Order a coffee -> order
- Leave -> leave

## Special

<friendly> "The special? You've got good taste," says $barista.

set gold = gold - 8
You sip something extraordinary. Worth every coin.

-> the_cafe

## Order

A plain coffee. Reliable. You have $gold gold left.

choice:
- Another round -> order [if gold >= 3]
  set gold = gold - 3
- Leave -> leave

## Leave

if gold <= 0:
  <tense> Your pockets are empty. Time to go.
  stop

You step back into the street. Maybe tomorrow.

stop

Key concepts:

  • $var and ${expr} insert live values into narrative text
  • <tag> and <tag prop=val> attach styling metadata for the game UI
  • cycle variation changes the entrance text on each visit
  • -- dismissible choices disappear after being selected
  • Indented statements under choices run before navigation
  • stop ends the story explicitly
  • declare seeds initial variable values

variation_blocks.plotknot

Demonstrates: all five variation modes (sequence, cycle, once, pick, shuffle).

A guard post and market square showing each mode:

# Guard Post

sequence
  The guard eyes you suspiciously. "First time in town?"
--
  The guard nods. "Back again?"
--
  The guard waves you through without a word.

-> market

## Market

shuffle
  A merchant hawks fresh bread.
--
  Children chase a stray dog between the stalls.
--
  A musician plays a lilting tune by the fountain.
--
  Two old women argue about the price of turnips.

choice:
- Browse the stalls -> browse
- Visit the fountain -> fountain

## Browse

pick
  You find a dusty old map.
--
  A vendor offers you a free sample of cheese.
--
  Someone's dropped a shiny button.

-> market

## Fountain

once
  The fountain burbles peacefully. You toss in a coin.
--
  The fountain again. Your coin glints at the bottom.

cycle
  Pigeons coo on the fountain's rim.
--
  A child splashes in the shallows.
--
  An old man fills his waterskin.

choice:
- Return to the market -> market
- Leave town -> leave

## Leave

You pass the guard one last time.

-> guard_post

Key concepts:

  • sequence plays items in order and sticks on the last
  • shuffle plays all items in random order, then reshuffles
  • pick chooses a random item each time (items can repeat)
  • once plays items in order, then produces nothing
  • cycle loops through items indefinitely
  • Items are separated by -- and can contain multiple lines

imports.plotknot

Demonstrates: multi-file imports, namespaced stage IDs.

A story split across files. The main file imports two modules:

import characters
import "scenes/tavern.plotknot"

# Village Square

declare reputation = 0

The village square bustles with morning activity.

choice:
- Visit the tavern -> scenes.tavern
- Talk to the elder -> characters.elder
- Leave the village -> leave

## Leave

You wave goodbye. Your reputation here is $reputation.

stop

The imported characters.plotknot file:

# Elder

The elder sits on a weathered bench, feeding pigeons.

set elder.wisdom = 10

if elder.wisdom > 5:
  "Sit," the elder says. "Let me tell you about this place."

-> village_square

The imported scenes/tavern.plotknot file:

# Tavern

The tavern is warm and loud. A fire crackles in the hearth.

choice:
- Order a drink -> drink
- Return to the square -> village_square

## Drink

set reputation = reputation + 1
The ale is good. The company is better.

-> tavern

Key concepts:

  • import name resolves to name.plotknot in the same directory
  • import "path" uses a literal file path
  • Imported sections are namespaced: characters.elder, scenes.tavern
  • Namespaced IDs are used in goto targets and choice destinations
  • Variables and character properties are shared across all imported files

complete.plotknot

Demonstrates: all features combined in a substantial story.

A sci-fi narrative (“The Awakening”) with 15 sections exercising:

  • Hub-and-spoke structure
  • Multiple conditional choices
  • Variable tracking (alert level, trust, power)
  • Game function calls
  • if/else branching
  • Multiple endings based on accumulated state

This is the best reference for how all features compose in a real story. Read it in examples/complete.plotknot.

Running examples

Compile any example:

./plotknot compile examples/hello.plotknot --output /tmp/

Run through the Lua runtime:

local plotknot = require("plotknot")
local data = require("hello")
local story = plotknot.load(data)
story:start()

for _, line in ipairs(story:current()) do
  if line.type == "text" then print(line.text) end
end

Real-world patterns

These patterns combine multiple features. Each is a skeleton you can adapt.

Branching dialogue with NPC memory

Track what the player has said to an NPC using character properties and conditions:

# Blacksmith

declare blacksmith.trust = 0
declare blacksmith.met = false

if not blacksmith.met:
  A broad-shouldered woman hammers at the anvil.
  She looks up. "First time here?"
  set blacksmith.met = true
else:
  cycle
    The blacksmith nods. "Back again."
  --
    "More work for me?"

choice:
- Ask about the crypt -> crypt_talk [if not blacksmith.crypt_told]
- Ask about the crypt -> crypt_repeat [if blacksmith.crypt_told]
- Buy a sword (50 gold) -> buy_sword [if gold >= 50]
- Leave -> village_hub

## crypt_talk

"The crypt? Aye, I know it."
"My grandfather sealed the lower levels. Said something was down there."

set blacksmith.crypt_told = true
set blacksmith.trust = blacksmith.trust + 1

if blacksmith.trust >= 2:
  "Tell you what. Take this key. It opens the lower gate."
  set has_crypt_key = true

-> blacksmith

Features used: character properties (blacksmith.trust), declare, cycle variation, conditional choices, nested if.

Replayable encounters

Use visited() and variation blocks to make encounters feel different on replay:

# Ambush

set encounter_count = visited("ambush")

if encounter_count == 0:
  Bandits leap from the trees! "Your gold or your life!"
else:
  pick
    The same bandits again. They look less confident this time.
  --
    "Not you again!" The bandits draw their weapons reluctantly.
  --
    The bandit leader sighs. "Just take the road. We're done."

choice:
- Fight -> combat
- Pay 20 gold -> pay [if gold >= 20]
- Intimidate -> intimidate [if strength > 12]

Features used: visited(), pick variation, conditional choices, if/else.

Quest state machine

Track quest progress with variables and call statements:

# Quest Board

choice:
- Accept the crypt job -> accept_crypt [if not quest.crypt_accepted]
- Check crypt progress -> crypt_status [if quest.crypt_accepted]
- Leave -> town_hub

## accept_crypt

"The crypt beneath the church. Something's stirring down there."
"Five hundred gold when the soulstone is secured."

set quest.crypt_accepted = true
set quest.crypt_stage = "accepted"
call notify_quest_log("crypt", "accepted")

-> town_hub

## crypt_status

if quest.crypt_stage == "accepted":
  "The crypt awaits. The church is at the east end of town."
else:
  if quest.crypt_stage == "soulstone_secured":
    "You did it. Here's your gold."
    set gold = gold + 500
    set quest.crypt_stage = "complete"
    call notify_quest_log("crypt", "complete")

-> town_hub

Features used: dot-notation variables (quest.crypt_stage), call for game interop, nested if/else, conditional choices.

Next steps

All examples are verified in the test suite (tests/test_runtime.lua) — they compile, load, start, and produce expected output.