I need to get rid of this pile up of if statements. I'm working on a game similar to zork and here is what I have
-My parser gets input, splits into two words and stores them as strings(rejects more than two words)
-passes into another class to handle the command.
Where the problem is, is that when it gets to the other class, I wasn't sure how to implement it correctly. I used a ton of ifs and else ifs to handle the command. I'm sure there is a better way to do this though. I have like 30 commands right now in early stages in development of the game. When it's done it will probably have a few hundred. I don't want to have all these if statements in here. It looks like this:
I'm assuming C++ and a text adventure here. Hope that is correct. C++ is not a great choice for text adventures but that is not really the point of the thread I guess.
Maybe you could consider something like:
[source lang="cpp"]
class Object
{
public:
Object(/*...*/);
int location;
std::string noun;
std::string description;
};
// simplified...
std::vector<Object> objects;
int playerLocation;
class Action
{
public:
virtual void do(const std::string &noun)=0;
};
std::map<std::string,Action*> actions;
class TakeAction
{
public:
virtual void do(const std::string &noun);
};
First off use something like a std::map to map your tokens to information. You could then make a bunch of contained classes that modify your gamestate and store them in the map. That way your command logic boils down to a search of the map. Likewise, you can store your objects the same way you store the commands, to allow the same searching.
// To take care of your commands, you'd make a CommandInfo structure like so:
typedef std::vector< std::string > tCommandListType;
class Command
{
public:
virtual ~CommandInfo() {}
virtual void parse( const tCommandListType & ) = 0;
};
// and some commands
class EatCommand : public Command
{
public:
virtual void parse( const tCommandListType &commands )
{
assert( commands[0] == "eat" );
if( commands.size() < 2 )
{
std::cout << "Eat what?" << std::endl;
}
else if( commands.size() > 2 )
{
std::cout << "You can only eat one thing at a time." << std::endl;
}
else
{
// logic to remove the item and whatnot
std::cout << "That <" << commands[1] << "> sure was yummy." << std::endl;
}
}
};
// Now setup a mapping of all the things you want to accept as commands
typedef std::map< std::string, Command * > tCommandMapType;
tCommandMapType commandMap;
commandMap["eat"] = new EatCommand;
commandMap["consume"] = new EatCommand;
while ( player_not_dead )
{
// read the input "eat rope", "use match on candle" etc.
// tokenize it on each space into a tCommandListType, so you have
// tokens[0] == "eat"
// tokens[1] == "rope"
// locate the command and run it.
tMapType::iterator itr = commandMap.find( tokens[0] );
if( itr != commandMap.end() )
{
itr->second->parse( tokens );
}
}
You could try creating a data structure that represents a mapping from (verb, noun) -> handler class/function pointer, then search that data structure to process a command. It is also easy to maintain flexibility by supporting wildcards with that. Something like
using namespace std;
// Define the type signature of a function which handles input commands.
typedef void (*CommandHandler)(string, string);
// Define the type signature of the data structure which stores our mappings.
typedef map<pair<string, string>, CommandHandler> CommandHandlerMap;
// Create the map to contain the handler mappings.
CommandHandlerMap handlers;
// This function handles "pickup" "*" if "pickup" was not handled by some other means.
void PickupHandler(string verb, string noun)
{
cout << "You picked up a " << noun << endl;
}
// This function handles "pickup" "moon".
void PickupMoonHandler(string verb, string noun)
{
cout << "Are you silly? You cannot pick up the moon!" << endl;
}
// This function handles "destroy" "*".
void DestroyHandler(string verb, string noun)
{
cout << "You destroyed a " << noun << endl;
}
// For each input command, dispatch it through the mapping table to its proper handler.
void ProcessInputCommand(string verb, string noun)
{
CommandHandlerMap::iterator iter = handlers.find(make_pair(verb, noun));
if (iter == handlers.end())
iter = handlers.find(make_pair(verb, "*"));
if (iter != handlers.end())
iter->second(verb, noun);
else
cout << "No handler found for command \"" << verb << "\" \"" << noun << "\"" << endl;
}
Not a huge amount of advantage in this case of a switch over an if chain really in terms of maintenance, plus OP is switching on two values, so switch is not a good suggestion really.
I thought about that, but it's a string so it won't work.
On that note, there is a lot to be said for mapping verbs and nouns to integer IDs before processing. You can then have two objects share the same noun, for example, without having the word duplicated. Sort of like a database normalisation principle really.