Now that we’re able to break a players input into useful chunks it’s time to do something with it. So today the goal is to build the structure for the map, and the verbs to move around that map. We’ll create a few new files and #include them so we can use them. You could just put all the code in one file but this makes it much easier to handle, edit and most importantly maintain. We’ll also be using enum’s (enumerators) and struct’s (structures) to create our world and the rules that define it.
The first thing we’re going to do today is create a few new files, go ahead and create nouns.cpp, verbs.cpp, rooms.cpp and properties.cpp. For the sake of ease just save them in the same directory as main.cpp. These are pretty much the only files we’re going to need to finish the whole tutorial with.
To kick off we’ll need to #include some of these in the main file, so under the includes we already have add:
#include “rooms.cpp”
#include “nouns.cpp”
#include “verbs.cpp”
You may have noticed we didn’t #include properties.cpp. That’s ok, we’re going to #include that inside the other files and main.cpp will inherit properties.cpp through those. We’re going to do a little ground work before we get to the interesting stuff. We never need to #inculde twice, so we’re gonna make sure that never happens by accident. Add:
#ifndef __PROPERTIES_H_INCLUDED___
#define __PROPERTIES_H_INCLUDED___
To the very start of the properties.cpp file and
#endif //__PROPERTIES_H_INCLUDED___
to the end. This will ensure that if properties.cpp has already been defined, it won’t be included again, so it will be included and defined first time, and then if another part of the program tries to include it again, it will see that it’s already been defined and skip that part. Useful! Now do the same for the rest of the files (Except main.cpp of course) changing the name to match the file of course. In nouns.cpp, verbs.cpp and rooms.cpp also add
#include “properties.cpp”
so that they can inherit the code from it.
Now that the housekeeping has been done lets start by defining the properties we’re going to use, and build a few structures. Go to properties.cpp and include string and vectors.
#include <string>
#include <vector>
And since we’ve #included these in the properties.cpp, the other files will inherit them and we won’t need to include them there. Less work is always good.
The first thing we’re going to define is our directions. For this tutorial we’re going to keep it simple and use the cardinal directions, that is North, South, East and West. You could use anything you want, use your imagination, you could use different directions depending on your location, like naval directions Port and Starboard if you happen to be on a boat but today we’ll keep it simple.
We’re going to use enums to define this. Essentially an enum is an array of words that replace numbers when you use the word. The compiler sees a number, which it likes, but you see a words, which we like. It also saves us from having to remember that 1 is North and 2 is South etc. Because they are case sensitive we’ll keep them all caps so as to make it easy to know when we’re using them. Go ahead and add
enum en_DIRS {NORTH, SOUTH, EAST, WEST, MAX_DIRS};
that last one is a useful addition to tell us when we’ve reached the end of an enum list. Like any array it starts at zero, so when we call MAX_DIRS it will tell us there are 4 items for us to use in the enum. Handy to know later on, don’t worry about it now.
Next we’re going to add the rooms to our map. But before we do that we’ll want to visualise the layout so we’ll know what we need in the structure. Planning is good. For the sake of this tutorial we’re going to have six rooms, laid out like so:
Now we have our lay out we need to define our six rooms in another enum:
enum en_ROOMS {CELL, DUNGEON, HALLWAY, KEEP, GATE, FREEDOM, MAX_ROOMS};
and for now, we’re just going to define two verbs, go and look. We’re not going to worry about nouns for this section. Now we have everything we need to start working on our structures. We have a places to be, directions to go, and a way to move around them. One little addition and we’re done with this bit for now. We’re going to add a constant integer of -1 called NONE. This will be nice and useful later one when input is or isn’t matching any of our defined words. Don’t worry about it too much now, we’ll come back to it later. Add:
const int NONE = -1;
Now it’s time to create the structures that will allow us to do just that. Here we will use strings, vectors and ints.
Since the verb is going to be the easiest structure to define we’ll start there. Using struct we’ll tell the program what a verb actually is. All it’s going to be is a string that will be what the user calls it and the enum code we’ve defined ourselves, as an integer (remember enum words are really just numbers) we’ll call code . So we end up with
struct verb
{
string word;
int code;
};
Simple right? Lets jump across to verbs.cpp and make our two verbs, and here we’ll also make our directions. To do this we need to write inline functions to set everything up. To do our directions we simply state
inline void set_directions(verb*dir)
{
dir[NORTH].code = NORTH;
dir[NORTH].word = “NORTH”;
dir[SOUTH].code = SOUTH;
dir[SOUTH].word = “SOUTH”;
dir[EAST].code = EAST;
dir[EAST].word = “EAST”;
dir[WEST].code = WEST;
dir[WEST].word = “WEST”;
}
So we have our code that the program will recognize and our word that the player will recognize. Our verbs are just as easy:
inline void set_verbs(verb*vbs)
{
vbs[GO].code = GO;
vbs[GO].word = “GO”;
vbs[LOOK].code = LOOK;
vbs[LOOK].word = “LOOK”;
}
We’re done with verbs.cpp now, back to properties.vbs to create the structure for our rooms. All we really need for our rooms right now is a description, so that will be a string, and an int array to covering the directions, so we can tell where the rooms exits are. We end up with this
struct room
{
string description;
int exits_to_room[MAX_DIRS];
};
you can see we used MAX_DIRS to tell the array to be big enoguh to cover all directions, without having to remember how many directions there are. This is also handy if later on you were to add more directions, you won’t have to remember to change the size of all the arrays, just keep MAX_DIRS as the last entry in the enum. This changes if you decide to specify the enums numbers yourself, but we don’t need to do this here. It’s probably not the best or perfect solution either but it works so we don’t need to worry about it.
So now it’s over to rooms.cpp to create our rooms, things are getting a little exciting now as things become a little bit more tangible, but that could just be me. Same as the verbs start your inline function as:
inline void set_rooms(room *rms)
{
rms[CELL].description.assign(“dark and musty cell with no windows”);
rms[CELL].exits_to_room[NORTH] = NONE;
rms[CELL].exits_to_room[SOUTH] = DUNGEON;
rms[CELL].exits_to_room[EAST] = NONE;
rms[CELL].exits_to_room[WEST] = NONE;
}
You can see here we’ve added each direction of the int array individually, I’ve done this so it’s easier to read and make sense of. Inside the set_rooms function add the other five rooms, paying attention to where the exits go as per the map above, you the lay out you decided earlier. You’ll end up with something like this
inline void set_rooms(room *rms)
{
rms[CELL].description.assign(“dark and musty cell with no windows”);
rms[CELL].exits_to_room[NORTH] = NONE;
rms[CELL].exits_to_room[SOUTH] = DUNGEON;
rms[CELL].exits_to_room[EAST] = NONE;
rms[CELL].exits_to_room[WEST] = NONE;
rms[DUNGEON].description.assign(“cold and dirty dungeon”);
rms[DUNGEON].exits_to_room[NORTH] = CELL;
rms[DUNGEON].exits_to_room[SOUTH] = NONE;
rms[DUNGEON].exits_to_room[EAST] = HALLWAY;
rms[DUNGEON].exits_to_room[WEST] = NONE;
rms[HALLWAY].description.assign(“plain hallway”);
rms[HALLWAY].exits_to_room[NORTH] = NONE;
rms[HALLWAY].exits_to_room[SOUTH] = KEEP;
rms[HALLWAY].exits_to_room[EAST] = NONE;
rms[HALLWAY].exits_to_room[WEST] = DUNGEON;
rms[KEEP].description.assign(“well lit, neat and tidy keep”);
rms[KEEP].exits_to_room[NORTH] = HALLWAY;
rms[KEEP].exits_to_room[SOUTH] = NONE;
rms[KEEP].exits_to_room[EAST] = NONE;
rms[KEEP].exits_to_room[WEST] = GATE;
rms[GATE].description.assign(“gatehouse, freedom seems so close”);
rms[GATE].exits_to_room[NORTH] = NONE;
rms[GATE].exits_to_room[SOUTH] = FREEDOM;
rms[GATE].exits_to_room[EAST] = KEEP;
rms[GATE].exits_to_room[WEST] = NONE;
rms[FREEDOM].description.assign(“wonderfully free land of freeness”);
rms[FREEDOM].exits_to_room[NORTH] = GATE;
rms[FREEDOM].exits_to_room[SOUTH] = NONE;
rms[FREEDOM].exits_to_room[EAST] = NONE;
rms[FREEDOM].exits_to_room[WEST] = NONE;
}
Have fun with the descriptions. This is one of the major places your story can unfurl. What I have here are really just placeholders so we can make sure everything is working right.
Now that we have our structures in place it’s time to set them up. As they are they won’t get initialised. We actually have to call them to get everything going and we do that in main.cpp so head there now.
First thing we’re going to do is create and array of the structures we’re setting up, and then call the function with that array to initilise everything. Sounds a bit confusing perhaps, basically we’re saying we want x number of a type of structure, essentially setting aside space for it in that array. Then where calling the function which fills that array with the information we put in it earlier. It looks a little something like this
verb directions[MAX_DIRS];
set_directions(directions);
verb verbs[MAX_VERBS];
set_verbs(verbs);
room rooms[MAX_ROOMS];
set_rooms(rooms);
Again the MAX_ coming in handy. Always a good idea to try and save yourself work. Next we need to create the actual game loop. This is really getting into the heart of the matter now! We’ll use a do/while loop so it will run once without asking questions and then our condition will come into effect, so it will give players a chance to input something and then act on it accordingly. What we’ll do is get the loop to close after the player types the first word as ‘Quit’. So the loop will keep going until the player types quit.
The first thing we’ll do when we’re in the loop is clear the words array so we don’t keep using the same commands over and adding to the end of it. Then we’ll use the two functions we already had in play, getline() and section() and the for loop we had for testing, altered slightly with a carriage return so we don’t keep typing on the same line all the time. It looks like this
do
{
words.clear();
getline(cin, userInput);
section(userInput, words);
//Loop through the words to make sure it it works
for(int i = 0; i <= words.size()-1; i++)
{
cout << words[i] + “\n”;
}
}while(words[0] != “QUIT”);
Notice that we used all caps for QUIT as after the input has been run through section, it will be all caps, if you typed it in lower case it would never escape the loop as the condition would always fail. Compile and run the game, and see that it still returns whatever you typed into it and quits when you type quit.
Time to get player moving around. But to do that they need to have an initial location, so well add that quickly as an int and using the enum of the location where they’ll start which will of course be the cell. Outside your loop, below the vector<string> words; add
int location = CELL;
That’s almost all we’ll need inside the main() function. All that’s left here is to call the parser we’ve yet to program. I know you’re itching to go and I’m itching to show you the way but we’ve done enough for today, and I’m up early for work tomorrow and it was getting late 2 hours ago! So we’ll save that for another day.
This tutorial was more than twice as long as the last so you’ve done a lot today, and I hope you’ve learned a lot. Well done, you’re on your way to having a complete game 🙂
As always any question or queries feel free to post a comment or reach out to me on twitter (@SeveralBytes). Thanks for reading and I hope you get something from this!
Today’s code in full:
main.cpp
#include <iostream>
#include <string>
#include <vector>
#include “rooms.cpp”
#include “nouns.cpp”
#include “verbs.cpp”
using namespace std;
void section(string userInput, vector<string> &words)
{
string subString;
//Make everything upper case for easier handling
for(int i = 0; i <= userInput.length()-1; i++)
{
userInput[i] = toupper(userInput[i]);
}
//Split userInput into a string vector for even easier handling later
for(int i = 0; i <= userInput.length()-1; i++)
{
if(userInput[i] != ‘ ‘ && i <= userInput.length()-1)
{
subString += userInput[i];
}
if(userInput[i] == ‘ ‘ || i == userInput.length()-1)
{
words.push_back(subString);
subString.clear();
}
}
}
int main()
{
string userInput;
vector<string> words;
int location = CELL;
verb directions[MAX_DIRS];
set_directions(directions);
verb verbs[MAX_VERBS];
set_verbs(verbs);
room rooms[MAX_ROOMS];
set_rooms(rooms);
do
{
words.clear();
getline(cin, userInput);
section(userInput, words);
//Loop through the words to make sure it it works
for(int i = 0; i <= words.size()-1; i++)
{
cout << words[i] + “\n”;
}
}while(words[0] != “QUIT”);
}
properties.cpp
#ifndef __PROPERTIES_H_INCLUDED___
#define __PROPERTIES_H_INCLUDED___
#include <string>
#include <vector>
using namespace std;
enum en_DIRS {NORTH, SOUTH, EAST, WEST, MAX_DIRS};
enum en_ROOMS {CELL, DUNGEON, HALLWAY, KEEP, GATE, FREEDOM, MAX_ROOMS};
enum en_VERBS {GO, LOOK, MAX_VERBS};
const int NONE = -1;
struct verb
{
string word;
vector<string> synonyms;
int code;
};
struct room
{
string description;
int exits_to_room[MAX_DIRS];
};
#endif //__PROPERTIES_H_INCLUDED___
nouns.cpp
#ifndef __NOUNS_H_INCLUDED__
#define __NOUNS_H_INCLUDED__
#include “properties.cpp”
#endif // __NOUNS_H_INCLUDED__
verbs.cpp
#ifndef __VERBS_H_INCLUDED__
#define __VERBS_H_INCLUDED__
#include “properties.cpp”
inline void set_directions(verb *dir)
{
dir[NORTH].code = NORTH;
dir[NORTH].word = “NORTH”;
dir[SOUTH].code = SOUTH;
dir[SOUTH].word = “SOUTH”;
dir[EAST].code = EAST;
dir[EAST].word = “EAST”;
dir[WEST].code = WEST;
dir[WEST].word = “WEST”;
}
inline void set_verbs(verb *vbs)
{
vbs[GO].code = GO;
vbs[GO].word = “GO”;
vbs[LOOK].code = LOOK;
vbs[LOOK].word = “LOOK”;
}
#endif // __VERBS_H_INCLUDED__
rooms.cpp
#ifndef __ROOMS_H_INCLUDED__
#define __ROOMS_H_INCLUDED__
#include “properties.cpp”
inline void set_rooms(room *rms)
{
rms[CELL].description.assign(“dark and musty cell with no windows”);
rms[CELL].exits_to_room[NORTH] = NONE;
rms[CELL].exits_to_room[SOUTH] = DUNGEON;
rms[CELL].exits_to_room[EAST] = NONE;
rms[CELL].exits_to_room[WEST] = NONE;
rms[DUNGEON].description.assign(“cold and dirty dungeon”);
rms[DUNGEON].exits_to_room[NORTH] = CELL;
rms[DUNGEON].exits_to_room[SOUTH] = NONE;
rms[DUNGEON].exits_to_room[EAST] = HALLWAY;
rms[DUNGEON].exits_to_room[WEST] = NONE;
rms[HALLWAY].description.assign(“plain hallway”);
rms[HALLWAY].exits_to_room[NORTH] = NONE;
rms[HALLWAY].exits_to_room[SOUTH] = KEEP;
rms[HALLWAY].exits_to_room[EAST] = NONE;
rms[HALLWAY].exits_to_room[WEST] = DUNGEON;
rms[KEEP].description.assign(“well lit, neat and tidy keep”);
rms[KEEP].exits_to_room[NORTH] = HALLWAY;
rms[KEEP].exits_to_room[SOUTH] = NONE;
rms[KEEP].exits_to_room[EAST] = NONE;
rms[KEEP].exits_to_room[WEST] = GATE;
rms[GATE].description.assign(“gatehouse, freedom seems so close”);
rms[GATE].exits_to_room[NORTH] = NONE;
rms[GATE].exits_to_room[SOUTH] = FREEDOM;
rms[GATE].exits_to_room[EAST] = KEEP;
rms[GATE].exits_to_room[WEST] = NONE;
rms[FREEDOM].description.assign(“wonderfully free land of freeness”);
rms[FREEDOM].exits_to_room[NORTH] = GATE;
rms[FREEDOM].exits_to_room[SOUTH] = NONE;
rms[FREEDOM].exits_to_room[EAST] = NONE;
rms[FREEDOM].exits_to_room[WEST] = NONE;
}
#endif //__ROOMS_H_INCLUDED__
Pingback: Text Adventure Tutorial: Parsing Thoughts | SeveralBytes