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.

1 comment:

  1. Hi,

    I've been following your blog, i found it because i'm currently developing for iphone, and i always liked this text adventures, it's a grea job you're doing here.

    Many thanks for your help and keep up this awesome work.

    Best Regards from Portugal
    Guilherme

    ReplyDelete