A wizard with his arms outstretched casting a magic spell. From the boxcart of the Atari game, Brain Games

All stories have an ending and the same is true for interactive fiction. In some stories, the ending may be an expression of feelings or the summation of themes. For others, the story concludes the when the protagonist disarms the bomb or sends their antagonist down a volcano.

Twine has no concept of an ending. Twine sees a story as a connected series of passages. The Harlowe story format provides you with a rich assortment of tools to build your interactive adventures. Yet, there are no tools for writing conclusions.

Writing an ending is up to you. And by you, I mean you – the person reading this tutorial. Your story may be an experience that has no conclusive ending. It might be something that twists in on itself. Or, it might be a full fledged game with definite winning and losing conditions. It’s your choice.

In the case of this tutorial, you will have two definitive endings. The player either collects all the items and escapes, or Bernie catches them. After the game’s completion, everything starts over. This makes sense because this story is really a game, but a game is just one application of Twine.

Catching the player

In the previous tutorial, you created an NPC that wandered around the game map. Of course, the NPC doesn’t do anything. It’s time to add some interactivity. To get started, open your project in progress or download the starter project for this tutorial:

In the following code, you’ll send the player to the Bernie Encounter passage. This is really just a static meeting, but you can add a conversation tree in your own story. Open the Move Bernie passage. At the top of your code, just underneath the second line of code, add the following:

  (if: $currentLocation is $bernieLocation) [
    (go-to: "Bernie Encounter")
  ]

Near the bottom of your code, add the following code:

  (if: $currentLocation is $bernieLocation) [
    (go-to: "Bernie Encounter")
  ]

It should look like the following:

This shows the Move Bernie passage with the new code added near the bottom of the passage.

You are adding this check in two places. At the top of the passage, the code checks to see if the player walks into Bernie. At the bottom of the passage, it then checks to see Bernie walks into the player.

Now create a new passage called Bernie Encounter. Add the following:

Bernie steps out of the shadows holding a steaming pile of chili. He puts a blackened spoon under your nose and the smell tightens your stomach. 

"Go ahead," he says pushing the spoon to your mouth. "Try it. Everyone's been raving about it."

"I'd love to," you say. "But I have to hit the bathroom."

Bernie steps back somewhat put out. "Okay," he says. "Five minutes. But after that, no excuses."

He steps into the darkness. As he turns, you run back to the camp entrance. When you get there, he's nowhere in sight.

[[You exhale a sigh of relief.]]

Now run the story. Once you enter the Camp Entrance and head down the Dusty Road toward the center of the camp. You’ll instantly run into Bernie. Except there is a problem. You were never warned that Bernie was nearby.

Warning the player

You’ve now reached an edge case. Bernie wasn’t close, yet you both stepped into the same location.

Remember the previous tutorial. Here the player is in location A and Bernie is in Location C. Since Bernie isn’t in Location D or in location B, the player will not hear Bernie’s whistle. The player thinks they are safe so they move to Location B. Bernie also moves to Location B. The player is caught.

You need to create an exception. The player should always receive a warning that Bernie is close. If the player runs into Bernie without being warned, Bernie should move return to his original location and then warn the player. It’s not “fair” but it makes for a better player experience.

Open the Setup passage. Add the following variable:

(set: $hasPlayerBeenWarned to false)

Now open the Bernie Nearby passage. Update it to the following:

(if: $isBernieNearby is true) [
  (print: "<br><br>You hear a soft whistle and footsteps in the dark.")
  (set: $hasPlayerBeenWarned to true)
]

Here you create a variable to track the player warning and set it to true once warned.

Open the Move Bernie passage again. Find this line of code:

(set: $previousBernieLocation to $bernieLocation)

Replace this line and all subsequent lines of code with the following code:

(if: $currentLocation is $newLocation) [
  (if: $hasPlayerBeenWarned is true) [
    (set: $bernieNearby to false)
    (set: $hasPlayerBeenWarned to false)
    (go-to: "Bernie Encounter")
  ]
  (else: ) [
    (set: $bernieNearby to true)
  ]
]
(else: ) [
  (set: $previousBernieLocation to $bernieLocation)
  (set: $bernieLocation to $newLocation)
  (set: $bernieNearby to false)
]
(print: $newLocation)
]}

The complete passage will look like the following:

This shows the Move Bernie passage with the new code added to it.

This code block starts with an (if :) macro. It first checks to see if the player has entered Bernie’s location. If the player has been warned that Bernie is nearby, the player will face Bernie. It resets some variables in case it is the first meeting.

If the player hasn’t been warned and they stumble into Bernie’s new location, then Bernie remains at his previous location. The player is then warned that Bernie is nearby.

Finally, in the case where the player does not encounter Bernie, Bernie moves as normal. Now play the story. and take the Dusty Road into the campground.

This shows the Dusty Road passage being played except this time, there's a warning that Bernie is nearby.

This time, instead running into Bernie, you’ll receive a proper warning.

Losing the game

To lose the game, Bernie needs to catch the player two times. For the first time, Bernie warns the player. On the second time, the player loses the story. To get started, you’ll create two variables to track the amount of encounters along with the max encounter amount. By setting these to variables, you can adjust these settings.

Open the Setup passage. Add the following to your variables:

(set: $bernieEncounters to 0)
(set: $maxBernieEncounters to 2)

The $bernieEncounters variable could actually be a true/false value, but by using a number in conjunction with the $maxBernieEncounter variable allows you to customize the amount of Bernie encounters per play-through. For example, on easy mode, you may allow three encounters but hard to only allow one encounter.

Now to open the Bernie Encounter passage. At the top of the passage, add the following:

(set: $bernieEncounters to $bernieEncounters + 1)
(if: $bernieEncounters is $maxBernieEncounters) [
	(go-to: "End Game")
]

You’ll write the End Game passage momentarily. For now, delete the “You exhale a sigh of relief” line. Add the following code:

(link: "You exhale a sigh of relief.") [
  (set: $bernieLocation to (either: ...$bernieStartingLocations))
  (go-to: "Camp Entrance")
]

This “resets” the game. The player is sent to the Camp Entrance whereas Bernie is sent to another random location.

Now create a new passage. Call it End Game. Add the following to the passage:

Bernie steps out of the shadows with his bowl of chili outstretched before him. "Oh hey!" he says as he closes in on you. "You ready to try my chili?"

You shake your head, but Bernie is too fast. He closes the gap in instant. He offers a dark spoon filled with a toxic sludge of grey beans and malformed tomatoes.

You open your mouth to say "no", but Bernie jams the spoon between your teeth. You feel the sludge trickle down the back of your throat. Your gut cramps at once. 

"Oh, you don't look very well," Bernie says. "You should probably go to the bathroom. And if you see anyone, let them know I still have plenty of leftovers."

You stumble away from Bernie feeling the chili work its "magic". You hope that you reach the bathroom in time.

Start over.

You may be tempted to create a link to the Introduction passage, but you’ll end up wrecking your story.

Implications of starting over

You would think starting over means sending the player to very first passage, but this is a dynamic story. Your story keeps track of a lot of different variables. Even the player is starting over, the previous story is actually still in progress. While the player will start in the Camp Entrance, they will still have their inventory items from their last game. Bernie will be located at his last location. The player will have already met Bernie twice.

This means your game “state” is corrupted. State in this context refers to all variables in your story. The player expects them to be reset, but you’ll be using old data. You need to reset those variables to the starting state.

As you can imagine, resetting every variable is quite tedious and error prone. Thankfully, Twine provides the (restart :) macro. This macro reloads the entire web page. It’s the equivalent of pressing reload in your browser.

With the End Game passage open, change the “Start Over” text to the following:

(link: "Start Over.") [
  (restart:)
]

Now open the Setup passage and the set the $maxBernieEncounters to 1.

(set: $maxBernieEncounters to 1)

Run the story. Move towards the camp center to run into Bernie. You’ll get the end game passage.

Click the Start Over link. This time the story starts over with a fresh state.

Winning the game

Now for the fun ending. Open the win passage.

As you turn the keys, you hear in the distance, "Wait, try my chili!". You start the pickup and drive off into the night. You make it to the hospital in time with the recipe. The doctor's reverse-engineer the chili recipe and save all the campers from the stomach churning nightmare.

When police go to the camp to get Bernie, they find it empty. Bernie is gone for good. Or is he?

You win!

(link: "Play again") [
  (restart:)
]

To test this win condition, open the Setup passage. Update the following:

(set: $maxBernieEncounters to 100)

This allows you to keep running into Bernie without consequence. Run the story. Collect all the items and escape.

You finished the story! Congrats. Before moving forward, change the $maxBernieEncounters back to 2. Also, in the Move Bernie passage, delete the (print: ) macro that prints Bernie’s location.

Where to go from here

As you can see, creating a “game over” screen requires a bit of thought. If you got stuck on this tutorial or want to see the completed code, you can download the final project for this tutorial here:

At this point, you may be interested in publishing your story, but while all the coding is done, it still looks kind of ‘meh’. It would be nice to incorporate some images into it. Also, the story features an undo arrow provided by default. It’d be nice to get rid of that.

In the next two tutorials, you’ll add some polish to the story. That said, you already have enough skills and knowledge to write your own. Give it a shot! Then, when you need a break, meet me in the next tutorial.


Discover more from Jezner Blog

Subscribe to get the latest posts sent to your email.

By Brian Moakley

Brian Moakley is a writer and editor who lives amongst the quiet hills in New England. When not reading tales of high adventure, he is often telling such stories to all who will listen.

Related Post

Leave a Reply