gabelossus
gabelossus

Reputation: 1

C++ Avoiding Duplicate Code in Switch Statements?

I am working on cleaning up an old platformer game of mine that was written outright horribly (sorry, past me). The execution time and memory management has not been great and so I'm trying to condense a lot of the redundant code. One area that is hanging me up is the void function that is called to load a specific level from an enum value (I have an enum header called EnumState that stores names for all the various levels), here's what I can recall (currently at work without access to the exact source code):

void loadLevel(EnumState state, GameHandler *handler)
{
    switch(state)
    {
    case main_menu:
        mainMenu(handler);
        break;
    case lvl_1:
        lvl1(handler);
        break;
    case lvl_2:
        lvl2(handler);
        break;
    //...so on and so forth...
    }

}

EnumState.h:

enum EnumState
{
    main_menu,
    level_select_screen,
    lvl_1,
    lvl_2,
    lvl_3,
    //...and so on...
};

This loadLevel function drives me bonkers because all it really does is call the lvl_xxx() function for the # of level that you already knew the number of anyway when you passed it into the function! It's main purpose is for the file handling routine within the main menu - when you select a previously saved game, the integer of the last level you left off is saved in a text file, and that crazy long switch statement (longer than the iceberg tip I showed above) is responsible for taking in an enum version of that integer (once it was converted from int to EnumState in the gotolevelfromint function shown below) and then loading the corresponding void function of that numbered level.

EnumState goto_level_from_int(int whatLevel)
{
    switch(whatLevel)
    {
    case 1:
        return lvl_1;
        break;
    case 2:
        return lvl_2;
        break;
        //...so on...
    }
}

I almost wish there was a way to call a function based on a concatenated string value (e.g. instead of having to call lvl1(handler) in the switch block, you could call: lvl_ + levelNumber + (handler) , where "levelNumber" is the integer value pulled out of the previously mentioned text file).

This bottomless switch block has been an issue lately in several of my programs. I have heard of vectors and maps being good replacements but have no experience with either.

Btw, the "handler" parameter seen above is an object of a class "GameHandler" I setup that contains arrays of all the images, fonts, music, etc. that are initialized once in Main.cpp and then passed throughout the game as needed thru these *handler pointer parameters. That is why each level's void function takes that as a parameter. I'm not sure this is the best way to support switching between multiple levels. Maybe just have 1 main while loop where after you advance to the next part of the game it just overwrites the locally used background image, music, etc. object pointers? Not sure how that is normally done. Again, the memory management has not been great - when I go to the pause screen mid-game and exit to Windows, it cycles back through all the previous states - level select screen, main menu, etc. in reverse and then crashes on error with a dialog box.

I'm sure there's some great feature of C or C++ I'm totally missing the knowledge of here that could condense this up nicely. Any advice is greatly appreciated!

Years ago when I initially started development I tried to avoid an integer level handling system altogether, and instead have the last level progressed to saved in the same text file as a string (e.g. "lvl1"). My idea was to setup one, only one, main switch statement in the main menu function's file handling routine that would call the void function of a level that matched the text string's value. Well, apparently I forgot that in C++, switch() does not work with strings, and even if it did work I think that switch block alone would have gotten crazy long and can still be avoided somehow. If I remember anything from my Python days in class, we got around an issue like this with dictionaries...

Upvotes: 0

Views: 145

Answers (2)

tmakino
tmakino

Reputation: 11

I think you can use a map from enum to the function, if all the function has the same set of parameters:

const std::unordered_map<EnumState, std::function<void(GameHandler *)>> 
  state_func_map = {
    { main_menu, mainMenu },
    { lvl_1, lvl1 },
    { lvl_2, lvl2 },
    ...
};

void loadLevel(EnumState state, GameHandler *handler)
{
  if (auto x = state_func_map.find(state); x != state_func_map.end()) {
    x->second(handler);
  } else {
    *** raise error ***
  }
}

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490663

You could use something like a std::map to hold pointers to functions:

using level_handler = void (*)(GameHandler *);
static const std::map<EnumState, level_handler> levels = {
    { main_menu, mainMenu },
    { lvl_1, lvl1 },
    { lvl_2, lvl2 },
    { lvl_3, lvl3 }
    // ...
};

void loadLevel(EnumState state, GameHandler *handler)
{
    auto handle = level_handlers.find(state);
    if (handle == level_handlers.end())
        throw std::runtime_error("Invalid level");
    (*handle)(handler);
}

Of course, depending on how you're handling errors in general, you may prefer something other than throwing an exception if you get a bad level number.

Depending on the situation, you might want to use an array of pointers to functions instead of a map. This will usually be at least a little faster executing, but means any changes to EnumState and the array contents have to be kept in synchronization, which can be a maintenance problem.

Upvotes: 2

Related Questions