It’s finally time to put some stuff in out little world. Right now we’re not going to put too much, just enough to test that we have a working system. To do that we’re going to create Nouns. Nouns as we all know can be a person, place or thing. But for our purposes we’re going to use it only to describe objects. Always trying to keep things as easy for outselves as we can.
As always, first things first, we’re going to need to tell the computer what a noun even is! Unfortunately our computers didn’t attend primary school like us, so until we tell it exactly what a noun is, it really couldn’t care less, or know better. So off to our properties.cpp to create a new struct by the name of noun. Of course we’ll need a string word and int code just like our verb has, we’ll skip synonyms as we’ll want to be precise about our nouns. We’ll also want a description of our item which we will of course store in a string and in addition we’ll also have a string for examination if we look a bit closer we’ll get a bit more information, lastly we’ll want to know whether or not we can actually pick this object up, so a simple bool will cover that. You should end up with something that look a bit like this:
struct noun
{
string word;
string description;
string on_examination;
int code;
int location;
bool can_carry;
};
Now that we’ve defined it, it’s time to create some objects. It works in exactly the same way as it did for verbs. We’ll need to create an enum list of the nouns we’re going to use. I’m going to add a stick, a string, a wire and a key for use in a simple puzzle we’ll set up another day. It will look nearly exactly the same as the other enum lists:
enum en_NOUNS {STICK, STRING, WIRE, KEY, MAX_NOUNS};
Now things get a little interesting as we move to nouns.cpp and add our nouns information into the list. start an inline function to initialise the, again the same way we did for the verbs and the rooms. Dont forget to use all capitals for the word and the code, and the locations we’ve already created in the rooms enum will be all we need for the location. The should look something like this:
inline void set_nouns(noun *nns)
{
nns[STICK].word = “STICK”;
nns[STICK].code = STICK;
nns[STICK].description = “a stick”;
nns[STICK].on_examination = “a crooked, thin stick as long as my arm”;
nns[STICK].can_carry = true;
nns[STICK].location = CELL;
}
Now that we’ve set them up it’s time to go back to main.cpp to initialise them fully, add the to the parser for use and ensure we can see them so we know they’re there! Set them up exacty the way we did for the other structs, lots of repetition but it makes life easy for us. You should now have something like this in your main() function.
noun nouns[MAX_NOUNS];
set_nouns(nouns);
parser(location, rooms, directions, verbs, nouns, words);
And your parser should look like this:
bool parser (int &loc, room *rms, verb *dir, verb *vbs, noun *nns, vector<string> words)
In the look function we’re going to need access to both the nouns and the directions as we’re going to finish up that function today, and finish up with it. Ah symmetry. So the function looks something like this now.
bool look(int loc, room *rms, noun *nns, verb *dir)
Don’t forget to add the nouns and direction to the function call in the parser script.
Currently all the look script does is tell you what your current location looks like. We’re going to change it so it also tells us what objects are in the room and what exits are available. Instead of using cout << throughout the function we’re going to write everything to a string and the print it to the screen right at the end of the function. So change the first line to reflect this:
string str = “I’m in a ” + rms[loc].description + “. “;
And from here we’ll just keep adding to str using str = str + “Whatever we want to add”. So first we’re going to tell the player what’s in the room. We’ll use a for loop that maxes out on, you guessed it, MAX_NOUNS. If a location of an object matches our location, we’ll add it’s description to the string until we reach the end. Now we could just leave it at that but it doesn’t look very well on screen, even if it is functional.
What we’ll do to make it a bit smoother is check ahead and see if there are going to be any more objects in the room, if there is only one more object left, we’ll add ‘and’ between them, but if there is more than one, we’ll use a comma to separate them. That should look something like this…
str = str + “I see “;
bool andadded = false;
for(int i = 0; i < MAX_NOUNS; i++)
{
if(nns[i].location == loc)
{
str = str + nns[i].description;
}
int ismore = 0;
int j;
for(j = i+1; j < MAX_NOUNS; j++)
{
if(nns[j].location == loc)
{
ismore++;
}
}
if(ismore == 1 && !andadded)
{
str = str + ” and “;
andadded = true;
}
if(ismore > 1)
{
str = str + “, “;
}
}
str = str + “. “;
As you can see when we start the second loop to check if there are more we use the current position + 1 so we don’t accidentally take into account the item we’re looking at currently.
Looking for exits is a much simpler affair. We’ll check each direction to see if it has an exit, if it does we’ll tell the player what is in that direction, and then print to screen.
for(int i = 0; i < MAX_DIRS; i++)
{
if(rms[loc].exits_to_room[i] != NONE)
{
str = str + “To the ” + dir[i].word + ” is a ” + rms[rms[loc].exits_to_room[i]].description + “. “;
}
}
str = str + “\n”;
cout << str;
As you can see we’ve nested rms[loc].exits_to_room[i] inside a call for a description, this tells the rms which room it is that we want a description of.
Compile and run, now you can see all the exits that we put in previously and see what else is around the dungeon. Next time we’ll look more at the parser and actually doing something with these objects!
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!
Here is all the code for the parser after today’s additions:
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};
enum en_NOUNS {STICK, STRING, WIRE, KEY, MAX_NOUNS};
const int NONE = -1;
struct verb
{
string word;
vector<string> synonyms;
int code;
};
struct noun
{
string word;
string description;
string on_examination;
int code;
int location;
bool can_carry;
};
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”
inline void set_nouns(noun *nns)
{
nns[STICK].word = “STICK”;
nns[STICK].code = STICK;
nns[STICK].description = “a stick”;
nns[STICK].on_examination = “a crooked, thin stick as long as my arm”;
nns[STICK].can_carry = true;
nns[STICK].location = CELL;
nns[STRING].word = “STRING”;
nns[STRING].code = STRING;
nns[STRING].description = “a piece of string”;
nns[STRING].on_examination = “a bit of rough but sturdy twine of origins unknown”;
nns[STRING].can_carry = true;
nns[STRING].location = CELL;
nns[WIRE].word = “WIRE”;
nns[WIRE].code = WIRE;
nns[WIRE].description = “a bit of wire”;
nns[WIRE].on_examination = “a small bit of wire, bent into an uneven U shape, I feel like I’ve seen this before”;
nns[WIRE].can_carry = true;
nns[WIRE].location = CELL;
nns[KEY].word = “KEY”;
nns[KEY].code = KEY;
nns[KEY].description = “a key”;
nns[KEY].on_examination = “The key to get out of this cell, but it’s out of reach”;
nns[KEY].can_carry = true;
nns[KEY].location = DUNGEON;
}
#endif // __NOUNS_H_INCLUDED__
main.cpp
#include <iostream>
#include <string>
#include <vector>
#include “rooms.cpp”
#include “nouns.cpp”
#include “verbs.cpp”
using namespace std;
bool section(string userInput, vector<string> &words)
{
string subString;
if(!userInput.empty())
{
//Make everything upper case for easier handling
for(int i = 0; i <= static_cast<signed int>(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 <= static_cast<signed int>(userInput.length()-1); i++)
{
if(userInput[i] != ‘ ‘ && i <= static_cast<signed int>(userInput.length()-1))
{
subString += userInput[i];
}
if(userInput[i] == ‘ ‘ || i == static_cast<signed int>(userInput.length()-1))
{
words.push_back(subString);
subString.clear();
}
}
}
else
{
words.push_back(“LOOK”);
}
return true;
}
bool look(int loc, room *rms, noun *nns, verb *dir)
{
string str = “I’m in a ” + rms[loc].description + “. “;
str = str + “I see “;
bool andadded = false;
for(int i = 0; i < MAX_NOUNS; i++)
{
if(nns[i].location == loc)
{
str = str + nns[i].description;
}
int ismore = 0;
int j;
for(j = i+1; j < MAX_NOUNS; j++)
{
if(nns[j].location == loc)
{
ismore++;
}
}
if(ismore == 1 && !andadded)
{
str = str + ” and “;
andadded = true;
}
if(ismore > 1)
{
str = str + “, “;
}
}
str = str + “. “;
for(int i = 0; i < MAX_DIRS; i++)
{
if(rms[loc].exits_to_room[i] != NONE)
{
str = str + “To the ” + dir[i].word + ” is a ” + rms[rms[loc].exits_to_room[i]].description + “. “;
}
}
str = str + “\n”;
cout << str;
return true;
}
bool parser (int &loc, room *rms, verb *dir, verb *vbs, noun *nns, vector<string> words)
{
//By using a vector we can have multiple actions in one input
vector<int> verbAction;
vector<int> nounMatch;
vector<int> route;
int v = 0; //verbAction index
int r = 0; //route index
int k = 0; //index setter
bool done = false;
for(int i = 0; i < static_cast<signed int>(words.size()); i++)
{
//check for verb
for(int j = 0; j < MAX_VERBS; j++)
{
if(words[i] == vbs[j].word)
{
verbAction.push_back(vbs[j].code);
}
}
//check for route command
for(int j = 0; j < MAX_DIRS; j++)
{
if(words[i] == dir[j].word)
{
route.push_back(dir[j].code);
}
}
}
if(verbAction.empty() && route.empty() && words[0] != “QUIT”)
{
cout << “That didn’t make any sense.\n”;
return true;
}
while(!done)
{
if(verbAction.size() == 0)
{
if(route.size() == 0)
{
break;
}
else
{
cout << “No actions given.\n”;
break;
}
}
if(verbAction.size() == route.size())
{
v = k;
r = k;
}
if(verbAction.size() > route.size())
{
if(k >= static_cast<signed int>(route.size()))
{
v = k;
r = route.size()-1;
}
else if(k < static_cast<signed int>(route.size()))
{
v = k;
r = k;
}
}
if(verbAction.size() < route.size())
{
if(k >= static_cast<signed int>(verbAction.size()))
{
v = verbAction.size()-1;
r = k;
}
else if(k < static_cast<signed int>(verbAction.size()))
{
v = k;
r = k;
}
}
if(verbAction[v] == LOOK)
{
look(loc, rms, nns, dir);
}
if(verbAction[v] == GO)
{
if(route.size() > 0)
{
if(rms[loc].exits_to_room[route[r]] == NONE)
{
cout << “There is no exit that way.\n”;
}
if(rms[loc].exits_to_room[route[r]] != NONE)
{
loc = rms[loc].exits_to_room[route[r]];
look(loc, rms, nns, dir);
}
}
else
{
cout << “Go where?\n”;
break;
}
}
k++;
if(k >= static_cast<signed int>(verbAction.size()) && k >= static_cast<signed int>(route.size()))
{
done = true;
}
}
return true;
}
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);
noun nouns[MAX_NOUNS];
set_nouns(nouns);
do
{
words.clear();
getline(cin, userInput);
section(userInput, words);
parser(location, rooms, directions, verbs, nouns, words);
}while(words[0] != “QUIT”);
}