Introduction: Building a Combination Lock in Twine 2, Harlowe 2

This instructable will teach you how to make a simple combination lock in Twine 2 using Harlowe 2.0

Step 1: Introduction to Twine

Before we dive into our lock we need to understand some basic principals of Twine 2. In this example we are using Twine 2 Harlowe 2.0.0.

The most simple command in Twine is the passage. Passages are denoted with double [ as follows:


Passages can also have a description or label. The direction of the arrow <- or -> determines which part of the code is the passage and which part is the passage. Look here is a label that points to our passage in this code example:

[[look here->Passage]]

Another major component of our combination lock is Twine's (set:) functionality. The set command itself is very straight forward you set a variable to a value. Strings need to be encapsulated in quotation marks, numeric values do not:

(set: $variable to 4)
(set: $variable to "Four")

The final major structure to understand is Twine's if structure. The basic format is (if:)[Hook] but it can also be combined with (else-if:) or (else) as follows:

(if: $variable is 4)[ Look I'm 4!]
(else-if: $variable is 3)[ Look I'm 3!]
(else:)[ Look I'm 2!]

Here we test the value of our variable, if its 4 we process the first statement, if that resolves false we move to the second else-if, and if that also resolves false we move to the default, or else, parameter.

We will go over some of the other macros found in Harlowe as we work through our project.

Step 2: Checking Our Story Format

The first thing we need to do before we dive in is check our story format and make sure everything is in sync. Open your story file and click on the arrow next to your story name. This will bring up the story menu. Choose "Change Story Format". In the next screen make sure you choose Harlowe 2.0.0 (or later).

Step 3: Overview of the Story

Our combination lock is actually built using only 5 passages. Of these 5 passages two of them exist as places, that is they don't effect the lock itself, but they are locations that the player can visit, or be directed to. Note that I could have just presented the combination lock in the introduction screen rather than taking the player out of the world and into the lock, and I could have also created the lock directly into the passage rather than having it exist on its own, I'll cover my thought process on these pieces as we build our lock. We will go through the story passage by passage, starting with the special passage Startup.

Step 4: Startup - Getting Going

The Startup passage in Twine is a special passage. It contains information that you want to process before your story gets going, outside of the story itself. The name of the passage does not matter, as long as it contains the tag startup. Tags are methods of identifying passages in Twine, for example if you want to set the background color to blue, but only for certain passages you could create a blue tag that twine could then evaluate and set the color based on whether the tag exists in a particular passage or not.

(if: (passage:)'s tags contains "blue")[(background: blue)[Blue]](else:)[(background: white)[White]]

In our start up tag we are setting a handful of variables:

(set: $filler_text to "You hold the combination lock in your hand. It is a series of 5 wheels, numbered 0 to 9")<br>(set: $locked to 1)
(set: $x to 1)
(set: $dial to (a:0,0,0,0,0))

I am setting $filler_text here simply to show that in Twine you can use variables to display text into your story, so that if you have a repetitious bit that you want to repeat you can store it in a variable and recall it at will. I am setting the initial state of the padlock to locked, which can be evaluated to determine if we have entered a correct combination or not. This will come into play in our lock passage. The $x variable will be used to determine the number of 'dials' that we have turned on our lock. Finally we use the array variable to set our initial combination to 0-0-0-0-0. The syntax for an array is as follows:

(set: $new_array to (a: "Value 1",4,"Value 2",7))

Which would produce an array that contains:

Value 1, 4, Value 2, 7

Twine uses the 1st, 2nd, 3rd, 4th and so on values to reference an array's index. You can also access it programmaticly with $new_array's $x, as you will see in the lock passage.

Step 5: Introduction - the Starting Passage

The introduction passage serves three purposes. One it is a landing place when you first launch the story. It sets the stage for the lock and its environment. Two it is a landing page if you fail to enter the correct combination. Three it is the entry point for the lock passage. Lets look at it in detail:

(if: $x >= 6)[The lock failed to open, you step back and think for a minute.
(set: $dial to (a: 0,0,0,0,0))
(set: $x to 1)]

In this block, we are using a variable $x as a counter for the number of digits on our combination lock. Since the lock kicks you back to this passage should you fail to enter the code we need something to listen for that action, and since you've entered an incorrect code it needs to reset the combination lock. We accomplish that with two set: commands, one to reset our array to all zeros and one to set our dial counter back to 0.

You are standing in front of a large shipping container. A Sea-Con if you will, with orange rusted sides and a very very large combination lock on the door. If only you could get it [[open]].
Scratched onto the name plate is a crude 5 digit number: //13378//
Fiddle with the [[lock]]

In this block we are simply setting our stage, and we've provided links to two passages, one leads to our shipping container, via the word open and the other leads to our lock.

Step 6: The Lock - Our Meat and Potatoes

The lock passage is the heart of this demo, and its where all the action is. The only new concept here is Twine's (live:) concept. The (live:) block automatically refreshes any code that is inside the [hook] attached to the (live:) stanza. You need to specify the speed of the refresh, in the time parameter when you start the (live:) code. The basic syntax for a (live:) stanza is:

{(live: time)[hook]}

Let's look at this in context:

(print: $filler_text)
{(live: 0.5) [
(if: $x is <= 5)[The lock is set to: (print: $dial.join("-"))
(if: $dial's 1st is 1 and $dial's 2nd is 3 and $dial's 3rd is 3 and $dial's 4th is 7 and $dial's 5th is 8)[
(set: $locked to 0)
(goto: "open")](else:)[
(goto: "Introduction")]
(display: "Dials")

The first thing is that we display our filler text, again this could have been simply typed into this passage, but for the sake of demonstration I made it a variable. Then we enter into our live code. This is the code that will be automatically refreshed, in this case every 1/2 a second. The first thing that we do is make sure that we have not overrun the number of dials in our combination lock. If we haven't it displays the current combination entered into the lock. Note the use of a .join which will connect each array element with the character provided, in this case a dash (-). To verify that we have (or have not) entered the correct combination we have a complex if statement that checks each position of the array to determine if the array contains the correct combination, and if it does it changes the value of the locked variable to 0, or unlocked, and sends the story to the open passage. Finally if we've entered an incorrect code the story moves back to the introduction.

Outside of the live block we display the dials of our combination lock. For brevity I have listed just one of the ten dials here:

(link-repeat: "Zero")[ (set: $dial's $x to 0) (set: $x to it +1)]

We use the (link-repeat:) macro so that we can click on our dials over and over again. A (link-repeat:) takes the form of:

(link-repeat: "Link Name")[Hook]

When you click on a dial we set the position in our array that corresponds to the variable $x to the value that we clicked on. In this case we have the 0 (zero) dial. Depending on the value of $x one of the members of our array will now be set to 0. This is why its critical to make sure that $x is set correctly each time via the introduction and startup passages. Lastly we increment $x to move the array pointer to the next index. This will also eventually trip the correct combination or incorrect combination (if:) statements in our {[live:]} code.

Step 7: Open Up - the Successful Conclusion

The last passage in our story is our Open passage. This passage simply checks to see if the $locked variable is 0 (unlocked) or 1 (locked) and displays some text to player about its status.

(if: $locked is 1)[ You try to open the container, but its securely fastened.]
(else:)[ You pry open the door, the metal groans in protest as you heave the door open. The container is filled with rubber chickens.]

You could do something really diabolical and put another lock inside the container, the code is very flexible and by using different arrays with different combinations you can recycle this code to create any number of locks!

Step 8: Getting Out of Dodge

Now that you've created this fantastic lock what to do with it? To export it out of Twine click on the story menu again. At the bottom of the menu is Publish to File this exported document contains all your Twine source code as well as as the Twine 2 engine with the Harlowe 2.0 interpreter. Upload this file to your hosting provider and navigate to the URL and you'll be able to play your story!

For ease of use I often rename my story to index.html and store it in a folder with a name that matches the project file, that way a user can simply browse to the folder and the game will load, rather than having to remember unique indexes for all my stories.

Note that a successful export should be around 245kb or greater, the Twine 2 source is about 240kb plus your source code.

Step 9: What Next?

So where do you go from here? As you can see from the image on this step that was the first combo lock I built. Cumbersome! Some things to do from here:

  • Refinement, the overall story is a proof of concept, and there are some rough edges and unnecessary code
  • Hide the combination better!
  • You could use letters instead of numbers, or a combination of both.

To try the story file out you can view the demo here:

I've also attached the story source as a file if you'd like to review the whole thing.