Twine: Further remarks about <<cyclinglink>> and game choices

I've recently played the game Breakfast on a Wagon With Your Partner, which uses the <<cyclinglink>> macro, and it's inspired a few brief thoughts about the user interface of text choices.

Generally, the interface used for choice, especially dialogue trees, in videogames and Twine games, resembles the following:

Fig. 1: A doctored page from "Breakfast on a Wagon With Your Partner"
A list, usually exhaustive, of the possible choices one could make, usually in an egalitarian fashion. Its exact shape is highly variable, but the important aspect is that every choice is simultaneously visible.

However, the introduction of my <<cyclinglink>> macro has unwittingly introduced an alternative arrangement:

Fig. 2: An actual page from "Breakfast on a Wagon With Your Partner"
A single hyperlink that cycles between choices when clicked, displaying only the current choice. The choice is confirmed by clicking a proper link below.

This differs from the former in a number of interesting ways. Firstly, when you initially approach the page, all of the other options are concealed. Only by clicking the link successively does each option become briefly visible - the choices are presented in a temporal sequence, rather than a spatial sequence. And, not only are the choices themselves hidden, but their quantity as well - a list of 20 choices takes up the same screen space as a list of two.

I feel as if these differences slightly more closely resemble the actual human process of making a decision. Seldom do we, in our minds, see two drastically opposing options appear side-by-side, when confronted with a tough decision. Rather, our minds actively scour themselves for choices, possibilities - the few that appeal to our intuitions rise immediately, and others must be forced up to our conscious realisation, whereupon we focus intently on it. Neither of these link presentations can be said to truly capture this marvelous process, but I like to think that the cyclinglink presentation has more superficial similarities.

One other difference I feel is worth examining is the way in which cyclinglink, in this particular usage, reverses the usual meaning of clicking a hyperlink. Consider the text "I'm not opposed to settling down" in both illustrations. In the first one, the act of clicking it means to commit to the decision - to perform these words. But in the second, the act of clicking it is the act of revising the decision, replacing it with a different possibility. This strikes a somewhat discordant note within me.

Of course, usually a Twine hyperlink doesn't translate to the player verb "act according to these words" - most often it simply means "provoke the computer to delve further into this subject" or "continue a line of thought paused at this point", any of which could somehow revoke or reverse the link text's meaning, relevance or significance. Porpentine has previously called the hyperlink an "omniverb", commenting that its meanings within a game are as varied as the words you could put inside it, and the text that results when you click it. And, indeed, my original impetus for creating this macro was to extend the mechanical potential of links in a playful fashion - links that change unexpectedly when clicked, like a puzzle switch, or a toy, or an animate object in Windosill. Links that are multifaceted, multifarious, capricious. And, indeed, many uses of cyclinglink that I've seen thus far have used it for playful or cute subject matter, where rubbing and touching the story text, and seeing unexpected changes appear, is in keeping with the tone of the story.

I only came to think of cyclinglink as a genuine replacement for the 'choice list', and began considering the implications thereof, when I encountered the above choice in "Breakfast on a Wagon With Your Partner". This story, to its credit, does a very good job of establishing a "hyperlink vocabulary" - a link at the bottom of each page, reading "(o)", serves as the 'confirm choice' button, and is the only one that is used to advance to a subsequent passage. Nevertheless, the cyclinglink still runs contrary to the grain of the traditional hyperlink's meaning - its underline suggests a button waiting to be pushed, rather than a dial waiting to be turned. I hesitated conspicuously when I got to that page, for I had become quite invested in the path of the story, and of my performance as the player-character.

What shall we conclude from this? I believe that the cyclinglink is entirely in keeping with the standard interface of Twine games, and of the versatility that the hyperlink is afforded within a Twine game. I believe further that the use of a cyclinglink for choices, particularly especially mental choices, lends the selection some superficial resemblance to the human act of reaching and developing decisions. However, it is also important that they are sufficiently differentiable from normal links, in situations where it's important the player has full confidence in the presented hyperlinks. This could be through establishing a firm in-game vocabulary, or, more directly, it could be through CSS. (I do not include CSS within the cyclinglink script that forces them to appear differently to normal links, but I do provide CSS hooks that enable them to be styled differently from normal links.)

For a time after adding the variable-alteration functionality to cyclinglink, I wondered if I had perhaps erred by making it produce a Javascript-augmented link - perhaps a more familiar interface element, like a dropdown menu, would be more appropriate for the use cases of this macro. But, I committed to the justification that the hyperlink is the player interface element in Twine games, far more so than even familiar website navigation elements. They are, after all, not web sites, but videogames. So, let the hyperlink's scope be extended so.

Twine: visited(), a function to find how often a passage was visited

Update: The Javascript on this page is now built into Twine 1.4! It is no longer necessary to install it.

I've noticed that a good deal of variable usage in Twine is simply employed to track whether or not you've visited a certain passage previously, or done so a number of times. This script code provides you with a function that can give this value straight, without needing to use the <<set>> macro.

Obsolete script removed: use Twine 1.4

To use it, use "visited("Passage")" as a value in a <<set>>, <<if>> or <<print>> macro. It will equal the number of times you've visited the passage whose name is between the quotes and parentheses. For instance:

  1. <<if visited("Store")>>You've been to the store.<<endif>>
  2. <<if not visited("Firepit") and not visited("Maggotpit")>>You've not yet been to the fire pit or maggot pit.<<endif>>
  3. <<if visited("Trap Floor") gt 2>>Your repeated stomping is making the floor weaken.<<endif>>
  4. You've visited the chasm <<print visited("Dark Chasm")>> times.<<endif>>
  5. <<set $count = visited("Jewelers")>>You've visited the Jeweler's <<print $count>> times.<<endif>>

Also, instead of a passage name, you can put a variable.
  1. <<if visited($bossPassage)>>If only you'd had this when you'd fought the boss!<<endif>>
  2. <<set $beenToMyHome = visited($myHome)>>

New: If you just put visited(), then it will count visits to the current passage, without you needing to include the passage's name.
<<set $beenHereBefore = visited()>>

In addition, here is a function called "visitedbefore" which can be used to tell you if a passage has ever, in the course of the game, been visited before another passage.

window.visitedbefore = function(e,f) {
	var h,c,ret;
  	for(ret=c=0; c<state.history.length; c++) {
		h = state.history[c].passage;
		if(h && h.title == e) {
			return true;
		} else if (h && h.title == f) {
			return false;
	return false;

This only returns true or false. Use it like so:
  1. <<if visitedbefore("Firepit", "Maggotpit")>>First the fire pit, then the maggot pit, now this?!<<endif>>
  2. <<if visited("Armory") and visitedbefore("Firepit", "Maggotpit")>>First the fire pit, then the maggot pit, now this?!<<endif>>

Again, variables can be substituted for passage name strings:
  1. <<if visitedbefore("Dragon Hoard", $myHome)>>You must've left it at home after meeting the dragon.<<endif>>

The two passage names must be separated by a comma.

Some notes:

  1. This doesn't count use of the <<display>> macro as a visit.
  2. If neither passage has been visited, visitedbefore() will return false.

Feel free to report any bugs to @webbedspace.

Twine: escape line breaks with backslashes

Update: The Javascript on this page is now built into Twine 1.4! It is no longer necessary to install it.

As you know, one of the most prominent difficulties with mixing code and prose in Twine games is the handling of line breaks. If you have a bunch of macros interspersed in your prose, especially <<if>> macros, then the temptation is to leave them on separate lines from the text, for readability. However, the line breaks adjacent to these macros end up appearing in the rendered passage.

The <<silently>> macro is the 'official' solution...

<<set $hp -= 1>>
<<set $dead = true>>
Wrap up all of the macros in it and the line breaks will be suppressed. But, it seems to me to be a half-effective solution, requiring two large macro tags enclosing each block, and disallowing <<print>> macros within it.

I've previously advocated a different solution, involving the TiddlyWiki comment syntax:

%/<<set $hp -= 1>>/%
%/<<set $dead = true>>/%
This has the advantage of allowing the use of <<print>> macros. But it, too, is authorially and visually unwieldy, requiring both the end and beginning of lines to be tagged with one of two similar-looking tags.

However, I believe it's on the right track. Right now, my proposal is to directly borrow the syntax of the C language's preprocessor - a single backslash at the end of a line suppresses the line break:

<<set $hp -= 1>>\
<<set $dead = true>>\
You are \
<<if $hp gt 1>>
almost dead
In my opinion, this has much more convenience compared to the other two.
Here is the script code to enable this in your stories:

Obsolete script removed: use Twine 1.4

To recap, when you install this script:

  1. A line that reads:
    You have \
    twenty dollars.
    ...will be rendered as "You have twenty dollars."
  2. A line that reads:
    You have \
    <<set $cash = "twenty">>\
    <<print $cash>>\
    ...will be rendered as "You have twenty dollars."

Feel free to report any bugs to @webbedspace.

The Minimalist Game

I don't have time to make anything substantial for Ludum Dare this weekend, so I decided to port NOM3RCY's classic text adventure, THE MINIMALIST GAME, to Twine.

I added a few bells and whistles. First, the scoring works unlike in the original game, and second, the order of the possible responses are randomised each time you play! REPLAY VALUE.

I couldn't submit it to the actual site because it's a port of someone else's game. :(


Twine: example uses for <<revision>>

The <<revision>> macros provide a good deal of expressive power, though some potential uses may not be immediately obvious. Here are a few examples which may provide some inspiration.

An extended form of <<cyclinglink>>

This combination of <<cycle>> and <<set>> changes a variable every time you cycle the passage using the link.

<<cycle attire>>
<<set $shirt="red">>[<img[redshirt.png]] You eagerly slip on a red shirt.
<<set $shirt="blue">>[<img[blueshirt.png]] No! Clearly, a blue shirt is required.
<<set $shirt="vermillion">>[<img[yellowshirt.png]] Nope, it ''has'' to be vermillion! Or so you think...
<<revise attire "Reconsider shirt">>

A music picker

This uses my HTML5 sound macros to create a link that changes the currently playing music when you click it. (If the music isn't meant to loop, of course substitute <<loopsound>> with <<playsound>>)

<<cycle music>>
<<stopallsound>><<loopsound "piano.ogg">>Piano music
<<stopallsound>><<loopsound "chiptune.ogg">>Chiptune music
<<stopallsound>><<loopsound "acapella.ogg">>A capella music
<<revise music "Change current music">>

More info / Less info

This functions similar to <<replace>> but also provides a link to hide the inserted text after it's been shown.

<<revision info>>You see here a spotted stripe frog.<<revise info "(More info)">><<gains>>
Poisonous in 3 countries and illegal contraband in 7, this fearsomely shaded amphibian is an aesthetic and biological terror.
<<revert info "(Less info)">>

More examples to come...

Twine: loading jQuery without editing the HTML

Update: Twine 1.4 now lets you load jQuery via a StorySettings option, so this page is no longer necessary.

You can load jQuery into your Twine game via Google Hosted Libraries using this simple script code:

Obsolete script removed: use Twine 1.4

Change whatever you want the function passed to ready() to do.
* Since Twine's engine uses '$' for something, jQuery must sadly relinquish that convenient abbreviation.
* Due to a minor bug, Twine's runtime currently doesn't work at all in IE 8 or below, so there's not much reason to use jQuery 1.9 over 2.0
* Using Google Hosted Libraries of course means that people can't play your game without a live internet connection - which, in the extremely rare chance you intend your Twine game to be available offline, should be borne in mind. Nevertheless, it can be more convenient for you, the author, due to not having to provide the jQuery file yourself.
Feel free to report any bugs to @webbedspace.

Twine bugfix: "and" and "or" in strings in <<set>>, <<print>> and <<if>>

Update: this has been fixed in Twine 1.4, so this is no longer necessary.

As you know from reading my macro reference, code parameters passed to <<if>>, <<set>>, <<print>> and <<remember>> have their operators ("and", "or", "$", "not", etc.) converted to Javascript equivalents ("&&", "||", etc.) when they're executed.

But, due to a bug, this conversion is also applied to "operators" that are inside strings passed to these macros. So, <<print "Vast and large">> will print "Vast && large", and <<print "You find $10">> will print "You find state.history[0].variables.10".

Now, I've sent out a pullreq to the Twine GitHub repo that fixes this, but in the meantime, if you're stymied by this bug, you can fix it with this script:

Obsolete script removed: use Twine 1.4

Again, note that in some versions this will be loaded after the Start passage has rendered.

Version history:
* 2/7/13 - Bugfix for "She's cute" string.
* 5/5/13 - Bugfix.
* 25/4/13 - Initial.
Feel free to report any bugs to @webbedspace.

Twine macro: <<hoverrevise>>, a mouseover extension to <<revise>>

The <<hoverrevise>> macro is an extension of the <<revision>> / <<revise>> macros. It lets you make a span of passage text briefly appear or disappear whenever you mouseover another span of text in the same passage.


Just install the latest version of <<revision>> (1.1.0 or later). It's included already.


You may want a "dissolve" type of transition to be applied to the text that appears or disappears. For that, give the <<insertion>> or <<removal>> macro a name that, say, contains "hover", and then use this CSS to exclusively target it:

.revision-span-in[class*=hover] {
	opacity: 0;
.revision-span[class*=hover] {
	transition: 1s;
	-webkit-transition: 1s;
.revision-span-out[class*=hover] {
	opacity: 0;

If you aren't using any normal <<revise>> macros, you could omit "[class*=hover]" to make every <<revision>> span have this transition.

Usage examples

Use this in conjunction with <<revision>>, <<insertion>> or <<removal>> spans, as you would with <<revise>>. The <<hoverrevise>> macro covers a full span of text, ending with <<endhoverrevise>>

To make the text "Aah! A ghost!" appear when you mouseover a span:

<<insertion hoverghost>>Aah! A ghost!<<endinsertion>>
<<hoverrevise hoverghost>>You'd better [[go to the chapel|chapel]]<<endhoverrevise>>

To make the text "Don't leave me" disappear when you mouseover a span:

<<removal words>>"Don't leave me"<<endremoval>>
<<hoverrevise words>>[[Go to the beach|beach]]<<endhoverrevise>>

To make the text "Cold" change to "Warm" when you mouseover a span:
<<revision hovermetal>>Cold<<becomes>>Warm<<endrevision>>
<<hoverrevise hovermetal>>[img[gold.png]]<<endhoverrevise>>

These are just a few of the possibilities allowed by this macro.

This wraps the contained text in a <span> with the class "hoverrevise" as well as "hoverrevise_" suffixed with the identifier you use (for instance, "hoverrevise_boo" for <<hoverrevise boo>>").
Feel free to report any bugs to @webbedspace.

Twine macro: <<revision>> and <<revise>>

The <<revision>> macro is a more powerful variation of the <<replace>> macro. It lets you alter a span of passage text by clicking a link from anywhere in the passage.

Install my <<Replace>> Macro Set to use these macros.

Basic usage
First, use the <<revision>> macro to define two different versions of some text:

You see here <<revision tome>>a closed book.<<becomes>>an open book.<<endrevision>> Each section of text separated by <<becomes>> is a different "version" of the text. The name "tome" in the macro is an identifier. You can use any single word you want as an identifier.

Then, you can create a link using the <<revise>> like so:
<<revise tome "Open the book.">>
This creates a link reading "Open the book" that changes any "tome" revision in the passage into the next version - in this case, changing "a closed book" into "an open book".

(Notice that the word "revise" is the verb form of "revision", reflecting the fact that the hyperlink serves as a verb for the player to perform.)

Running code in revisions
Just like with <<replace>>, any macros inside a revision are run as soon as they are made to appear:

<<revision button>>You see a button.<<becomes>>The button is pushed.<<set $button=true>><<endrevision>>
<<revise button "Push it.">>

The above code will set $button to true when you click the "Push it." link.

Multiple versions
You can have many versions of a span of text:

You see here <<revision books>>a closed book.<<becomes>>a chewed book.<<becomes>>paper scraps.<<endrevision>>
<<revise books "Chew book">>

In this case, the link "Chew book" will change "a closed book" into "a chewed book", then "paper scraps". When it reaches the end, the link vanishes.

Back and forth
The <<revert>> macro functions to reverse the effects of the <<revise>> macro.

<<revision box>>Here is a closed box.<<becomes>>Here is an open box.<<endrevision>>
<<revise box "Open the box.">>
<<revert box "Close the box.">>

The <<revert>> macro's link won't be displayed if it's at the first revision, just as the <<revise>> macro won't be visible if it's at the last revision.

Cycling link text options (New)
The <<revise>> and <<revert>> macros have a number of additional options similar to the <<cyclinglink>> macro. You can supply additional link text strings after the first, and it will change to the next string after you click it.
<<revise wall "Smash wall" "Smash it again" "Keep smashing">>

If the second parameter begins with the "$" sigil, then it will interpret it as a variable to be altered by clicking the link. When the player clicks the link, the variable will be changed to match the text of the link.
<<revise heat $warmth "Set it to warm" "Set it to hot" "Set it to boiling">>

If the last parameter is the word "end", then it will disable the link at the parameter before last, leaving just the text. If the last parameter is the word "out", then it will vanish altogether once it's reached the final string.
<<revise roast "Devour roast" "Munch some more" "You're too full now" end>>
<<revise amphora "Drink wine" out>>

Click here to read about <<hoverrevise>>.

<<becomes>> vs. <<gains>>
Just as the <<replace>> macro has a variation, <<insert>>, so too does <<becomes>>.

The vase contains:<<revision vase>>Two roses<<gains>>, an orchid<<gains>>, a pencil<<gains>>, a straw<<endrevision>>.
<<revise vase "Put something in the vase">>.

When you click "Put something in the vase", the text changes from "Two roses" to "Two roses, an orchid", then to "Two roses, an orchid, a pencil" and so forth.

You can mix and match <<gains>> and <<becomes>> at will:

<<revision count>>One<<becomes>>Two<<gains>>and a half<<becomes>>Three!<<endrevision>>

This changes from "One" to "Two", "Two and a half", and finally "Three!".

Normally, when a <<revision>> macro reaches the end of its revisions, its links disappear. If you change "revision" to "cycle", then you can keep clicking the link to return to the first revision.

You see here <<cycle pet>>a dog<<becomes>>a cat<<endcycle>>.
<<revise pet "Change pet">>

This will change "a dog" to "a cat" and vice-versa, with the link never expiring.

<<insertion>> and <<removal>>
Two other variations exist, which are roughly analogous to <<timedinsert>> and <<timedremove>>.
You look at the plate. <<insertion grain>>1 grain.<<becomes>>2 grains.<<becomes>>3 grains.<<endinsertion>>
For <<insertion>>, the first version "1 grain" is initially invisible. Clicking a <<revise>> link will make it visible. Then, it functions as normal.
You look at the plate. <<removal seed>>3 seeds.<<becomes>>2 seeds.<<becomes>>1 seed.<<endremoval>>
For <<removal>>, the <<revise>> link will remain when you get to "1 seed". Clicking it a final time will remove the text altogether.

Example program

Technical details

  1. Each version inside a <<revision>> macro is a <span> classed with "revision-span" as well as the kind of version they are ("becomes", "gains"). When they appear, they are given the class "revision-span-in" and their display is set to "inline". When they disappear, they gain the class "revision-span-out" and, after 1 second, their display is set to "none". All of these are inside a container <span> classed with the name of the macro ("insertion", "removal", "cycle", "revision") and the name of the identifier ("book" or whatever the author set it to be).

Version history:

  1. 27-9-2013: Fixed several bugs, including:
    <<revision>> macros were being called twice on passage load.
    <<revision>> macros were still taking effect while they were animated being removed by other macros.
    Multiple <<revision>> macros with the same ID weren't all being revised at the same time.
  2. 16-6-2013: Updated regarding Combined Replace Macro Set.
  3. 26-4-2013: Added extra options to <<revise>>.
  4. 20-4-2013: Added <<hoverrevise>> and support for <<randomise>>.
  5. 18-4-2013: Initial

Feel free to report any bugs to @webbedspace.

Twine macro: <<timedloop>>

This macro is similar to <<timedreplace>>, but instead of replacing one block of text with another, it just re-runs a passage section, re-drawing the text and running the contained macros again.

Much like <<timedreplace>>, the transition between each rewrite is handled by CSS. Here's a default "fade-in" transition:

.timedloop.replacement-in {
	opacity: 0;
.timedloop {
	transition: 1s;
	-webkit-transition: 1s;
.timedloop.replacement-out {
You can easily modify this CSS. If you want an "instant" transition, for instance, change "opacity: 0;" to "display:none;"

New: This now takes CSS time values, which are decimal numbers ending in "s" (for seconds) or "ms" (for milliseconds).

Here's a usage example:

<<set $red=1>>
<<timedloop 1s>>You have <<set $red += 1>><<print $red>> seed pods.<<endtimedloop>>
The text will initially read "You have 2 seed pods.", then change to "You have 3 seed pods." after 1 second, then "You have 4 seed pods." after another second, and so forth until you leave the passage. The time value is in half-seconds, like <<timedreplace>>.

Known bug: When you click a link to leave a passage, the loop will still run while the passage is transitioning out. This may cause unexpected behaviour (if, for instance, a <<timedgoto>> is inside the looped code).

Implementation details:
* If inserted text appears and descends below the bottom of the screen, the page should automatically scroll down to make it visible.
* Note: due to the way the browser and Twine interact, any changes made by code inside a <<timedloop>> tag after the first iteration will be forgotten if you use the Back or Forward browser buttons. This means that if you put long-term variable changes that affect future passages inside one, you should disable the Back button.

Version history:
* 11-4-2013: Fixed bug where the timeout wouldn't expire if you clicked a "refresher" link to the same passage.
* 5-4-2013: Initial.

Feel free to report any bugs to @webbedspace.

Syndicate content