How to Write a Text Adventure.
Years ago, I got a computer and after time acquired some games for it. A couple of the games where text adventures. The interface was simple. Text. The game would start with a description of where you were and possible directions you could go. You could type in commands. For example "use rock", "go west", "look". Sometimes the game would take single characters like "I" for Inventory or E to go east. There would be puzzles along the way until you died or finished the game.
Let's get coding.
No.
Instead let's start with a design.
You need to ask yourself, what is the story?
A text adventure is a story you tell by guiding the player. Decide what genre, a western?,sci-fi?, mystery?,fantasy? Then what happens? Who is the player? Decide what the ending is going to be. Save the world! or just make it out alive. But you need an ending to know how to get there.
Let's get coding.
No.
We should Design the layout and puzzles.
This is a old school game so let's go old school with a pencil and paper. Draw a box on the paper near the center. This is the starting point. This is where the player begins. Then add pathways and other box destinations as you see fit. Give and ID (number or letter) to each of the destinations and label the directions a player can move (N,S,E,W). Maybe you have a destination blocked, a fallen tree, an ogre, a locked door that will require the player to use an item on it. You may have multiple obstacles to get the item to get past it.
Is there a time element. Do you limit the number of moves from the beginning or perhaps after the player unlocks something.
Let's get coding.
No.
Not yet.
This article not a programming tutorial. I am showing my approach. But there are many different ways of getting things done. I am writing this text as I am creating the text adventure. No code so far because the target platform has not been decided. I want people to be able to run the game in a browser. So decide where you want it to run and then figure out what languages are available. I have decided to use JavaScript. I thought about using Python and then running some kind of conversion to turn it into JavaScript but that seems like a kludge. So I will start with an HTML doc with a JavaScript in the document. I will use a TextArea, text input box and a button for hitting enter.
I will set up the HTML and JavaScript so that I can to break it apart later, if need be. Having everything in one document means I can test it in a browser without having to set up a server.
Let's get coding.
If you want to play the game without spoilers you should do that before proceeding.
First up create the HTML.
(Anytime you want to see the code open up the version of the page in a browser right click and view source.)
Now that we basic interface of text input and output we can move to the next step. I think it would be good to getthe"map" working. Each room or place in the "map" will need to know things. Each room will need to know its own id, description, directions the player can take and the rooms that those directions connect. I am going to use 0 to represent game over conditions, including death, times up or successful completion.
id,desc,dirs,connections
0,"Game Over","NSEW",(0,0,0,0)
1,"Bridge","W",(3)
2, "Captains Quarters","S",(3)
3, "Front Hallway", "NSEW",(2,4,1,5)
4, "Crew Quarters", "N",(3)
5, "Middle Hallway","NSEW",(6,8)
6, "Mess","NS",(7,5)
7, "Kitchen", "S", (6)
8, "Medical", "NS",(5,9)
9, "Lab", "N", (8)
10, "Cargo", "NSEW", (11,12,5,14)
11, "Left Fuel Stores", "SW",(10,15)
12, "Right Fuel Stores", "NW",(10,13)
13, "Right Engine Access" "NEW", (14,12,18)
14, "Back Hallway", "NSEW" (15,13,10,16)
15, "Left Engine Access", "SEW", (14,11,17)
16, "Engineering" , "E", (14)
17, "Left Engine", "E", (15)
18, "Right Engine", "E", (13)
I could create each room as an object, but I would like to keep this relatively old school. So this is going to be a series of arrays, all keyed on the room id. I reformatted the above data to be one array of arrays. I could write the code using only this array but I think it would be confusing trying to keep track of all the subscripts and location of arrays inside arrays.
(this is the final version)
roominput = [[0,"Game Over Area","NSEW",[0,0,0,0]],
[1,"Bridge","W",[3]],
[2,"Captains Quarters","S",[3]],
[3,"Front Hallway","NSEW",[2,4,1,5]],
[4,"Crew Quarters","N",[3]],
[5,"Middle Hallway","NSEW",[6,8,3,10]],
[6,"Mess","NS",[7,5]],
[7,"Kitchen","S", [6]],
[8,"Medical","NS",[5,9]],
[9,"Lab","N", [8]],
[10,"Cargo Area","NSEW", [11,12,5,14]],
[11,"Port Fuel Stores","SW",[10,15]],
[12,"Starboard Fuel Stores","NW",[10,13]],
[13,"Starboard Engine Access","NEW", [14,12,18]],
[14,"Back Hallway","NSEW", [15,13,10,16]],
[15,"Port Engine Access","SEW", [14,11,17]],
[16,"Engineering" ,"E", [14]],
[17,"Port Engine","E", [15]],
[18,"Starboard Engine","E", [13]]];
Now load the data into the individual arrays.
for (let i = 0; i < roominput.length; i++)
{
console.log(roominput[i]);
rooms.push(roominput[i][1])
availdirs.push(roominput[i][2])
destinations.push(roominput[i][3])
}
Now there needs to be a way to convert the player's choice to a room destination.
function roomDestination(roomid,dir)
{
//determine the position of the dir
// indexOf,search - nah go oldschool loop until you find it
// if only one destination then default to zero.
pos = 0
if (availdirs[roomid].length > 0)
{
for (let i = 0; i < availdirs[roomid].length; i++)
{
if (dir == availdirs[roomid][i])
{
pos = i;
}
}
}
// use the position to get the destination
return destinations[roomid][pos];
}
Add code to display the room info.
function showroom(roomid)
{
var strRoom = "You are in the " + rooms[roomid] + "\n";
var strRoomDirs = "There are door(s) to the ";
for (let i = 0; i < availdirs[roomid].length; i++)
{
switch (availdirs[roomid][i])
{
case "N":
strRoomDirs += "N(orth),";
break;
case "S":
strRoomDirs += "S(outh),";
break;
case "E":
strRoomDirs += "E(ast),";
break;
case "W":
strRoomDirs += "W(est),";
break;
}
}
// remove the last comma
strRoomDirs = strRoomDirs.substring(0,strRoomDirs.length - 1);
return strRoom + strRoomDirs;
}
Next create the way to move from room to room.
The playeronly needs to type a single character.
Test the direction is valid before moving.
function checkDir(roomid,dir)
{
validdir = false
for (let i = 0; i < availdirs[roomid].length; i++)
{
if (dir == availdirs[roomid][i])
{
validdir = true;
}
}
return validdir;
}
.. snippet in process funtion.
//check the length of input if single char valid values are dirs and I inventory or L for look
// for now just directions
if (strPlayerInput.length == 1)
{
strcommand = strPlayerInput.toUpperCase();
if (checkDir(playerroom,strcommand))
{
//now move the player and display the destination.
playerroom = roomDestination(playerroom,strcommand);
stroutput = showroom(playerroom);
}
else
{
stroutput = strPlayerInput + " is not a valid command."
}
}
else
{
stroutput = strPlayerInput + "Not a valid command."
}
During my testing I found I could not move where I should have been able to East or West from the middle hallway. I forgot to include those in the data. ( I corrected the above data)
Now there is complete movement around the map.
You are in the Bridge
There are door(s) to the W(est)
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Cargo
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Back Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Engineering
There are door(s) to the E(ast)
w is not a valid command.
You are in the Back Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Left Engine Access
There are door(s) to the S(outh),E(ast),W(est)
You are in the Left Fuel Stores
There are door(s) to the S(outh),W(est)
You are in the Left Engine Access
There are door(s) to the S(outh),E(ast),W(est)
You are in the Left Engine
There are door(s) to the E(ast)
You are in the Left Engine Access
There are door(s) to the S(outh),E(ast),W(est)
You are in the Back Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Cargo
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Mess
There are door(s) to the N(orth),S(outh)
You are in the Kitchen
There are door(s) to the S(outh)
You are in the Mess
There are door(s) to the N(orth),S(outh)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Medical
There are door(s) to the N(orth),S(outh)
You are in the Lab
There are door(s) to the N(orth)
You are in the Medical
There are door(s) to the N(orth),S(outh)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Captains Quarters
There are door(s) to the S(outh)
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Crew Quarters
There are door(s) to the N(orth)
s is not a valid command.
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
Now it is time to decide how to handle the puzzles. When it comes down to it a text adventure is a series of puzzles. Some of the puzzles will be required to be completed in sequence. The puzzles will be completed by the player typing commands like "use wrench" or a more compound "use wrench on pipe". So there is now a need for objects or items in the map. At this point I think there will be two types of items. Items that can be taken and others that are fixed. So at a minimum an object will need to have a description or name, an indicator it can be taken. There will also have to be something for the player to hold the items as well as a way to display the contents using "I" for Inventory.
Here is an example of the object being displayed.
You are in the Bridge
There are door(s) to the W(est)
You see:
navigation computer
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Mess
There are door(s) to the N(orth),S(outh)
You see:
dinner tray
You are in the Kitchen
There are door(s) to the S(outh)
You see:
chicken soup can
pot
stove
meat tenderizer
Now some of the objects can be changed after completing the puzzle. So add another array with an object modifier. I decided to reorder the items and directions.
You are in the Bridge
You see:
the navigation computer
There are door(s) to the W(est)
You are in the Front Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Middle Hallway
There are door(s) to the N(orth),S(outh),E(ast),W(est)
You are in the Mess
You see:
a dinner tray
There are door(s) to the N(orth),S(outh)
You are in the Kitchen
You see:
a full chicken soup can
an empty pot
the stove
a meat tenderizer
There are door(s) to the S(outh)
It is best tokeep the objects as a single word and use the modifier to extend its description.This simplifies theparsing of the puzzles.
(this is the finalversion)
objectsinput = [[0,"computer",0,"the navigation"],
[1,"can",1,"a full chicken soup"],
[2,"stove",0,"the"],
[3,"tray",1,"a dinner"],
[4,"computer",0,"the medical"],
[5,"lab-o-tron",0,"the"],
[6,"spanner",1,"a sonic"],
[7,"driver",1,"a tech"],
[8,"screw",1,"a"],
[9,"tenderizer",1,"a meat"],
[10,"opener",1,"a can"],
[11,"code",1,"ship's"],
[12,"line",0,"a disconnected fuel"],
[13,"stabilizer",0,"a hole in the Port fuel"],
[14,"coupler",0 ,"a disconnected fuel"],
[15,"nozzle",0,"a misaligned Engine"],
[16,"jet",0,"a misaligned Engine inlet"],
[17,"Pilot",0,"laying down is the sick and dizzy"],
[18,"Captain",0,"sitting in the corner is the babbling delirious"],
[19,"pot",1,"an empty"],
[20,"crates",0,"some stacks of"],
[21,"cure",1,"space sickness"],
[22,"hatch",0,"access"]
];
Now to handle player inventory. Implement a "take" and "drop" command. That means we need to parse the words from the input string. While doing this I realized it would help to have an Inventory and Look command implemented with "I" and "L" respectively.
The look command calls the showroom function. The inventory displays the playerinventory array. If the input value is more than one character then the processcommand function is called.
There is a case condition for handling the commands.
I created a funcion toconvert an object from text to id
Then I could create thefunction takeanobject another todrop an object.
Up to this point the code is all data driven. If I were to try to keep the rest of this data driven I would probably wind up writing my own scripting language. So going forward the rest of the code will be specific to my map and objects.
I will havebooleans for puzzles where I need to keep track of completed tasks. Some puzzles will be simple and some that will require multiple steps.
All the puzzles have been written. And I added code to scroll the textarea to make it more playable.
Youjust have toparse out the minimum words to determine if a puzzle has been solved.
By default the puzzles function will return“Nothing happens.”
Depending on the room parse outexpected verbs and objects. I created afunctionscontainsword, playerhas and roomhasfunctionswhich returns true of false.
Then it is a matter ofmaking the conditional statements thattest for the puzzles.For some of the puzzles I figured there maybe a couple of different ways to do somethinglike the command“use”and“open”.
Here is an example of code in the puzzles function
if (playerroom ==18)//Starboard Engine bStarboardEngine use tech driver
{
if ((words[0] =="USE") && (!bStarboardEngine) && (playerhas(7)) && (containsword("DRIVER",words)) && (containsword("JET",words)))
{
bStarboardEngine =true;
objectsmodifier[16] ="an aligned Engine inlet";
strReturn ="You have aligned the engine inlet jet";
}
}
I addeda single trap puzzle that gets the player killed, like "You were eaten by a grue."
You hear a beeping from the bridge of the space ship. \nYou go to the bridge and read the display.\n "Ship is descending. Orbit failure imminent. " \nYou must save the ship to save yourself.
I also added code to keep track of some of the puzzles. “use computer” in the bridge will display the ship’s status.
The computer displays...
Systems:
Right Engine - offline
Right Fuel - offline
Left Engine -offline
Left Fuel -offline
While doing this I realized I should be using port and starboard
also change plural items codes and screws to singular. I think this will be less confusing when typing and forgetting to use the plural.
Iadded a HELP command to display all the commands that can be used.
Help displays:
The available commands are:
Single letters
N for go North,S for go South,E for go East, W for go West
I for display inventory, L for look
Commands DROP, GIVE, OPEN, TAKE, TALK, USE Commands can be combined with object for example:
'take pen' 'take paper' 'use pen with paper'
type HINT to get a couple of hints if you are stuck
I added a restart message to the game over message.
I changed the CSS for the page to have the oldgreen screen look.
And a nifty text title:
/ ______\ | ____ \ /\ | ___| | | \ \_____ | | | | / \ | | | | \______ \ | |____/ / / /\ \ | | | | ________\ \ | | /_/__\_\ | | | | \_________/ |_| /_/ \_\ |______| |______| Adventure in Deorbit of Doom!