How to write an interactive fiction game
Everything you need to turn text into an interactive story. Each topic has a permanent link you can bookmark or share.
Game basics - text and choices
A game is a set of named pages. The player reads a page, then follows a choice to another page. Put the game details at the top, between optional separator lines.
Your first game
---
title: The Lantern Path
description: Find your way home before dark.
author: Alex
tags: fantasy, mystery
start: crossroads
---
# Page: crossroads
## The crossroads
You find a lantern beside two paths.
### What do you do?
@choice "Take the woodland path" -> woods
@choice "Follow the river" -> river
# Page: woods
The lantern reveals a safe trail home.
@ending won "Home by lantern light"
# Page: river
The current carries away the path markers.
@ending lost "Lost beside the river"Pages and text
Start every scene with # Page: page-id. Page IDs may contain letters, numbers, hyphens, and underscores. Use blank lines to start new paragraphs. Headings written with ## through ###### appear in the story; # Page: is reserved for page declarations.
Choices
A choice contains the button label and the destination page. Add if and an expression to show it only when a condition is true.
@choice "Open the door" -> hallway
@choice "Use the brass key" -> vault if player has "Brass key"Formatting
Formatting goes inside story text. Close every brace style with its matching slash tag.
| Style | Write this | Result |
|---|---|---|
| Bold | **important** or {bold}important{/bold} | important |
| Italic | *quietly* or {italic}quietly{/italic} | quietly |
| Underline | {underline}clue{/underline} | clue |
| Strikethrough | {strike}removed{/strike} | |
| Serif | {serif}old letter{/serif} | old letter |
| Monospace | {monospace}ACCESS 04{/monospace} | ACCESS 04 |
| Uppercase | {uppercase}warning{/uppercase} | warning |
| Large | {large}Crash!{/large} | Crash! |
| Small | {small}a whisper{/small} | a whisper |
| Speech | {speech}Who goes there?{/speech} | Who goes there? |
| Left aligned | {left}On the left.{/left} | On the left. |
| Centred | {centre}In the middle.{/centre} | In the middle. |
| Right aligned | {right}On the right.{/right} | On the right. |
Colours
Use {red}Danger{/red}. The complete set is red, orange, yellow, green, blue, purple, black, and white.
Links and images
Read [the inscription](lore:inscription).
Visit the [project website](https://example.com).
{left}
Web links open in a new tab; other links open an information dialog. Image paths always start with images/ and are relative to the main game.game, even when written in an included file. Subfolders are supported. Images accept left, right, center, or centre. HTML typed into story text is escaped.
@commands
Commands are instructions to the game engine. Put declarations outside pages and actions inside the page where they should happen.
@choice
Show a button that opens another page. An optional if condition controls whether it appears.
@choice "Climb the tower" -> tower if courage > 2@if and @end
Show a block of text only when its expression is true.
@if lampOn
The room glows amber.
@end@set
Change a variable, entity property, or entity state when the page opens.
@set courage = courage + 1
@set guard.trust = guard.trust + 1
@set guard is "alert"@once, @random, and @or
Show a block once, or select one of two or more alternatives. Close either block with @end.
@random
Rain strikes the window.
@or
Thunder rolls overhead.
@end@goto and @ending
@goto creates a Continue button to a page. @ending finishes the game as won or lost. A page using either cannot also contain choices.
@goto next-chapter
@ending won "The mystery solved"@inventory, @take, and @give
Set starting items, add items, remove them, or transfer them between the player and an entity.
@inventory "Folded map"
@take "Brass key"
@give "Brass key" to guard
@take "Gate token" from guard@include, @include-shared, and @namespace
Split a large game into files, load the shared character file, and group page and entity IDs.
@include "chapters/station.game"
@include-shared "character.game"
@namespace stationSimple combat module
Include the shared combat module, set its win and lose page names, then link a choice to combat.begin. It uses the player character's Hit points, Attack, and Defence values. Winning, escaping, or making the opponent flee goes to the win page; player death goes to the lose page.
@include-shared "character.game"
@include-shared "combat.game"
@set combatWinPage = "after-fight"
@set combatLosePage = "game-over"
@choice "Fight the creature" -> combat.begin@entity
Declare a persistent character or creature and its starting state. See entities for properties and examples.
@entity guard
state: "awake"
@endVariables
Variables remember information across the whole game. Declare them outside pages, insert them in text with braces, and update them with @set.
| Type | Good for | Declaration |
|---|---|---|
@int | Whole numbers: visits, health, score | @int visits = 0 |
@float | Decimal numbers: temperature, distance | @float temperature = 19.5 |
@string | Names, labels, and other text | @string heroName = "Alex" |
@flag | True/false flags | @flag lampOn = false |
# Page: return
@set visits = visits + 1
@set temperature = temperature + 0.5
@set heroName = "Captain " + heroName
@set lampOn = not lampOn
Welcome back, {heroName}. This is visit {visits}.
Expressions and conditions
Compare values with ==, !=, >, >=, <, and <=. Combine tests with and, or, not, and parentheses. You can also use inventoryCount, player has "Key", random(1, 6), and chance(25).
Inventory and entities
Inventory
Item names are always in double quotes. Items persist between pages and the same item is not added twice.
@inventory "Folded map"
# Page: workshop
@take "Brass key"
@if player has "Brass key"
The key fits the lock.
@end
@give "Brass key"
Entities
Entities are people or creatures with persistent state, typed properties, and their own inventory. Declare them outside a page.
@entity guard
state: "awake"
@string name = "Mara"
@int health = 10
@float pace = 1.0
@flag alive = true
@end
# Page: meeting
@set guard.health = guard.health - 2
@set guard is "angry"
@give "Gate token" to guard
{guard.name} has {guard.health} health.
@if guard is "angry" and guard has "Gate token"
Mara grips the token and blocks the gate.
@end
Use entity.property in text, expressions, choices, and @set. Entity state, properties, and inventories are included in saved games.
player is a built-in entity with a default state of "normal". Use @set player is "drunk" to change it and player is "drunk" in conditions. You can optionally declare @entity player once to choose its initial state and add typed properties. Its inventory is the same inventory used by @take, @give, and player has "Item".
Advanced game techniques
Larger games and namespaces
Use includes to divide a filesystem game into chapters. Paths are relative to the file containing the include and cannot leave the game folder. A namespace prefixes its pages and entities; local links inherit it automatically.
@include "chapters/station.game"
@namespace station
# Page: platform
@choice "Wait here" -> platform
@choice "Board" -> train.carriage
Reusable snippets
Declare repeated story text once with # Snippet:, then insert it into any page with @use. Snippets named standard-header, standard-footer, and standard-prompt are automatically added to every page. standard-prompt appears after the page text and before the choice buttons. Snippets can contain Markdown, images, conditional text, and other snippets. Put them in an included file if you want to keep the main game file short.
# Snippet: standard-header
@inventory-panel

---
# Snippet: standard-footer
---
*Thanks for playing.*
# Snippet: standard-prompt
Choose carefully.
# Page: woods
You are in a forest of trees.
# Page: clearing
@use standard-header
An explicit use is allowed and will not be duplicated.
Place @inventory-panel in a page or snippet to show the live player inventory there. When it is omitted, no inventory panel is shown.
Custom styles and themes
Declare a stylesheet in the game details, then apply one or more of its CSS classes in story text.
stylesheet: styles/game.css
{style=warning large}The bridge begins to collapse.{/style}
Page images
Add an optional scene image immediately after a page declaration. GIF, JPEG, PNG, and WebP files up to 5 MB are supported.
# Page: platform
image: images/scenes/platform.webp
The last train waits beneath the station clock.
Scene-image paths are relative to the main game.game and must remain inside its images folder.
Validation checklist
- Every start page, choice destination, and transition destination must exist.
- Page IDs and variable names must be unique.
- Close every
@if,@once,@random, and@entityblock with@end. - A random block needs at least two alternatives separated by
@or; text blocks cannot be nested. - Errors block Play; warnings do not. Save can keep an unfinished draft before it validates.