Jump to content
  • Advertisement
Sign in to follow this  

Need advice for user input parser for text adventure

This topic is 1326 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This isn't the first time I'm doing this, but the first time it was simpler than what I intend now. My goal is to add a little bit of depth. My concern is mostly with how to handle this in the code, although I'm also struggling a bit to find a good way to describe objects and items in an external file, as well (not which format to use, just what elements to consider (name, ID, what variables, etc). So I guess my problem is twofold. 
 
A few examples of the input I would want to allow: (I'm using parentesis to mark irrelevant commands and commas to mark actual object names. Doing this only for clarity, the user wouldn't type type the markers. Also using slashes to separate equivalent commands.)
 
lock east door / lock "front door"                     (assuming "front door" is the one at the east)
cover east window (with) sheets / use sheets (on) "big window"
combine key (with) clay / use key (on) clay
(the object names, by the way, is what I thought could be used to distinguish two doors that might be in the same room, for example, as well as allow the player to refer to them by the name they see in the descriptive text of the area they're in)
 
I'm curious to know how people would go about doing such a thing. The last time I did it I ended up with an gigantic function with a switch() with many switch()'s inside it. I used arrays to separate and store the commands by categories (specials (quit, help, save...), verbs, directions, etc) and their short versions if available in subsequent dimentions, like this,...
string specials[3][7] = {
    {"exit", "quit", "help", "back", "save", "load", "\0"},
    {"----", "qqq" , "h"   , "bk"  , "----", "----", "\0"},
    {"\0"  , "\0"  , "\0"  , "\0"  , "\0"  , "\0"  , "\0"}
};

...and when I received input I'd compare them to find in which array the command was, and in which column.The column would be the criteria I'd handle in the big switch(). But I never went any further than to accept one command, because, like now, I was struggling to find a way to do it that made sense. My vision was that it would require at least one more huge switch() like the one I already had.

 
Considering the ammount of combinations, my guess is that I'll have to reduce whatever I get to a set of main commands, (i.e., "cover" and "combine" would evaluate to "use", "walk" to "go", etc.). Being that the case, I wonder if an enum would be of any use. I also wonder if my intermediary method with the arrays couldn't be superseeded by something else more handy.
 
That's the kind of thing I'm trying to figure out: the tools that are reasonably adequate for the job. Not how to code it, but what to code it with. I may come to simplify/streamline my goals, but I'll have to see what I can do before I make those decisions. Inform games had a pretty neat system for input, I wish I could see a source code for such an engine, because most of the open source games I found on google used choice menus... :|
 
Any help/insights/advice would be really apreciated. Thanks. 
Edited by Master thief

Share this post


Link to post
Share on other sites
Advertisement

I would start by splitting the user input string by space so you have an array of string

commandStrings = split("open door")
//becomes ["open", "door"]

Then write a function to search for specific words from a list. It should return the

index of the command as well as how many words the command. You can return

two values by either returning a struct or having one of the parameters passed by reference

// returns what index in vocabulary matches the commandStrings
// searching starts at parsePosition
// command length is an output parameter
// parse position specifies what index to start searching in vocabulary
// command length is the number of words the found vocabulary word is
// for example, "open" would return a commandLength of 1, "go back" would return 2
int searchCommandStrings(const vector<string>& commandStrings, const vector<string>& vocabulary, int parsePositon, int& commandLength);

actionVocabulary= {"open", "go back", ...ect}
int currentParsePosition = 0
int actionLength = 0;
int actionIndex = searchCommandStrings(commandStrings, actionVocabulary, currentParsePosition, actionIndex)
currentParsePosition += actionIndex 

Now that you have command index, you can use that to determine the behavior. I would recommend implementing the different behaviors as polymorphic classes, or function pointers.

typedef void (*ActionFunction)(Room& currentRoom, const vector<string>& commandStrings, int parsePosition);

// current objects is an array of strings of all objects in the scene

void OpenAction(Room& currentRoom, const vector<string>& commandStrings, int parsePosition)
{
    int objectLength = 0
    int objectIndex= searchCommandStrings(commandStrings, currentRoom.GetObjectNames(), parsePosition, objectLength)
   
   if (objectIndex == -1)
      // object not found
   if (currentRoom.GetObjects()[objectIndex].CanOpen())
      // open object
   else
      // cannot open object
}

void GoBackAction(Room& currentRoom, const vector<string>& commandStrings, int parsePosition)
...ect

// populate list of function pointers
ActionFunction actions[] = {&OpenAction, &GoBackAction, ...ect}

// to run a certian actions simply lookup the function pointer
ActionFunction action = actions[actionIndex]
// then call the action
action(currentRoom, commandStrings, currentParsePosition)
}

As you can tell, I didn't actually write compilable c++ code. I simply want to communicate the idea of seperating each action into a seperate class or seperate functions. This helps seperate your code into distict pieces. You also could easily map multiple names to the same action

string actionNames[] = {"use", "cover", "combine", ..ect}
ActionFunction actions[] = {&UseAction, &UseAction, &UseAction, ...ect}

I hope that helps.

Edited by HappyCoder

Share this post


Link to post
Share on other sites

I'd go with an array of strings and enums, something like this:

 

knownWords = {

 { "get", ACTION_PICKUP },

 { "pick up", ACTION_PICKUP },

 { "grab", ACTION_PICKUP },

 { "hold", ACTION_PICKUP },

 ...

 { "go", ACTION_MOVE },

 { "move", ACTION_MOVE },

 { "walk", ACTION_MOVE},

 { "crawl", ACTION_MOVE},

 { "climb", ACTION_MOVE}, 

 ...

 { "north", DIRECTION_NORTH },

 { "n", DIRECTION_NORTH },

 ...

 { "bucket", OBJECT_BUCKET },

 { "can", OBJECT_BUCKET },

 ...

 

Then I would make one pass through the string trying to convert all the words into a series of known tokens, giving an error on a parse failure.

 

That would be followed by a simple tree as you described, working with the data of the map to ensure when I get ACTION_MOVE DIRECTION_NORTH the map actually has a North direction to travel, giving output accordingly.

Share this post


Link to post
Share on other sites

What I'm doing in my miner (which isn't quite the same thing, but hopefully will inspire you).

At a glance: it gets messages from the peer in JSON in a non-performance-critical way so I cut some corners here and there. Note since this is JSON there's no real "parsing" at my handler level but rather mangling the json structs directly.

 

There are two contexts in which this can happen. In the easiest case (where I could design the protocol) I just pull out a verb from the message. This selects the parser to use which MUST be able to consume the syntax or signal an error message. Selecting the parser is just as easy as looking up an std::map! Because in this case I check the verb in advance the selected parser is guaranteed to work. Here's how it looks (rapidjson). The parsers in this case are added to an internal vector in an initialization function which can be changed to meet other needs. This went through a few interations, I'm still not 100% satisfied with it but rather close, it's very extensible and ... ideally can also support "aliases" like frob notes above. As long as the parsers are lightweight the cost involved in maintaining their state is very low.

 

There is another context where I had to interface with the legacy protocol. For some reason I forgot I use a different approach here. I have this function taking advantage of template which creates on stack a bunch of temporary parser objects. Each of those parsers can 1) ignore the message, not theirs 2) mangle it 3) signal error.

I just keep trying parsers as long as 1 happens. This is basically an implicit switch but slightly better looking and easier to update IMHO. In particular, no enums are required either.

As you see, I don't really stop when a parser matches... so far my CPU load is often lower than 3% so I'm fairly happy with how it turned out.

 

EDIT:

I guess it's worth noticing it's not all roses. The interfaces would sure have enjoyed some additional design, here's how it pulls all the data together (monitor, admin). Hopefully you don't have to be as ugly!

Edited by Krohm

Share this post


Link to post
Share on other sites

I would go with what frob said. Very good approach! I have never even thought about text based adventure games but sounds like a great way to take on the problem.

Share this post


Link to post
Share on other sites
I developed a text adventure input parser for a MUD I wrote in Java some years ago, I found it to be a great exercise.

What I did, as others have said is break the input string into tokens. You then search the tokens for patterns of verbs and nouns. So if you had this:

Open the door, go through door and take all

The verbs here are open, go (through) and take, so you would search tokens after each verb looking for a noun. To keep it simple you can ignore any 'the's. If you come across another verb before finding the noun, you could interpret that as something like:

Open and go through the door

You then just build a stack of things to do (verbs) on the eventual noun. When you find a noun, you start the process again from the following verb. If you find a list of nouns following the verb then you can interpret that as:

Take sword and shield and go through door

Which would be {VERB, (NOUN, NOUN)}, {VERB, (NOUN)}

What I did for finding nouns was first search the player's inventory, then the inventory of the room. My MUD rooms and 'data' were interpreted (as in Lars Pensjo's original lpmud) so for verbs, I had to check the existing standard vocabulary (go, north, take, drop, etc) and check the object you're in or holding for any special commands and run that code if you find any. You can even stretch it (as I did) to make the player fully interpreted so 'take' was a command written in the c-like language you interpret. Depends how far you want to go with it I guess,

As frob said, linking up multiple words to the same verb is useful for things like get, take, pick, etc

All exciting stuff though, text adventures are timeless in my opinion and a great way to learn to code in a different language as they cover so much.

Share this post


Link to post
Share on other sites

Sorry for the delay, my internet is down so it's going slow for me. Using local library's internet for now.

 

Anyway, at a glance I saw quite some useful stuff. Much more than I expected. I'm saving this page and some links and going to read through this at home. I'll be back soon.

 

Thanks everyone.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!