Text Adventure Command Parser

Hey guys,

I'm trying to write a simple text-based adventure game that I can expand on as a hobby and I'm wondering what the best way to implement a command system is - additionally how to parse those commands most efficiently.

The only way I can think of is making a class called "CommandLibrary.h" or something and creating a string array of "possibleCommand" that holds all of the commands a user can enter. When a user enters a command, it then checks that command against the array to see if it's valid.

Similarly, when a user enters a command like "Cast inferno ogre", how do I parse that command properly for comparison to properly execute the command?
Last edited on
This can get quite complicated depending on how smart you want your parser to be. (in fact, text adventure games are IMO actually more difficult in general than a simple 2D shooter or something... but that's a side point).

I would start by classifying "verbs" and "nouns" and create a list of recognized words. Every command that the user is going to input is going to have a verb.

For example: "Take potion", "drink potion". Take/drink are your verbs, potion is your noun. You would need to identify the verb first so that your program can identify what to do.

More complex verbs could use 2 nouns. Such as "Use key in lock". 'Use' is your verb requiring 2 nouns... and 'key' and 'lock' are your nouns. You might be able to get away with ignoring the preposition 'in' here (which would allow "use key lock" to have the same effect.... or "use the key in the lock" would also be the same).

In some cases the verb could be implied. "east" is actually a noun, but if the user only gives that, you could imply that the verb is "go" and internally treat it as if they typed "go east".


But yeah ultimately it's going to come down to having a dictionary of recognized verbs.
Thanks so much for your quick reply.

So maybe have 3 classes "Verb" (Class listing all of the verbs) and "Noun" (Class listing all nouns) and "Command", lets say.

A command is made up of a "Verb" and a "Noun" (We're starting simple). When a command is typed by the user "Drink Potion", lets say, the command is parsed into it's "Verb" and "Noun" format:

Drink -> Verb
Potion -> Noun

Then we'd do something like Drink.checkVerb() to see if it was a proper verb and Potion.checkNoun() to see if it's a valid noun (each would return a boolean), if both return true then the command is valid and we can start looking to execute the command?

Example

Struct Verb
{
string verb;
string possibleVerbs[ 5 ] = { "drink", "go", "cast", "take", "use" };
bool checkVerb() { // cycle through possible verbs, if match return true }
}

struct Noun
{
string noun;
string possibleNouns[ 3 ] = { "potion", "key", "barrel" }
bool checkNoun() { // cycle through possible nouns, if match return true }
}

struct Command
{
verb verb;
Noun noun;
bool isValidCmd() {//if checkNoun and checkVerb true, valid command}
}

... execute proper command ... ? (Of course the above code is probably riddled with syntax errors, but you get the idea)
Last edited on
I don't know if a bool will suffice here. Eventually you're going to need to know more than just whether or not the word is valid, you're going to need to know what word it is so you can take the proper action.

Maybe an enum would be better:

1
2
3
4
5
6
7
8
enum class Verb
{
   drink,
   go,
   use,

   invalid
};


Then maybe have some kind of dictionary that maps a string to a given verb:

1
2
3
4
5
std::map< std::string, Verb >  knownVerbs;
knownVerbs["drink"] = Verb::drink;
knownVerbs["go"] = Verb::go;
knownVerbs["move"] = Verb::go;
// etc 


When it comes time to parse the word...
1
2
3
4
5
6
7
8
Verb parseVerb(const std::string& word)
{
    auto w = knownVerbs.find(word);
    if( w == knownVerbs.end() ) // not found
        return Verb::invalid;  // invalid verb

    return w->second;  // otherwise return the verb
};


Then to parse the command:
1
2
3
4
5
6
7
8
9
10
11
12
Verb verb;
Noun primary;
Noun secondary;

parseCommand( user_command, verb, primary, secondary );

switch(verb)
{
case Verb::invalid:  /*...print error message...*/ break;
case Verb::drink:  /*...drink the primary noun */ break;
case Verb::use:  /* ... use the primary noun in the secondary noun */ break;
}


etc
Still Processing this - I'm sure it will make more sense after I research some of the concepts you're using here, like Maps and such. The idea makes sense, but the implementation is a bit fuzzy as I don't appear to be as fluent in C++ as you are. This definitely helps tho. The idea of mapping strings to commands makes sense.
In your example, something is confusing me and perhaps -- if you had time -- you could explain? I did some research on it, and couldn't find much.

I'm not sure what

1
2
3
4
5
6
7
8
enum class Verb
{
   drink,
   go,
   use,

   invalid
};


does or what it is. Why use "enum class" instead of just "enum"?

I know that an enumeration is basically just a way to quickly create a structured set of constant integers, but why would I want to map strings to integers? Because switch only handles ints and it's a convenient way to process the int-converted strings?

Sorry for bothering you again.
Why use "enum class" instead of just "enum"?


"enum class" is a C++11 feature that makes a "strongly typed" enum.

With a normal "enum" the type is basically just an int, but with "enum class" it is treated as it's own type. It basically makes using the enum more strict (less chance of making a mistake).

but why would I want to map strings to integers? Because switch only handles ints and it's a convenient way to process the int-converted strings?


That's one reason.

Really integral types are just easier to work with. Less chance to screw them up. I avoid embedding string literals into my code when possible/practical.

For example:
1
2
3
4
5
6
7
8
9
10
11
12
// if using enums
if( foo == Verb::dirnk ) // whoops!  Typo.
   // but it's OK because the compiler will give you an error so you can easily
   // find and fix this.


// if using strings
if( foo == "dirnk" )  // whoops!  Typo.
   // but now it's a problem because the compiler will not complain (it doesn't know
   // you misspelled it).  So your program will run, but will behave incorrectly...
   // and now you have to debug and find out where in your program the problem
   // is 
Cool! That totally makes sense. You're the best. Thank you so much for all of your help. I tend to shy away from C++ forums because some people can be critical/condescending when it comes to programming - but you were very nice and helpful :)
Happy to help. =)
Topic archived. No new replies allowed.