Creating a basic maze in Twine

One of the most common Twine projects I see are people wanting to create a role-playing experience. Be it a fantasy setting with brave knights of yore, a modern-day investigation of a murder, or even a post-apocalyptic space station, there is a clear desire to make a nonlinear space for players to explore in Twine stories and games. And, of course, often an author just wants an exciting setting for their characters and players.

Jan7--1

Last year, I experimented with a project I created in Twine called Cnossus: Lost in Darkness where I had players wander around a maze. It was based on an idea I had to reduce the total number of passages in a Twine story to only a handful, each representing the cardinal directions and conditionally linking to each other based on the player’s current location in the maze. At the time, I released it as one in a number of attempts to make a Twine story, game, or experiment per week.

However, with the start of a new year and with Twine 1.4 now out, I thought I would re-visit that game and explain in detail how to re-create what I did to make it.

var maze =
[[0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,0,1,1,1,1,1,0],
[0,0,0,1,0,0,0,0,0,1,0],
[0,1,0,1,1,1,1,1,0,1,0],
[0,1,0,0,0,0,0,1,0,1,0],
[0,1,1,1,1,1,1,1,0,1,0],
[0,0,0,0,0,0,0,1,0,1,0],
[0,1,0,1,1,1,1,1,1,1,0],
[0,1,0,1,0,0,0,1,0,0,0],
[0,1,1,1,0,1,1,1,1,2,0],
[0,0,0,0,0,0,0,0,0,0,0]];
view raw maze.js hosted with ❤ by GitHub

First, I used an array of arrays for the maze itself. In JavaScript, there are a limited number of data structures. Beyond functions, objects, and the various number types, there really is only arrays for storing connected data in one structure. Here, I used a two-dimensional array, linking the position of the player to the indices of the first array to the second array.

Each location in the maze was a (X, Y) placement. Starting at (1,1), the player moved a single position in one direction, if they could, with the code increasing either the x-position or the y-position each time. Then, a series of Boolean values (1 or 0) were set, one for each of the directions.

var x = 1;
var y = 1;
$posx = 1;
$posy = 1;
macros['navigate'] =
{
handler: function(obj, fnc, val)
{
x = $posx; y = $posy;
if(maze[y-1][x] eq 1) {
$North = 1;
} else if(maze[x][y+1] eq 2) {
$Exit = 1;
} else {
$North = 0;
}
if(maze[y+1][x] eq 1) {
$South = 1;
} else if(maze[x][y-1] eq 2) {
$Exit = 1;
} else {
$South = 0;
}
if(maze[y][x-1] eq 1) {
$West = 1;
} else if(maze[x-1][y] eq 2) {
$Exit = 1;
} else {
$West = 0;
}
if(maze[y][x+1] eq 1) {
$East = 1;
} else if(maze[x+1][y] eq 2) {
$Exit = 1;
} else {
$East = 0;
}
}
}
view raw maze.js hosted with ❤ by GitHub

In practice, this meant calling a macro called ‘navigate’ with the loading of a new passage, checking each time the position of the player in the maze. If the space (the [x][y] position) to either the North, South, East, or West was a ‘1’, that meant the player could move there and thus the direction was enabled (set to ‘1’, true). Because the exit, a ‘2’, could appear in any direction each direction conditional had to also check for this value.

<<silently>>
<<set $CreateMaze =
function()
{
var maze =
[[0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,0,1,1,1,1,1,0],
[0,0,0,1,0,0,0,0,0,1,0],
[0,1,0,1,1,1,1,1,0,1,0],
[0,1,0,0,0,0,0,1,0,1,0],
[0,1,1,1,1,1,1,1,0,1,0],
[0,0,0,0,0,0,0,1,0,1,0],
[0,1,0,1,1,1,1,1,1,1,0],
[0,1,0,1,0,0,0,1,0,0,0],
[0,1,1,1,0,1,1,1,1,2,0],
[0,0,0,0,0,0,0,0,0,0,0]];
var x = 1;
var y = 1;
$posx = 1;
$posy = 1;
macros['navigate'] =
{
handler: function(obj, fnc, val)
{
x = $posx; y = $posy;
if(maze[y-1][x] eq 1) {
$North = 1;
} else if(maze[x][y+1] eq 2) {
$Exit = 1;
} else {
$North = 0;
}
if(maze[y+1][x] eq 1) {
$South = 1;
} else if(maze[x][y-1] eq 2) {
$Exit = 1;
} else {
$South = 0;
}
if(maze[y][x-1] eq 1) {
$West = 1;
} else if(maze[x-1][y] eq 2) {
$Exit = 1;
} else {
$West = 0;
}
if(maze[y][x+1] eq 1) {
$East = 1;
} else if(maze[x+1][y] eq 2) {
$Exit = 1;
} else {
$East = 0;
}
}
}
}
>>
<<print $CreateMaze()>>
<<endsilently>>
view raw maze.js hosted with ❤ by GitHub
<<display "CreateMaze">>
The entrance of the cave stands before you.
[[Enter Cave]]
view raw maze.js hosted with ❤ by GitHub

Since the loading timing of script passages in Twine cannot be guaranteed, I also needed to use an older Twine hack of creating a variable, making it a function, and then calling ‘print’ on it immediately after it is set. Having another passage ‘display’ it, such as the Start passage, triggers this process and loads everything on demand. (It is not always an elegant solution to the loading problem, but it is one that will work.)

<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
view raw EnterCave.js hosted with ❤ by GitHub

From Start, I used a “Enter Cave” passage to link to each direction, if after calling the macro ‘navigate’ they were available. For each directional passage, they updated either the x-position or the y-position accordingly and then called ‘navigate’ themselves each time. The player then moved around the maze until they came across a link to the ‘exit’ passage.

As a final touch, I used the Jonah story format and the few lines of JavaScript that forces only one passage to be displayed at a time (by removing all others from the storage area each time).

(For those wanting to use the below complete code, be sure to copy and paste it into a file with a “twee” suffix. While I can have GitHub show you Twine code, it does not recognize the file format itself and I have thus needed to select ‘JavaScript’ for it to highlight some of the code.)

:: Start
<<display "CreateMaze">>
The entrance of the cave stands before you.
[[Enter Cave]]
:: CreateMaze
<<silently>>
<<set $CreateMaze =
function()
{
var maze =
[[0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,0,1,1,1,1,1,0],
[0,0,0,1,0,0,0,0,0,1,0],
[0,1,0,1,1,1,1,1,0,1,0],
[0,1,0,0,0,0,0,1,0,1,0],
[0,1,1,1,1,1,1,1,0,1,0],
[0,0,0,0,0,0,0,1,0,1,0],
[0,1,0,1,1,1,1,1,1,1,0],
[0,1,0,1,0,0,0,1,0,0,0],
[0,1,1,1,0,1,1,1,1,2,0],
[0,0,0,0,0,0,0,0,0,0,0]];
var x = 1;
var y = 1;
$posx = 1;
$posy = 1;
macros['navigate'] =
{
handler: function(obj, fnc, val)
{
x = $posx; y = $posy;
if(maze[y-1][x] eq 1) {
$North = 1;
} else if(maze[x][y+1] eq 2) {
$Exit = 1;
} else {
$North = 0;
}
if(maze[y+1][x] eq 1) {
$South = 1;
} else if(maze[x][y-1] eq 2) {
$Exit = 1;
} else {
$South = 0;
}
if(maze[y][x-1] eq 1) {
$West = 1;
} else if(maze[x-1][y] eq 2) {
$Exit = 1;
} else {
$West = 0;
}
if(maze[y][x+1] eq 1) {
$East = 1;
} else if(maze[x+1][y] eq 2) {
$Exit = 1;
} else {
$East = 0;
}
}
}
}
>>
<<print $CreateMaze()>>
<<endsilently>>
:: template [script]
History.prototype.originalDisplay = History.prototype.display;
History.prototype.display = function (title, link, render)
{
if ((render != 'quietly') && (render != 'offscreen'))
removeChildren($('passages'));
this.originalDisplay.apply(this, arguments);
};
:: North
<<set $posy = $posy - 1>>
<<print "(X: " + $posx + "; Y: " + $posy + ")">>
<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
<<if $Exit eq 1>>
[[Exit]]
<<endif>>
:: StoryTitle
Basic Maze in Twine
:: West
<<set $posx = $posx - 1>>
<<print "(X: " + $posx + "; Y: " + $posy + ")">>
<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
<<if $Exit eq 1>>
[[Exit]]
<<endif>>
:: Enter Cave
<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
:: East
<<set $posx = $posx + 1>>
<<print "(X: " + $posx + "; Y: " + $posy + ")">>
<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
<<if $Exit eq 1>>
[[Exit]]
<<endif>>
:: Exit
This is the exit.
:: StoryAuthor
Dan Cox
:: South
<<set $posy = $posy + 1>>
<<print "(X: " + $posx + "; Y: " + $posy + ")">>
<<navigate>>
<<if $North eq 1>>
[[North]]
<<endif>>
<<if $South eq 1>>
[[South]]
<<endif>>
<<if $West eq 1>>
[[West]]
<<endif>>
<<if $East eq 1>>
[[East]]
<<endif>>
<<if $Exit eq 1>>
[[Exit]]
<<endif>>
view raw Maze.js hosted with ❤ by GitHub