=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- How to Program Text Adventures =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Terry Cavanagh (terrycavanagh@eircom.net) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Learning to program can be difficult, regardless of the language you're using. Often, however, it's not lack of knowledge that holds the amateur programmer back. The problem is usually that they don't know how to move that box up a pixel when the up key is pressed, they don't know how to make it shoot in a given direction, they don't know how to make the screen scroll. Most of these things can be found through trial and error, but the process really speeds up when somebody actually tells you how to do it! However, making an RPG as your first ever serious product is a little over ambitious - Personally, the first proper game I ever made was a text adventure, followed by a small X and O's game and then another text adventure. This tutorial should be very useful to show you how to make a text adventure. From there, you can work your way up - I plan to write one of these game development tutorials at least ever other month, so start collecting! Before we proceed, I'd better clear up the concepts of Text Adventures. To put it simply, a text adventure is an interactive book. The room description is printed onscreen, and you are given a prompt to select your actions. The aspect that means Text adventures are still popular today is the freedom this allows you - You don't just have to walk around and kill the bad guys, you walk around locations doing basically what you like, killing animals, stealing from shops, wherever your corrupt little mind may take you! Freedom is the single most enjoyable feature with these games, so the more commands your game has the better. The key to programming a game like this is the PARSER. The parser is basically a subroutine which you're going to write, which takes your answer, processes it and carrys out it's instructions. There are loads of commands that your parser will have to learn to cope with, but once you have the basic ones (GET, EXAMINE, DROP, etc), the rest will come logically. Let's get Started! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Step 1 : The basic Concepts. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- At the start of your program, put the line ------------------------------ DIM SHARED Room% ------------------------------ Room% is probably the most important variable you will use. Set it to 0, Which it will be by default anyway, but it's good programming practice. Now, you're gonna have to decide now how wide your game is. Let's say you've got 7 rooms in the horizontal axis, and we've got 7 vertical rooms. Your map will look like this. [Image missing] Remember, Room% = 0. The Red box represents the value of Room%. Now, assume that the picture is really all the rooms, given numbers. And assume all of them are joined. Your Parser will have to learn first to respond to the Commands NORTH, SOUTH, EAST and WEST. Say for example the parser returns WEST. Looking at the diagram, what is the value of the room to the west of 0? It's -1. Basically, if the parser ever receives the command W or WEST or whatever you assign to mean "left", Room% = Room% -1. This works for any value of Room% ; say Room% was equal to 9. When the Parser receives the command WEST, Room% is now equal to 8. The same works for EAST - Simply add 1. For NORTH and SOUTH, the answer is a little more complicated. If you want to go north, take the assigned width of the level from Room%. The width of the level is 7, So when Room% = 0, Room% - 7 = -7, which when you look at the diagram makes perfect sense. Then of course for south, simply add the width of the level. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Step 2 : The Parser =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- We can't go very far with out programming a parser, so let's get this one out of the way. You'll need to be familiar with String Processing to do this, so before I start I'll briefly run through all the commands you'll need, and how to use them. The first command in any parser is to give the prompt; like so : ------------------------------ INPUT "What Now"; Answer$ ------------------------------ Answer$ is a string which holds the players answer to the prompt. My first ever Text adventure actually had had a message on the title screen - "please play with caps-lock on!" - This is because my incredibly limited parser only understood words in capital letters! UCASE$() is a simple function that converts your answer into capital letters, so even if you type "get stick", "GET STICK", or "GeT StIcK", it won't make any difference. What this does is basically make your parser insensitive to case. Another important aspect is to remove leading and trailing spaces. LTRIM$() and RTRIM$() do the trick here. Usage : ------------------------------ Answer$ = LTRIM$(RTRIM$(UCASE$(Answer$))) ------------------------------ We're getting somewhere now! Where previously Answer$ could have contained anything from " geT Box " to "CollEcT ManNa ", it now contains a tight, uppercase string. Ok, the process now revolves around extracting words from the answer. This is how the parser will eventually look : [Image Missing] You need to declare an array to hold the individual words from Answer$. The size of the array depends solely on the power of your Parser - I don't honestly see why it should even need to understand more than 2 commands at a time - which is why I used simply A$ and B$ in programming "Spellbound Island". This tutorial will assume that you only want to understand two words at a time. Programming to understand any more is actually not really that much more difficult - If anybody really wants to know how it's done, Email me. (But you should really try to work it out for yourself anyway!) You also will need to know how to use LEFT$(String$,num%) and RIGHT$(String$, Num%). Basically, these two functions return the values of the leftmost "Num%" characters and rightmost "Num%" Characters. You also need to understand MID$; A function that returns the value of num% midmost character in the String String$ starting with letter number X% (Where the command is in the form MID$(String$, X%, num%)). It's vital you understand these commands for the next part. Also, LEN() returns the value of the number of letters in a string; e.g. LEN("hello") = 5. Finding the command, A$, means simply extracting the first word. Read the source code here and I'll explain the method later. ------------------------------ Temp% = 0 IF Answer$ = "" THEN A$ = "": EXIT SUB DO Temp% = Temp% + 1 IF MID$(Answer$, Temp%, 1) = " " OR MID$(Answer$, Temp%, 1) = "" THEN Temp% = Temp% - 1: EXIT DO LOOP A$ = LEFT$(Answer$, Temp%) ------------------------------ Basically, All the program does is run a loop to find the first space or first accorance when no character exists in the string, and extract the first word from it. It also exits the Sub (Assuming you're running this in a subroutine) if the Answer$ String is empty. There's your first word. Getting the second word is even easier : ------------------------------ Answer$ = LTRIM$(RIGHT$(Answer$, LEN(Answer$) - LEN(A$))) Temp% = 0 IF Answer$ = "" THEN B$ = "": EXIT SUB DO Temp% = Temp% + 1 IF MID$(Answer$, Temp%, 1) = " " OR MID$(Answer$, Temp%, 1) = "" THEN Temp% = Temp% - 1: EXIT DO END IF LOOP B$ = LEFT$(Answer$, Temp%) ------------------------------ That might look complicated now, but it accually works out very easy when you look at it. All it's doing is changing the value of Answer$ to Answer$ without A$, and then repeating the extracting process. There you go! Parser complete! Are you still on board? The hard part is over. Things are about to get much easier. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Step 3 : North, South, East and West. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Recall what I said earlier about movement. Now apply it to the parser. Pretending that we've moved all the processing stuff into a Subroutine called "Process" and the width of the level is 7, let's imagine this is our program code - ------------------------------ Start: PRINT "Your are in Room" + STR$(Room%) INPUT "What now";Answer$ PROCESS Answer$ SELECT CASE A$ CASE "N", "NORTH" Room% = Room% - 7 CASE "S", "SOUTH" Room% = Room% + 7 CASE "W", "WEST" Room% = Room% - 1 CASE "E", "EAST" Room% = Room% + 1 CASE "NW", "NORTHWEST" Room% = Room% - 8 CASE "NE", "NORTHEAST" Room% = Room% - 6 CASE "SW", "SOUTHWEST" Room% = Room% + 6 CASE "SE", "SOUTHEAST" Room% = Room% + 8 CASE "QUIT", "EXIT", "Q" END END SELECT GOTO Start ------------------------------ Hmm... Kinda Limited, huh? It's time to add some more basic features. Biggest problem so far is room discriptions. Fixing that is no problem at all - Instead of printing a constant, replace that line with something like : ------------------------------ LOADROOM Room% FOR I% = 1 to 5 IF RoomDiscription$(I%) <> "" THEN PRINT RoomDiscription$(I%) NEXT I% ------------------------------ Hey Presto! You can now have different rooms! That's assuming you've made good use of the implied LoadRoom Sub - For those of you who didn't see that first time round, LoadRoom is a Sub which assigns values of RoomDiscription$(1 TO 5). But you didn't need me to tell you that, did you :) ? That's one problem. A most severe one is obviously direction. You don't always want to be able to travel in all 8 directions at once. This is done by adding a few lines in the LoadRoom Sub that assigns values to an array containing 8 Elements. Assign each direction a number between 1 and 8, then check in the main loop to see it that direction in your array equals to 1. If it does, travel in that direction. If it's 0, return the message "You cannot go in this Direction.". For Example, the LoadRoom Sub could Look something like this : ------------------------------ IF Room% = 0 THEN RoomDescription$(1) = "You are standing in a thick woodland." RoomDescription$(2) = "You are surronded by trees." RoomDescription$(3) = "You can go East." RoomDescription$(4) = "" RoomDescription$(5) = "" CanIGo%(1) = 0 CanIGo%(2) = 0 CanIGo%(3) = 0 CanIGo%(4) = 1 CanIGo%(5) = 0 CanIGo%(6) = 0 CanIGo%(7) = 0 CanIGo%(8) = 0 END IF IF Room% = 1 THEN RoomDescription$(1) = "You are East of the original Forest." RoomDescription$(2) = "You are surronded by trees." RoomDescription$(3) = "You can go back the way you came." RoomDescription$(4) = "" RoomDescription$(5) = "" CanIGo%(1) = 0 CanIGo%(2) = 0 CanIGo%(3) = 0 CanIGo%(4) = 0 CanIGo%(5) = 0 CanIGo%(6) = 1 CanIGo%(7) = 0 CanIGo%(8) = 0 END IF ------------------------------ And then simply Check the CanIGo% varibles when a movement command is recieved by the parser. Your game should be starting to piece itself together now. But one important element that I havn't touched yet is interacting with your world - objects, basically. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Step 4 : Objects in your world. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- You're going to need a subroutine to load the objects. The objects have several properties, so it wouldn't hurt to set up a type for them, but it's not nessicary. Before you start to program objects, It's important to have all the objects planned out, and their locations. The objects must have a Location Tag. At the start of the program, declare an array equal in size to the number of items you plan to have. ItemRoom%(1) would contain the number equal to the room that the first Item is in, ItemRoom%(2) contains the location of the second item and so on. But when you enter a room, how do you know the item is there? This brings another important aspect to mind - Each item must have a Glace Discription. Remember this ? ------------------------------ LOADROOM Room% FOR I% = 1 to 5 IF RoomDiscription$(I%) <> "" THEN PRINT RoomDiscription$(I%) NEXT I% ------------------------------ You only have to make a slight change here. Assuming you've got 30 items in your game : ------------------------------ LOADROOM Room% FOR I% = 1 to 5 IF RoomDiscription$(I%) <> "" THEN PRINT RoomDiscription$(I%) NEXT I% FOR I% = 1 to 30 IF ItemRoom%(I%) = Room% THEN PRINT ItemGlance$(I%) NEXT I% ------------------------------ Basically, this prints the preset glace discriptions of ever object that is in that room. What about preseting the objects? Well, that's a long tedious job that you'll have to create a sub for. For example : ------------------------------ ItemRoom%(1) = 0 ItemGlance$(1) = "You see a small branch." ItemRoom%(2) = -7 ItemGlance$(2) = "You see a revolver." ------------------------------ That's a basic version of what I'm about to do :) How are you ever going to be able to get the objects without the player knowing what to call them? You'll need another array, this time containing the short, capitalised names of the items. In the example above, it would make sense to have the names equal to something like this : ItemShortName$(1) = "BRANCH", and ItemShortName$(2) = "REVOLVER". Now the player has something to call the items, i.e., it can type command like GET BRANCH. It's time to do the GET and DROP commands. For this, you'll need a good knowledge of how the INVENTORY works. The inventory is simple. Assign an unused room as the "Inventory Room". I recomend using a room number that you cannot posibly visit, although I used to use room -1, It makes more sense to use a distant room like room 30000 that you're never likely to have to walk around. When the parser receives the GET command, Check the contents of B$ - assuming that B$ is equal to the short name of object 6, Run through a loop of your items until you find that B$ is equal to ItemShortName$(6): Then Change ItemRoom%(6) = the inventory room, say 30000. The Drop command is similar. Check B$ in the Answer$ string against the objects in room 30000. If the item is there, plcae ItemRoom%(6) to the value of the current Room%. A long name is nessicary also - you can have ItemLongName$(1) = "A Tree Branch", and ItemLongName$(2) = "A Colt Single action army Revolver". Then whenever the Parser receives the INVENTORY command (Or simply A$ = "I") List the long names of the contents of the inventory room. That's basically it! I guess I could have explained it better, but it's simple enough you think about it. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Step 5 : Thats it! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=- I'm afraid that's all I'm telling you. You should be able to work the rest out yourself, if you can't, email me. But best yet, simply read the source code of my most recent text adventure, "Spellbound Island", which, despite being very messy, contains all you should need to know. It's healthy to work out the things I left out, The USE command is the only real sever command I neglected, apart from the EXAMINE command. With a bit of thought, it's not difficult (heh, ok, I'll tell you. Just Assign an Array of Tags, altered within the use command. You'll need to work out stationry objects too, objects that you cannot pick up but can interact with (eg, searching rocks.)) I hope you enjoyed the tutorial! I'll be back next Month! Terry Cavanagh.