Tuesday 20 November 2012

Text Adventure Games C++ Part 3

Verbs introductory
Once again picking up where I left off, this time for a little action, as in verbs. Check my previous posts on the subject of Text Adventures; FIRST POST and SECOND POST. And here is what I am going to cover this time around.

1. Implement some simple VERBS.
2. Get the parser to handle a verb.
3. Demonstrate a sub procedure from the parser (the LOOK procedure).

So, once again, enumerated types come to the rescue here. Using the completed source code from my previous post as a starting point, I will specify some enumerated identifiers for some VERBS. Right after en_ROOMS, I globally set the following verbs...

enum en_VERBS {GET, DROP, USE, OPEN, CLOSE, EXAMINE, INVENTORY, LOOK};

That is, eight verbs. These are pretty common verbs in text adventures. I am not going to get all of them working in the parser by the end of this post, but just "sample"; LOOK.

For the purpose of looping through the verbs (should it be necessary at some stage), I am also going to define a global constant for them, right after const int ROOMS in the source code...

const int VERBS = 8;

Now, verbs will reutilize the word structure that was previously used for directions, in order to set the words and assign the code identifiers for the verbs. In main(), before the loop, I will declare that new array, and call a function to set the verbs...

// In main()
    word verbs[VERBS];
    set_verbs(verbs);

// Function to set the verbs...
void set_verbs(word *vbs)
{
    // Reminder GET, DROP, USE, OPEN, CLOSE, EXAMINE, INVENTORY, LOOK
    vbs[GET].code = GET;
    vbs[GET].word = "GET";
    vbs[DROP].code = DROP;
    vbs[DROP].word = "DROP";
    vbs[USE].code = USE;
    vbs[USE].word = "USE";
    // and so on, see the source code in the link for the rest...
}

Great, I have some verbs set. Now, to use them. But first, the parser must receive the list of verbs as a parameter, so this modification is in order.

// Where the parameters of the parser function read...
bool parser(int &loc, string wd1, string wd2, word *dir, room *rms)
{
    // code...
}
// It will now read...
bool parser(int &loc, string wd1, string wd2, word *dir, word *vbs, room *rms)
{
    // code...
}

And the call to the parser from inside the loop in main() will pass verbs as a parameter...

// Where the call was...
parser(location, word_1, word_2, directions, rooms);

// It will now be...
parser(location, word_1, word_2, directions, verbs, rooms);

With that I am all set to start interacting on a very basic level with some of the verbs held in the array. A good starting point is the LOOK verb. It is common in all adventure games I ever played, and is a simple one word command that the player enters. It is also a good example here because it will be an exceptional case of sub proceduralizing something out of the parser. Generally, my own rule of thumb is to make a procedure for anything that may be used by more parts of a program than one. LOOK could be used twice by the program, for example;

1. When the player types LOOK as a command, and;
2. Automatically invoked whenever the player changes location from one room to another.

Okay, I will move into the parser and do that "trapping" of the VERB. In the parser I will declare a local variable to catch the verb that is passed to it, using my old NONE as -1 to initialize it...

// In the parser function...
int VERB_ACTION = NONE;

And now the simple "verb trap", which is nothing more than a for() loop that compares word_1 (wd1, as passed) to the list of verb words. When a word coincides, it assigns the code of that verb to the variable VERB_ACTION and breaks out of the loop. If no coincidence at all occurs, then the variable VERB_ACTION remains as initialized (NONE). Here is that chunk of code, to be placed in the parser function right after the direction for() loop...

    for(i = 0; i < VERBS; i++)
    {
        if(wd1 == vbs[i].word)
        {
            VERB_ACTION = vbs[i].code;
            break;
        }
    }

And with that I can go on to examine the variable VERB_ACTION. The first logical thing to do would be to deal with a value of NONE, that is to say, the player did not enter a recognized verb.

    if(VERB_ACTION == NONE)
    {
        cout << "No valid command entered." << endl;
        return true;
    }

Personally, I would put this condition check at the very end of the parser function and all other condition checks above it, but that's just me. Now for the LOOK command condition check, which upon validating true simply calls the look_around() function...

    if(VERB_ACTION == LOOK)
    {
        // This is an example of sub proceduralizing a function from the parser.
        look_around(loc, rms, dir);
        return true;
    }

And here is that function...

void look_around(int loc, room *rms, word *dir)
{
    int i;
    cout << "I am in a " << rms[loc].description << endl;
   
    // LOOK should also allow the player to see what exits exist from the current room.
    for(i = 0; i < DIRS; i++)
    {
        if(rms[loc].exits_to_room[i] != NONE)
        {
            cout << "There is an exit " << dir[i].word << " to a " << rms[rms[loc].exits_to_room[i]].description << "." << endl;
        }
    }
}

That can all be compiled and tested now. Not much, as yet. I can walk around my map, and use one "verb" command. But the stage is set to move onto to the next phase, on next post. That is, nouns, better known as items or objects.

Here is the complete source code for what has been done this time around.

And here is a figure of the running program.



That's all for now.

Saturday 10 November 2012

Text Adventure Games C++ Part 2


So, moving on (quite literally for this post) I am picking up where I left off from my last post (entering commands), and am here am going to design a game map and move around inside it with some basic commands. Before actually diving in and trying to program the map, it is a good idea to draw out a map of my world, something like this...


It should go without saying, at this point, that anyone writing an "adventure game" should already have an engrossing "adventure story" in mind.

I will keep it simple. Exits from a room will be only north, east, south and west. I will not do any northeast or up or down type of thing here (though there is no impediment for doing so in more complex maps). First I will number my rooms, from 0 to 10 (that is, eleven rooms). I will also consider my exits to be from 0 to 3, going around clockwise from north (0). Now the problem with using numbers as identifiers is that I will need to write down what each one "equates to" on a sheet of paper and keep track of it all the time I am programming. For example;

0 is a SPORTSHOP
1 is a CASINO... and so on,

...for the rooms, and for the directions...

0 is NORTH,
1 is EAST... and so on.

This is the perfect sitaution for using enumerated types. So, first thing; at the top of my code, just after the #includes, I will declare some enumerated types globally for the directions and the rooms...

enum en_DIRS {NORTH, EAST, SOUTH, WEST};
enum en_ROOMS {SPORTSHOP, CASINO, CARPARK, LOBBY, RESTAURANT, CORRIDOR, STOREROOM, POOL, GARDEN, POND, PUMPROOM};

const int NONE = -1;
const int DIRS = 4;
const int ROOMS = 11;

I will also want an identifier for NO EXIT; check out the map, not all rooms have exits in all directions. I will use a constant integer -1, above declared as NONE. Also, for the purpose of looping through the DIRECTIONS and ROOMS, which will probably be required later on, I will set their number with another couple of constants, DIRS and ROOMS.

Now the programming will be instantly easier to understand. In pseudo code...

if direction commanded is 1 and location is 2 then
    location = 3

...is certainly more difficult to follow than...

if direction commanded is EAST and location is CARPARK then
    location = LOBBY

The Map
But that is just an example. For now, it would be convenient to make a global structure for holding information about each room. And while I am at it, a global structure for words, which will be a multipurpose structure to hold both directions and verbs (ie; basically commands). So, under those constant integers, I will code this;

struct word
{
    string word;
    int code;
};

struct room
{
    string description;
    int exits_to_room[DIRS];
};

That is a basic minimum for each of those structures. I will come back to the word structure in a minute. For now, I want to focus on the room structure. First it has a description. That is just a line of text that offers a description of the room itself, for example "car park". It will be displayed in the game for the player to know where he (or she*) is. The exits_to_room variable, on the other hand is an array, and is the size of the number of directions that the game handles (ie; four of them). Each room has four exits. It does not matter that some of these exits in the game do not exist for specific rooms (for example, you cannot go south or west from the carpark). That is what the NONE constant is for. The exit is still there, it just cannot be used in the game.

Just as a note, exits_to_room should be considered as "room interconnectivity". That is to say (normally) if room 0 exits south to room 2, then room 2 should exit north to room 0. I know deeper consideration of this can lead to ideas of all sorts of weird maps that have exits leading to unexpected rooms, but I sincerely think that it is generally NOT a good idea to deliberately confuse the player like this. The adventure should be the challenge, not the map, unless there is a very good reason why part of the map should be a challenge (for example, the player dies but the game continues with him/her in a strange Labyrinth of the Underworld, trying to get back to life. Heh! Heh! A "serving suggestion").

With that clarified, I will continue and "set" all the rooms for the game. This first requires an array of the structure room to be created in main(). So, somewhere in the beginning of the main() block, before the while() loop, I create;

room rooms[ROOMS];
set_rooms(rooms);

That second line in there is a call to a separate function that is yet to be made, and which will set my rooms characteristics (that is, its description and its exits). Here is that function, in part (see the full listing at the end of this post for the complete version).

void set_rooms(room *rms)
{
    rms[SPORTSHOP].description.assign("sports shop");
    rms[SPORTSHOP].exits_to_room[NORTH] = NONE;
    rms[SPORTSHOP].exits_to_room[EAST] = NONE;
    rms[SPORTSHOP].exits_to_room[SOUTH] = CARPARK;
    rms[SPORTSHOP].exits_to_room[WEST] = NONE;

    rms[CASINO].description.assign("bustling casino");
    rms[CASINO].exits_to_room[NORTH] = NONE;
    rms[CASINO].exits_to_room[EAST] = NONE;
    rms[CASINO].exits_to_room[SOUTH] = LOBBY;
    rms[CASINO].exits_to_room[WEST] = NONE;


    // ...and so on untill all rooms and their exits are completed.
}

Take care doing this. The structure array is passed to the function, and the number of rooms described in the function simply MUST not exceed the size of the array (const int ROOMS), or bad things will happen to the program when it is run. When adding or removing rooms to the map (say, if I want to expand the game) I must first change the value of the global ROOMS, then edit the enumerated type en_ROOMS to include (or suppress) rooms, and finally edit the rooms and their exits in this set_rooms function.

So great, I have my map now, but it would be nice to move around it, too. Here is where I will take the first step in bringing together the command interface from the previous post with what I have done so far on this post. For this, I will start to create that thing which is the heart of an adventure game, from a programming point of view. The Parser.

The Parser (Rudimentary)
So. What is the parser? It is a block of code (I am going to create it as a separate function) that takes a command entered by the player, interprets it, and then executes the command. It can be very lengthy. Efforts can be made to reduce its core size by "proceduralizing" it somewhat (other, smaller sub functions), but I will not worry about that for now. The parser must recognize "words", the structure for which has already been created above. In this case, the parser will receive either one or two words (a direction or a verb and a noun). There will be much more about the parser in future posts; this time around? Well, it stays simple. It will receive direction commands only, so that I may at least navigate my map.

A good place to start, therefore, would be setting the "words" (directions, in this case). So, inside main(), I declare the array of the structure word as directions.

word directions[DIRS];
set_directions(directions);

...and I then write the function that sets the directions...

void set_directions(word *dir)
{
    dir[NORTH].code = NORTH;
    dir[NORTH].word = "NORTH";
    dir[EAST].code = EAST;
    dir[EAST].word = "EAST";

    // ...and so on, through SOUTH and up to WEST.
}

Now that I have set the directions that I will use, I need the way for the player to actually employ them through text commands. Basically, this means that when the player types "east" and presses enter, the program will take the word and check the current room for exits, and if there is an exit to the east, it will transport the player's location to that room. For this to happen, I am first going to need a variable that actually holds the players current position. I will call it the "location" variable, which makes it instantly recognizable. It gets declared and initialized at the top of the main() function...

int location = CARPARK; // using the enumerated type identifier, of course.

Further down in the main() function, inside the while loop, below the section_command() function call, I will call the parser() function, like this...

parser(location, word_1, word_2, directions, rooms);


Now I will actually write the rudimentary parser function itself...

bool parser(int &loc, string wd1, string wd2, word *dir, room *rms)

{
    int i;
    for(i = 0; i < DIRS; i++)
    {
        if(wd1 == dir[i].word)
        {
            if(rms[loc].exits_to_room[dir[i].code] != NONE)
            {
                loc = rms[loc].exits_to_room[dir[i].code];
                cout << "I am now in a " << rms[loc].description << "." << endl;
                return true;
            }
            else
            {
                cout << "No exit that way." << endl;
                return true;
            }
        }
    }
    cout << "No valid command entered." << endl;
    return false;
}

Taken from the top. I create the parser as a bool function. At the moment that is not a very useful thing to do, but when that parser grows, returning a boolean may well be a useful method for error trapping later on. It does no harm at present.

I then declare a local integer (i) to enable me to loop through commands. In this case, I start by looping through the directions. What I am doing is causing the program to compare what the player entered (and was passed to the parser as word_1) with the data for the member word in the structure directions. If word_1 coincides with a word member in the structure, then we have a hit, and the parser then checks if there actually is an exit form the current location in that direction (it checks the exits_to_room member of the room structure). If the result is something other than NONE, then there is an exit there, and the location variable (passed referrenced so that the function can permanently modify it) is moved to the room in that direction, using the directon code. It offers a description of the new room, and breaks out of the function, returning true. If there is no exit in the entered direction, the parser simply states so, and returns. And finally, if nothing that the player entered is recognized by the parser, then it returns false, telling the player that no valid command was entered.

Though not apparent here, this method of using the word structure "code" instead of the "word" itself can considerably shorten the coding of the parser. Eventually, it will permit the use of synonyms, expanding the possibilities of what the player may enter as an acceptable text command. For example, a command like "GO" may also be expressed as "WALK", but both words will have the code GO, making the parser do the same thing for two different words that mean the same thing.

The program can now be compiled and tested. I can now move around my map, entering simple direction commands like north, or east. Hooray! I am going places!

Here is the code of what was done for this post.

Here is a sample out put of what happens when the program is run.



Next time, that parser will be expanded a little, and introduce verbs. As yet, it is only using word_1 of the entered text, and that word must be a direction for the present parser to accept it.

That is all, for this post!

Sunday 4 November 2012

Text Adventure Games C++ Part 1


So, it has been a while again. Oh! How the demands of my job keep me from my beloved hobby!

In the continuing exploration of C++, though short of time lately to really get into more intricate subjects, I decided to revert to something simpler and, surprisingly, never tried before by yours truly. Text adventures. Why this is surprising is two-fold. One: The first ever game I played (on someone's Commodore Vic 20, lent to me for one afternoon sometime back in 1981 or so), and indeed my first ever experience of computers, was a text adventure (and it was to be my only experience of computers until my own Spectrum in 1986) . Two: It turns out to be quite easy to write a simple console style text adventure with the power of C++. Everyone should try it; it is fun!

Warning: I decided to tackle the challenge completely unprimed; that is to say, without reference to any books or tutorials about the subject, in order that it be a test of my "problem solving abilities". Therefore, the results on this post may not be "standard", if such a thing exists, for text adventures.

First Steps:
As far as I was able to deduce, text adventures are initially about two things, from a programming point of view. First, handling input text and second, moving around a map. Things like getting, dropping and using objects, and the occurrence of events in the adventure, will come later.

Though I can see now that handling text strings could have been a little bit of a chore previously (in the days when text adventures were first popular), with the tools available in C++, it is pretty much a simple task. The idea goes something like this;

1. The player inputs a line of instructions.
2. The program divides the line into individual words.
3. The program interprets the words and checks if the instruction makes any sense.
4. The program executes the command, if it makes sense, or by default informs the player that the command made no sense.
5. The program loops back to asking for a command to be input by the player.

The first item is very easy, using C++'s standard iostream and string.

#include "iostream"
#include "string"

using namespace std;

int main()
{
    string command;
   
    while(1 == 1) // Temporary condition for now...
    {
        command.clear();
        cout << "What shall I do? ";
        getline(cin, command);
        cout << "Your raw command was " << command << endl;
    }
    return 0;
}

I do not recommend compiling and running this fragment because of the infinite loop in the while statement, but this is as simple as simple gets for inputting a command. Now there are a few considerations, as I am about to move into the second item of the list; dividing the line into separate words. The first consideration is; how many words of the command do I want to be actually interpretted? For this example I want to keep it as simple as possible. By that I mean that I only want to interpret one or two words of the command, which restricts the player to either inputting a direction or a verb and a noun. For example, the player can input at the comand prompt...

What shall I do? north

or...

What shall I do? get keys

So, if the player inputs something like...

What shall I do? go get a club and hit yourself over the head with it

...will result in the program only interpretting the words "go" and "get", which will be an invalid command input. Straight off the bat we can see that some ground rules are established. With that in mind, I will continue to expand the above snippet. I need to make a function that will section the raw command line into one or two words. So, first the expansion of main()...

#include "iostream"
#include "string"
#include "vector" // For the command handling function.
#include "cctype" // Will be used to eliminate case sensitivity problems.

using namespace std;

int main()
{
    string command;
    string word_1;
    string word_2;
   
    while(word_1 != "QUIT") 
// I have provided an escape condition from the loop here
    {
        command.clear();
        cout << "What shall I do? ";
        getline(cin, command);
        cout << "Your raw command was " << command << endl;
        word_1.clear();
        word_2.clear();
        // Call the function that handles the command line format.
        section_command(command, word_1, word_2);
       
        // For test purposes, output the command after formatting by the function.
        cout << word_1 << " " << word_2 << endl;
       
    }
    return 0;
}

I have added vector and cctype headers, and two more strings (word_1 and word_2). These strings will hold the two words I want from the sectioned command line, for later parsing. In the while loop I also call a function (section_command) and pass it three arguments (command, word_1 and word_2). Here is that function, which does as its name implies; it sections the command.

void section_command(string Cmd, string &wd1, string &wd2)
{
    string sub_str;
    vector words;
    char search = ' ';
    size_t i, j;
    // Split Command into vector
    for(i = 0; i < Cmd.size(); i++)
    {
        if(Cmd.at(i) != search)
        {
            sub_str.insert(sub_str.end(), Cmd.at(i));
        }
        if(i == Cmd.size() - 1)
        {
            words.push_back(sub_str);
            sub_str.clear();
        }
        if(Cmd.at(i) == search)
        {
            words.push_back(sub_str);
            sub_str.clear();
        }
    }
    // Clear out any blanks
    // I work backwords through the vectors here as a cheat not to invalidate the iterator
    for(i = words.size() - 1; i > 0; i--)
    {
        if(words.at(i) == "")
        {
            words.erase(words.begin() + i);
        }
    }
    // Make words upper case
    // Right here is where the functions from cctype are used
    for(i = 0; i < words.size(); i++)
    {
        for(j = 0; j < words.at(i).size(); j++)
        {
            if(islower(words.at(i).at(j)))
            {
                words.at(i).at(j) = toupper(words.at(i).at(j));
            }
        }
    }
    // Very simple. For the moment I only want the first to words at most (verb / noun).
    if(words.size() == 0)
    {
        cout << "No command given" << endl;
    }
    if(words.size() == 1)
    {
        wd1 = words.at(0);
    }
    if(words.size() == 2)
    {
        wd1 = words.at(0);
        wd2 = words.at(1);
    }
    if(words.size() > 2)
    {
        cout << "Command too long. Only type one or two words (direction or verb and noun)" << endl;
    }
}

This function uses both vector and cctype functions. The cctype function toupper() is used to turn any lower case letters in the command line into upper case. This process is to simplify the internal workings of the program. All text commands in the program will be interpreted in upper case, but the player is still free to input lower case.

I want to go through that function bit by bit. I am going to use a space character to split the words of the command line, as it can be (reasonably) safe to assume that the command line will be input with spaces between words. A local substring will be used to "collect" the letters of each word in the command line, while no space occurs, and when a space occurs, it will push the substring (containing the word) into the local string vector. The substring is cleared, and the process is repeated for the next series of letters, either up to the next space, or to the end of the command line string, whichever happens first. By the end of the process, I will have a local string vector loaded with the individual words of the command line.

That said, there is at least one possible and plausible problem. If the player, by typo mistake, entered more than one space between words, the above method will "collect" and pushback a blank word into the vector. In a rudimentary effort at error trapping, I will endevour to eliminate the occurence of these blank words. I search through the vector strings, from end to beginning (ie; backwards) looking for such blank words. If one is found, it is erased with an iterator reference from the beginning of the vector (note, I do not actually use an iterator, just the reference that is normally used to set an iterator). Yes, a bit crude, as there are other ways to do this, but doing it this way exempts me from having to reinitialize the vector iterator every time the vector changes size because a blank word was eliminated.

Next, the remaining words in the vector need to be converted to upper case (as explained why above, and will become clearer later). The loop goes through each character in each vector string looking for the occurence of lower case characters and converting them to upper case.

Finally, the first two words of the string vector only are returned to the main loop by the function (wd1 and wd2). The rest of what was written on the command line is, basically, wasted time by the player. So, why do I go through the trouble of "collecting" all the words in the command line? Future expansion! One day, I might want to create a text adventure that accepts more complex commands, like for example "open door using crowbar", instead of just "use crowbar". The section command function is already up to meeting that challenge as it is now.

So, the program may be compiled and run now. I will have a console application that prompts me for a command and, after entering it, shows me the first two words of that command in upper case. Not very exciting, as yet, but more is coming on this somewhat archaic but none-the-less fun subject.

Click here for some code of what has been done on this post.

Here are some references to the headers used in this post...
cctype
vector
string
iostream
more on vector

And here is a sample output of the program so far...


Next time, navigating a game world map, where the enum types really come into their own!

That's all, for now!