Writing about Horror, Sci-Fi and Interactive Fiction

Twine 2 Tutorial: Branching Your Narrative With the If Macro

In this tutorial series, you defined some passages, created some variables and you even developed a simple inventory system. Unfortunately, your story doesn’t react to player choice.

For example, when the player starts your story, they choose the difficulty level of the story. In easy mode, the story places objects in fixed locations. In hard mode, the story places objects in random locations.

That’s just one choice and your story has lots of other choices to consider. Thankfully, there’s a macro for that; the (if: ) macro.

In this tutorial, you won’t be implementing easy or hard mode just yet. Don’t worry, you’ll get to that soon. Rather you’ll continue to improve on the inventory that you made in the previous tutorial.

Making Choices with the (if:) Macro

The (if:) macro allows you to make choices. This is a powerful macro. In fact, after this tutorial, you will be able to create some feature-rich stories.

The (if:) macro asks a simple question that boils down to a simple true or false answer. Does the player have the flashlight? Did the player select easy mode? Has the player encountered Bernie?

Notice these questions are quite specific as opposed to being open-ended. By using the (if:) macro combined with variables, you can create elaborate logic chains.

Okay, okay – enough theory. It’s time to put the (if:) macro to use. Note, if you are just starting, you can download the starter project here:

Fixing the Inventory

In the previous tutorial, you implemented a very basic inventory system but it had bugs. First, the Camp Entrance passage always informs the user about keys being on the ground, regardless as to whether the user has picked them up.

Next, the passage tries to display the player’s inventory but shows an error message when the player is carrying nothing. You’ll start with the first one.

Update the text, “On the ground, you see a set of [[keys]]”, to the following:

(if: ) [
  On the ground, you see a set of [[keys]].
]

It should look like the following:

A screenshot showing the if macro in use.

You’ll notice a few things. First, if (if: ) macro doesn’t do anything yet. There is no condition. Next, you’ll notice the brackets: [ ]. If the condition is true, everything between the brackets will be evaluated.

The brackets are known as a hook. You’ll learn more about hooks later in this series. They are a very powerful part of this story format.

Note: If you are coming from another programming language like Javascript or Swift, you may think of the brackets to be the same as braces. This is not the case at all. In Harlowe, the brackets (hooks) work very much like a closure. That is, you can assign them variables and call them on demand. If you think of them just like braces, you’ll be very confused down the road.

Now to add the condition for the (if:) macro. You need to check for a true or false condition. In your case, you’ll check to see the user’s inventory contains the keys.

Add the following:

(if: $inventory contains "keys") [
  On the ground, you see a set of [[keys]].
]

It should look like the following:

A screenshot using the if macro to check the players inventory.

Now play your story. This time, you don’t see anything but an error message.

A screenshot showing the story in progress with an error.

What gives?

Checking for Items Not Picked Up

You just ran into another bug; only, in this case, the error wasn’t a coding bug, but a logic bug. You just wrote some code to show the keys on the ground in the case that the player is carrying the keys.

In summation, you must have the keys to pick up the keys.

That’s … kind of a problem. Twine thinks it’s fine since all the code is valid. The player, on the other hand, will think otherwise.

You want to display the passage so long as the player DOES NOT have the keys. This can easily be fixed. Update the code to the following:

(if: $inventory does not contain "keys") [
  On the ground, you see a set of [[keys]].
]

It should look like the following:

A screenshot of code showing the if macro checking if the inventory doesn't contain keys.

Now run the story and select Easy Mode. You’ll still get another error, but don’t worry about that. You’ll handle that in a moment.

A screenshot of the story show an error.

Click on the keys and pick them up. When you are brought back to the Camp Entrance passage, you’ll notice that the keys are gone. Mind you, you’ve picked up an extra line break.

A screenshot of the story showing lots of whitespace.

To quote Charlie Brown, “good grief!”

Managing Spaces

This is where the fun begins and by fun, I mean crying. Working with line breaks in Twine can be a maddening experience. Twine does its best to infer your line breaks, but it’s making its best guess.

In my experience, it’s best to manually set your breaks and you can do this with the HTML element <br>. A line break will be placed wherever you place the element.

First, you need to remove all whitespace by adding braces.

{
(if: $inventory does not contain "keys") [
  On the ground, you see a set of [[keys]].
]
You are carrying: (print: $inventory's 1st)
}

It should look like the following:

This will remove all line breaks from the passage. Now, manually add in the individual line breaks.

{
(if: $inventory does not contain "keys") [
  <br>On the ground, you see a set of [[keys]].<br>
]
<br>
You are carrying: (print: $inventory's 1st)
}

Now when you play your story, you’ll see the correct line breaks.

A screenshot of the story with the whitespace gone.

Getting the Size of Things

You still have one other error. If you don’t pick anything up, the story displays an error.

A screenshot of the story showing an inventory error.

You are trying to display the first element of the array, but the array is empty. That’s the root cause of the error.

As you learned, an array is a collection of things. It can contain numbers, strings and even other arrays. Being a collection, you’ll often want to know how big it is. For this, you use the length property.

Every array has access to this property. As you add items, the length increases and decreases when remove items.

Open the Camp Entrance passage and change the macro the bottom:

You are carrying (print: $inventory's length) item(s).

Your code should look like the following:

A screenshot of code printing out the inventory's length.

Now run your story. Select easy mode and you’ll see the error is gone. Instead, you’ll see that you aren’t carrying any items.

A screenshot of the story showing zero items

Now click the keys and pick them up. Now you’ll see that you are carrying one item.

A screenshot of the story showing 1 item.

While nice, you still want to show the items. To do this, you’ll need to make a comparison.

Making Comparisons with the If Macro

When working with numbers, you will oftentimes make comparisons. For example, if the current temperature is less than 0°F (-17°C), then you might print out a message, “you are cold”.

If a player has over a hundred gold pieces, then they can buy the magic sword. Or if a player’s hit points equal zero, then the story should end. You can do this with the (if:).

Mind you, this isn’t exclusive to just the Harlowe story format. This is a general part of programming languages like C, C++, Java, Swift, and many many others.

In your case, you want to check if the first element exists. To do this, you’ll want to check how many items are contained in the player’s inventory.

Open the Camp Entrance passage, and update your inventory message to the following:

You are carrying: (if: $inventory's length > 0) [  (print: $inventory's 1st) ] 

Wow – that is some code. The (if:) macro first checks to see if the player is carrying anything. If they have one or more items, it will print out the first item.

It should look like the following:

A screenshot of code showing the if macro

The length property belongs to the array. It just checks to see how many items are contained within it. Also, notice the greater-than sign (>). You can also use the less than sign (<).

Sometimes, you want to do a greater than or equal comparison. For instance, you might want to do an age check of twenty-one years old and older. For example:

(if: $age >= 21) [

Conversely, you can less than or equal operator:

(if: $temperature <= 32) [

Or, if you want to find the exact number, you could do the following:

(if: $hitPoints is 100) [

You can also use compound checks. For instance, you may want to check if a person is a certain age and has a certain amount of money:

(if: $age is 21 and $money >= 100) [ 

In that case, the hook will only be evaluated if both conditions are true.

You can also check if either condition is true:

(if: $age is 21 or $money >= 100) [

Now, the age has to be twenty-one or the money must be greater or equal to one hundred. If either is true, the hook will be evaluated.

For more information, see this great cheat sheet.

Now, run the story. Now the error is gone!

A screenshot of the story showing an empty inventory.

That’s good, but not great. This can be improved with some alternative if statements.

Branching Your If Macro

In your story, you created an if statement to check if the player is carrying an item. This allowed you to get rid of that purple error message. But in cases where the player wasn’t carrying anything, it prints empty text.

A better solution is to print the text: nothing.

There are a couple of solutions for this. First, you could put one (if:) macro underneath another (if:) macro. A better solution is to use an (else-if:) macro. This works just like another (if:) macro.

(if: $age < 1) [
 print("infant")
](else-if: $age >= 1 and $age <=3) [
  print("toddler")
](else-if: $age >= 4 and $age <= 12) [
  print("child")
]

The code is evaluated at the top and goes through each (else-if:) macro until it finds the appropriate condition. If the age variable is set to 25, none of the conditions will apply.

You can apply a default condition using the (else:) macro. This becomes a catch-all.

(if: $age < 1) [
 print("infant")
](else-if: $age >= 1 and $age <=3) [
  print("toddler")
](else-if: $age >= 4 and $age <= 12) [
  print("child")
](else:) [
  print("you are older than 12")
]

By using these macros, you create some complicated logic. One thing to keep in mind, you can put (if:) macros inside of your (if:) macro.

(if: $gold > 100) [
  (if: $charisma > 14) [
    print("you get a discount!")
  ]
]

As you can see, things can get complicated pretty fast!

Adding Nothing

Now it’s time to add an (else:) macro. Open the Camp Entrance passage, and update your inventory message to the following:

You are carrying: (if: $inventory's length > 0) [  (print: $inventory's 1st) ] (else:) [
  nothing
]

It should look like the following:

A screenshot of code showing the updated if macro.

When you run your story, you’ll now see that you are carrying nothing. This is a much better approach.

A screenshot of the story showing the corrected inventory.

Nice work!

Where to Go From Here?

While you didn’t write a lot of code, you learned a TON of new information. The (if:) macro is critical for creating complex stories. Believe it or not, with the combination of variables and branching logic, you have the capabilities to create any type of game.

But things are just getting interesting. You are using an array to hold inventory items and are printing out each element of the array. As you add items, this will grow old pretty fast.

Thankfully, you have loops the help you out with that and you’ll be diving into them in the next tutorial. See you then!