Late Nights With Twine and Snowman

October 29th, 2016

Filed under: Game Development Web Development

In preparation for the Twine and National Novel Writing Month jam I’ve been working on a relatively small test story to get my Twine 2 sea legs. The process has been mentally exhausting…but the end results have been wonderful.

Twine 2?

Twine is a web/desktop application made for creating your own interactive fiction (like a choose-your-own-adventure story) and outputting it to a single HTML file. The output is standalone, executable by itself for viewing in your browser or able to be hosted as a static webpage on any webserver. Twine has been out for about seven years and has matured into Twine 2, a robust, versatile engine worthy of use in any text-centric interactive fiction project.

Snowman is one of Twine’s “story formats”, that is, ways to code your Twine game. The under-the-hood operations that drive game interactions are very similar regardless of format, but the formats offer different approaches to interfacing with Twine’s API for people with different preferences. I figured that since Snowman’s approach is bare JavaScript which, as a web developer, is my cup of tea, I wouldn’t have issue.

The Nitty Gritty

When using Twine with Snowman to develop a story, I thought it was going to be all smiles. “I know how to write JavaScript, I know HTML/CSS, no problem!” As usual, being cocky was my first mistake.

Let’s make a small story shall we? In Twine’s editor you’d first choose Snowman from the default formats by clicking the title of your story and then clicking Change Story Format. Then, you’d type something like the following in the default first passage to get going:

              There's paths on the left and right.

[[Go left]]

[[Go right]]

            

Easy, right? The code above would output this in the editor…

…and this in the browser:

Not too complicated. In the editor, you’d then double click on the newly-created passages that correspond to the links you made, edit those, going and going and eventually you get a whole story laid out:

Note the ridiculous amount of lines. This is how Twine shows you what’s connected to what, and if your dead ends go back to the start as the ones above do there will be a web covering the whole thing. Ways to work around that include:

  • Having an exit link close the tab when clicked (not ideal)
  • Going to the next passage via script (more on this later)
  • and…that’s it as far as I know

While the lines can be beneficial as a connection overview, they tend to clutter the editor and are unable to be turned off. Not a fun time. But! Once you’ve completed something like this it’s all worth it. Click here to see what this example looks like in the wild. The fancier you want to get, the more you have to understand. C’est la vie, no?

Digging Deeper

Making something more complex than what I have just shown isn’t exactly a walk in the park with other formats.

Format differences

I’ve been informed that my knowledge of Harlowe is fairly incomplete regarding breaks. Check out Leon Arnott’s comment below (I flubbed the comments from before somehow) for more info on how to avoid the strange whitespace I mention in this section:

The rule for line breaks in Harlowe is simply: any line break outside of a (macro:) is converted to a br element. This includes all line breaks inside hooks, including after the preceding [ and before the terminating ], if those are left. You can escape line breaks using the C-style \ at either the end or start of lines, and you can compose these together into what I call “block hook delimiters” by writing [\ and \] instead of [ and ] (which you can still use for inline hooks).

With that in mind, let’s keep going!

The default format (Harlowe) is a little too limiting in what it can do, as you’re limited by the provided macros. If they’re fine for your use case (and most of the time they will be) then by all means go for it. This will mean that you would have to think in Harlowe when scripting if you’re used to thinking in JavaScript, and that would be a bit too jarring for some (myself included).

The second provided format (SugarCube) is too “plain-English” for my tastes, and leaves strange whitespace in the final publish for no discernible reason if not formatted absolutely perfectly and rigidly, a flaw shared by Harlowe. Not only that, SugarCube is based on TiddlyWiki’s syntax for prose formatting, which is just way too unconventional for me.

The third provided format Snowman uses a terse Underscore.js powered template system for code, allowing simple declaration and calling of functions or variables through the usual JavaScript conventions. All without any strange whitespace left behind! Perhaps best of all, the prose syntax of choice for Snowman is Markdown! Anything outside of Underscore brackets is parsed through Marked as Markdown before being processed through Twine, producing things such as links as you’d expect to see them.

All that said, let’s take a look at some comparisons for implementing a conditional block of text in all three default formats:

Harlowe:

              (if: $checkpointName is "leave")[
	"Well then. Where did we leave off? Oh yes, you're not among the living anymore."
]
(else:)[
    "Alright. There's a lot you don't know about me, or this place. Or why you're invisible to everyone but me. So, let's keep things simple: I'm a medium. You're dead."
]

(if: $checkpointName is "stand")[
    The force holding you in place lifts completely. You feel that you are able to move again.
]

[[You're taken aback.|surprised at dead]]

[[You quietly await more information.|quietly wait]]

(if: $checkpointName is not "leave")[
    [[You get up and leave without another word.|leave]]
]

            

SugarCube:

              << if story.checkpointName eq "leave" >>
  "Well then. Where did we leave off? Oh yes, you're not among the living anymore."

<< else >>

  "Alright. There's a lot you don't know about me, or this place. Or why you're invisible to everyone but me. So, let's keep things simple: I'm a medium. You're dead."
<< /if >>

<< if story.checkpointName eq "stand" >>
  The force holding you in place lifts completely. You feel that you are able to move again.
<< /if >>

[[You're taken aback.|surprised at dead]]

[[You quietly await more information.|quietly wait]]

<< if story.checkpointName not eq "leave" >>
[[You get up and leave without another word.|leave]]
<< /if >>

            

Snowman:

              <% if (story.checkpointName == "leave") { %>
"Well then. Where did we leave off? Oh yes, you're not among the living anymore."

<% } else { %>

"Alright. There's a lot you don't know about me, or this place. Or why you're invisible to everyone but me. So, let's keep things simple: I'm a medium. You're dead."
<% } %>

<% if (story.checkpointName == "stand") { %>
The force holding you in place lifts completely. You feel that you are able to move again.
<% } %>

[[You're taken aback.|surprised at dead]]

[[You quietly await more information.|quietly wait]]

<% if (story.checkpointName != "leave") { %>
[[You get up and leave without another word.|leave]]
<% } %>

            

If any of the above syntax above is incorrect please let me know in the comments.

As you can see, there’s a special type of bracket for each of the non-Snowman formats. Use of those brackets as shown above causes any new lines they form to be rendered in the output, revealing where possible hidden paths would be through an unusual whitespace.

Consider the passage in my example rendered in Harlowe (using debug mode to show the issues):

vs. Snowman (no debug mode view as debug occurs using in-browser tools):

In Harlowe, the non-matching portions of the conditionals are left in the layout, as are the lines of Harlowe markup. If you view it without debug mode all the non-Harlowe text stays in place as you see it above, with a mess of strange whitespace. This, as you might expect, presents a problem when you want to hide portions of text from the user without them suspecting any text is actually there.

With Snowman, any scripting is parsed without leaving the markup in the layout; the scripting is relegated to the background as JavaScript. This is the behavior I expect to encounter for a story scripting syntax such as this. It lets me shape the source structure in the form that makes the most sense to me, but doesn’t let the user know there’s anything but the story in front of them. A+.

Interactivity/Templating

You’ve got a few passages laid out now and want to get even more fancy, do you? You can start by adding some interactivity to the story and really make it your own. If you know how to manipulate the DOM or grab values from HTML inputs, you can utilize those skills in-game through Twine’s built-in use of jQuery. Snowman helps with its Underscore.js-powered templating, showing in the following source of a passage with gender selection (with some explanations in the comments):

              /*
Using <%= %> instead of just <% %> to surround code lets you render the value of the contained variable outright. Perfect for when you set your player's name in one passage and then want to retrieve it for showing later.
*/

Assured that "<%= story.state.yourName %>" is your name, the blanks begin to refill in your mind as you slowly remember your identity. The next thing you struggle with is remembering how you preferred to be addressed:

/*
Notice the use of bare html for links here. Compared to Twine's link syntax, this lets you use actions on click as shown below. This technique is cross-format.
*/

Him/his.

Her/hers.

They/theirs.

It/its.

/*
Code within a template block is executed on load, so these functions are set before the player even starts reading.

Each function sets the pronouns variable to be an array of pronouns, and stores it in the story.state object. Then, the start2 passage is navigated to. Connecting passages using script like that prevents connection lines from being drawn in the editor view.
*/

<%
story.state.setHe = function (){
  story.state.pronouns = ["he","him","his","his","he's"];
  story.state.cPronouns = ["He","Him","His","His","He's"];
  story.show('start2');
}
story.state.setShe = function (){
  story.state.pronouns = ["she","her","her","hers","she's"];
  story.state.cPronouns = ["She","Her","Her","Hers","She's"];
  story.show('start2');
}
story.state.setThey = function (){
  story.state.pronouns = ["they","them","their","theirs","they're"];
  story.state.cPronouns = ["They","Them","Their","Theirs","They're"];
  story.show('start2');
}
story.state.setIt = function (){
  story.state.pronouns = ["it","it","its","its","it's"];
  story.state.cPronouns = ["It","It","Its","Its","It's"];
  story.show('start2');
}
%>

            

The output:

Basically, when one of those links is clicked there are two things happening:

  • Game-wide variables are set and added to the story.state object, which contains anything that needs to be accessible to the story as a whole for easy access or redeclaration later.
  • The story then moves on to the next passage.

Looks a bit verbose in the source, but this way you have full control over what is done in each interaction you add. Furthermore, if you wanted to implement that gender selection throughout the story you could directly call those pronouns arrays as story.state.yourName was called. Now that they’ve been defined you can even drop the story.state part and abbreviate it as s. Thus…

              "<%= s.cPronouns[2] %> name is <%= s.yourName %>, why not say hi?"

            

…would render…

“His name is Noel, why not say hi?”

…as you’d expect if you were me playing as myself. From here, the world is your oyster! You can take even what you’ve gleaned in these basic examples and use it to do all sorts of things with text replacement, item selection, conditional blocks of text, and much more! I could go over all the possibilities but I think I’ve ranted long enough.

“Turn The Page To Find Out!”

Over the past week or two of delving into Twine I’ve concluded that I’ve only scratched the surface of what’s possible with Twine and Snowman. With the combination of straight CSS in Twine, pure JS with Snowman, and careful consideration of Twine’s HTML structure, anything’s possible for the dedicated and the passionate. Any who are willing to get their hands dirty with web fundamentals will be rewarded handsomely by being granted fine-grain control, clean output, and a wide-open world of possibility for their interactive fiction. Even if you’re not willing to go that deep into web code, the other story formats can take you very far and enable some great work.

I leave you with a list of resources I found useful in these endeavors, both for myself and other Twine enthusiasts:

  • Twine wiki - The definitive knowledge base for anything Twine, from macros to format information to FAQs.
  • Twine forums - For those times when the docs fail you and you’re pulling your hair out.
  • phiolme.la - If you don’t want to pay for webhosting for your Twine game, but you have a Twitter account, look no further.
  • Dan Cox’s Twine 2 tutorials - an informative set of tutorial videos covering the use of many different techniques for creating Twine games, including how to make specific types of games such as the classic incremental, Candy Box 2.
  • Depression Quest - An excellent example of what Twine can do beyond just text and prose.

Oh yeah, if you wanted to see “Invisible”, the story I’ve been pulling from for the format examples, you can click here. It’s in progress and will be for a while as I focus on the upcoming Twine/NaNoWriMo jam. My entry will be called “Breakpoints” for now and will be tracked over on the NaNoWriMo site.

Happy writing, everyone!