Chapter 8: Ticks, scenes, and progress

Timed code

As we've seen earlier (Stopping and ticking), the standard library measures time in ticks. One tick corresponds to one action. The library makes a multi-query to the predicate (on every tick) on every tick. By default, this predicate contains a rule that in turn makes a multi-query to (on every tick in $), with the current room as parameter.

To add flavour text to a location, you can combine this mechanism with a select statement:

(on every tick in #library)
(select)
Somebody tries to hold back a sneeze.
(or)
You hear the rustle of pages turned.
(or)
The librarian gives you a stern look.
(or)
(or)  %% Don't print on every single turn.
(or)
(or)
(at random)
[Copy to clipboard]

For fine-grained control, you can use a global variable to implement timed events:

(global variable (dragon's anger level 0))
(narrate entering #lair)
(now) (dragon's anger level 0)
(fail)  %% Proceed with the default '(narrate entering $)' rule.
(on every tick in #lair)
(#dragon is #in #lair)  %% Only proceed if the dragon is here.
(dragon's anger level $Anger)
(narrate dragon's anger $Anger)
($Anger plus 1 into $NewAnger)
(now) (dragon's anger level $NewAnger)
(narrate dragon's anger 0)
The dragon gives you a skeptical look.
(narrate dragon's anger 1)
The dragon huffs and puffs.
(narrate dragon's anger 2)
The dragon looks at you with narrowed eyes.
(narrate dragon's anger 3)
The dragon roars! You'd better get out.
(narrate dragon's anger 4)
The dragon almost hits you with a burst of flame. You flee.
(enter #outsideLair)
[Copy to clipboard]

To model a scene that plays out in the background for several moves, use a global flag and a tick handler:

(perform [read #notice])
Auction! Today! In the marketplace!
(now) (auction scene is running)
(on every tick in #marketplace)
(auction scene is running)
"Who can give me five dollars for this lovely
(select) spatula (or) glass bead (or) stuffed wombat (at random)?"
shouts the auctioneer at the top of his lungs.
(perform [wave])
(current room #marketplace)
(auction scene is running)
Just as you are about to place a bid, an unexpected thunderstorm
emerges from a gaping plot hole. "Auction suspended", says the
auctioneer.
(now) ~(auction scene is running)
[Copy to clipboard]

Cutscenes

In their simplest form, cutscenes are just large blocks of text and perhaps a couple of modifications to the object tree. As such, they can appear anywhere in the program. If a cutscene is triggered by an action handler or a tick callback, it is customary to end the scene with (stop) or (tick) (stop), to inhibit any subsequent actions mentioned in the player's input. For instance:

(perform [pull #handle])
You grab the handle of the machine, and hesitate for a moment. Is
this really safe?
(par)
But you have no choice. You pull the handle. Sparks hum in the air
as you are sucked into the vortex of the machine.
(par)
You find yourself in a barn.
(move player to #on #haystack)
(try [look])
(tick) (stop)
[Copy to clipboard]

If the cutscene can be triggered in multiple ways, put it in a separate predicate and query that from as many places in the code as you wish.

To prevent a cutscene from occurring twice, use a global flag:

(perform [pull #handle])
(if) (have teleported to barn) (then)
Nothing happens when you pull the handle.
(else)
(teleport to barn cutscene)
(endif)
(teleport to barn cutscene)
...
(now) (have teleported to barn)
(stop)
[Copy to clipboard]

The intro

When a story begins, the standard library queries the (intro) predicate, which story authors are encouraged to override.

The default implementation of (intro) just prints the story banner by querying (banner). The banner includes version information for the compiler and the standard library. By convention, stories should print the banner at some point during play. With Dialog, there is no formal requirement to print the banner at all, but it is helpful to the community and to your future self, and it looks professional.

(intro)
"In medias res?" exclaimed the broad-shouldered Baron furiously.
"We find it preposterously cliché!"
(banner)
(try [look])
[Copy to clipboard]

Keeping score

The player's progress can be tracked by a global score variable. This feature needs to be enabled, by including the following rule definition somewhere in the story:

(scoring enabled)
[Copy to clipboard]

For scored games, the current score is displayed in the status bar.

The global variable is called (current score $).

Points can be added to the score with (increase score by $), and subtracted with (decrease score by $). These predicates fail if the score would end up outside the valid range of integers in Dialog, which is 0–16383 inclusive.

After every move, the standard library will mention if the score has gone up or down, and by how much, unless the player has disabled this feature using NOTIFY OFF.

If you know what the maximum score is, you can declare it:

(maximum score 30)
[Copy to clipboard]

When declared, the maximum score is mentioned by the default implementation of the SCORE command, in the status bar, as well as by the (game over $) predicate. It does not affect the operation of (increase score by $).

The status bar

It is straightforward to supply your own, custom status bar. Define a rule for the predicate (redraw status bar), and make use of the status bar functionality built into the Dialog programming language.

The standard library defines (status headline), which can be used to print the location of the player character in the usual way. That would normally be the current room header, followed by something like “(on the chair)” if the player character is the child of a non-room object. But if the player character is in a dark location, control is instead passed to (darkness headline), which usually prints “In the dark”.

%% A thicker status bar with the name of the current player in the upper right corner.
(style class @status)
height: 3em;
(style class @playername)
float: right;
width: 20ch;
margin-top: 1em;
(style class @roomname)
margin-top: 1em;
(redraw status bar)
(status bar @status) {
(div @playername) {
(current player $Player)
(name $Player)
}
(div @roomname) {
(space 1) (status headline)
}
}
[Copy to clipboard]

Game over

The library provides a predicate called (game over $). Its parameter is a closure containing a final message, which the library will print in bold text, enclosed by asterisks. Then it will:

Here is an example of a (very small) cutscene that ends the game:

(perform [eat #apple])
The apple is yummy. You feel that your mission has come to an end.
(game over { You are no longer hungry. })
[Copy to clipboard]

A fifth option can be added to the game-over menu: AMUSING. First, add the option to the menu with the following rule definition:

(amusing enabled)
[Copy to clipboard]

Then, implement a predicate called (amusing) that prints a list of amusing things the player might want to try:

(amusing)
(par)
Have you tried...
(par)
(space 10) ...eating the transmogrifier? (line)
(space 10) ...xyzzy? (line)
[Copy to clipboard]