Sign in to follow this  
stein102

how to eliminate multiple if statements?

Recommended Posts

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:

[code]
if(verb.equals("take")&&noun.equals("something")){
take(something);
}
[/code]

Share this post


Link to post
Share on other sites
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);
};

void TakeAction::do(const std::string &noun)
{
for(auto o=objects.begin();o!=objects.end();++o)
{
if(o.noun==noun && o.location==playerLocation)
{
std::cout << "You take the " << o.description << ".\n";
return;
}
}

std::cout << "There isn't one of those here.\n";
}

void init()
{
objects.push_back(Object("hat","large, floppy hat"));
// etc

actions["take"]=new TakeAction();
// etc
}

void handleCommand(const std::string &verb,const std::string &noun)
{
auto a=actions.find(verb);

if(a==actions.end()) std::cout << "I do not understand.\n";
else a.second->do(noun);
}
[/source]

Share this post


Link to post
Share on other sites
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.
[code]

// 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 );
}
}


[/code]

Share this post


Link to post
Share on other sites
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

[code]

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;
}

// Register our mapping table.
void Init()
{
handlers[make_pair("pickup", "*")] = PickupHandler;
handlers[make_pair("pickup", "moon")] = PickupMoonHandler;
handlers[make_pair("destroy", "*")] = DestroyHandler;
}

// 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;
}

[/code]

Share this post


Link to post
Share on other sites
[quote name='TheCompBoy' timestamp='1313825189' post='4851510']
Thats what Switch statements is for..
[/quote]

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.

[quote name='stein102' timestamp='1313825231' post='4851511']
I thought about that, but it's a string so it won't work.
[/quote]

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.

Share this post


Link to post
Share on other sites
[quote name='stein102' timestamp='1313825344' post='4851516']
I totally forgot to mention I'm using Java lol
[/quote]

Java has a [url="http://download.oracle.com/javase/6/docs/api/java/util/Map.html"]Map interface[/url] with a number of implementations. You can use that to do somthing similar as with std::map.

Share this post


Link to post
Share on other sites
For the sake of simplicity, I am going to assume that your actions are String objects. If you want to make your own Action class with customized parameters, that's up to you. But here it is:

[code]
public interface ActionHandler {
public void onAction(String a);
}

Hashtable<String, ActionHandler> actionsMap = new Hashtable<String, ActionHandler>();

/* somewhere where you define the actions and handlers */
actionsMap.put("walk", new ActionHandler {
public void onAction(String a) {
// do something here when user execute 'walk' command
}
});
actionsMap.put("talk", new ActionHandler {
public void onAction(String a) {
// do something here when user execute 'talk' command
}
});
... do more for other actions


/* somewhere where you execute the actions */
public void executeAction(String action) {
ActionHandler handler = actionsMap.get(action);
if (handler != null) {
handler.onAction(action);
}
}

[/code]

Thanks to Java anonymous class, you don't actually need an if statement for each action, as it has its own implementation.


However, if you do decide that all your actions should be handled by one class, then you still need to do if statements. For example:

[code]
public class SomeClass implements ActionHandler {
public void onAction(String action) {
if (action.equals("walk")) {
// do something for walk
} else if (action.equals("talk")) {
// do something for talk
}
}
}
[/code]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this