Sign in to follow this  
Funyun

Menu hierarchy for text-based MUD.

Recommended Posts

I've just been playing around with C++ for a while and trying to make a very rudimentary text-based MUD. I plan on having a menu hierarchy where a player can select a certain menu, which leads them to another menu, then another, until they reach the specific option they want to choose. I used to do it by using case statements, such as:
switch(var)
{
   case 1:
      switch(var2)
         {
            case 1:
            // do something
            break;

            case 2:
            // do something
            break;

            default:
            break;
         }

   case 2:
      // etc.
      // etc.
}

Is there a better way to do this? It gets very tedious when I have lots of menus and are testing for different conditions. Any help? Thanks.

Share this post


Link to post
Share on other sites
i think if your looking at a system of menus within menus, you should look at a pseudo recursive algorithm. Something like having your menu object allowing other menu objects to be selectable, that way instead of having a bunch of conditional statements which leads to inflexible and illegible code, you could have an easily changeable system that would lead to more elegant code. I'll give a code sample a whirl, hopefully it can get across what i'm trying to say. EDIT: tried to make the code more clear.


selection(MenuChoice obj) {
if obj isn't another menu {
process the selection
}
else {
selection(get the user's choice on the submenu)
}
}


Share this post


Link to post
Share on other sites
You are describing a tree structure actually. N-Level nested menus can be most easily traversed via recursion, though if you are manually traversing it you won't need to do that. You will need to store handles to menu lists within each element that needs to go deper.

Something like this (VERY quickly done):


#include <iostream>
#include <string>
#include <vector>
#include <list>

using namespace std;

//Need to write a predeclaration so we can use this in the next class
class MenuOptionList;

//This class contains the information required for a specific option node.
//I have chosen to have an integer return value, but you could as easily contain
//textual output in string form that gets displayed when the option is selected.
//Or whatever else you need. The MenuOptionList handle is to contain reference to
//a nested option list when this option is selected.
class MenuOption{
public:
string label;
MenuOptionList *handle;
int returnValue;
};

//just a basic wrapper with the option list. Not sure if you want to carry more info
//within this.
class MenuOptionList{
public:
vector<MenuOption> values;
};

//This is the actual menu containing all the nexted options and the present level
//of options being viewed. Basic function to get an option from the stored option lists...
//contrary to the name, it won't go based on "Depth" so much as the flat index... Too lazy
//to rename that function.
class FullMenu{
public:
MenuOptionList* GetOptionListAtDepth(int depth){
list<MenuOptionList>::iterator cell;
int i;
for(cell = optionListStorage.begin(), i = 0;cell!=optionListStorage.end() && i<depth;i++, cell++){;}
if(cell!=optionListStorage.end()){
return &(*cell);
}
return NULL;
}

list<MenuOptionList> optionListStorage;
MenuOptionList* presentLevel;
};

int main(){
FullMenu mainMenu;
MenuOptionList tmpOL;
MenuOption tmpOption;
MenuOptionList* OptionList;

//Here we set up the individual options. Push them onto the tmp optionlist
//one at a time.
tmpOption.label = "First Option (Go Deeper)";
tmpOption.returnValue = 1;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

tmpOption.label = "Second Option (Get Value)";
tmpOption.returnValue = 2;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

tmpOption.label = "Third Option (Quit Program)";
tmpOption.returnValue = 3;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

//When we have all the options for a level of our option list we push the tmp
//variable onto our storage. We'll rinse and repeat, I won't comment the below ones
//as they are the same steps.
mainMenu.optionListStorage.push_back(tmpOL);

tmpOL.values.clear();

tmpOption.label = "Second List First Option (Go Deeper)";
tmpOption.returnValue = 4;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

tmpOption.label = "Second List Second Option (Return to Top Level)";
tmpOption.returnValue = 5;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

mainMenu.optionListStorage.push_back(tmpOL);

tmpOL.values.clear();

tmpOption.label = "Third List First Option (Return to Top Level)";
tmpOption.returnValue = 6;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

tmpOption.label = "Third List Second Option (Return to Second Level)";
tmpOption.returnValue = 7;
tmpOption.handle = NULL;

tmpOL.values.push_back(tmpOption);

mainMenu.optionListStorage.push_back(tmpOL);


//DONE populating, now to link the lists a bit. (link the linked lists lol)
//basically here we're setting the traversal of the menus.
OptionList = mainMenu.GetOptionListAtDepth(0); //we could error check for null, but I'm too lazy
OptionList->values[0].handle = mainMenu.GetOptionListAtDepth(1);

OptionList = mainMenu.GetOptionListAtDepth(1);
OptionList->values[0].handle = mainMenu.GetOptionListAtDepth(2);
OptionList->values[1].handle = mainMenu.GetOptionListAtDepth(0);

OptionList = mainMenu.GetOptionListAtDepth(2);
OptionList->values[0].handle = mainMenu.GetOptionListAtDepth(0);
OptionList->values[1].handle = mainMenu.GetOptionListAtDepth(1);

//NOW set the present level of our nested menu:
mainMenu.presentLevel = mainMenu.GetOptionListAtDepth(0);
//DONE setting up our options.

int i;
int selection;
int ListLength = 0;
int ReturnedValue = 0;
//and here we simply traverse the menus until the quit option is selected.
//You will realistically simply want to stop looping after you reach a
//selection which does NOT have a sub-menu. I'll comment where you can detect
//that below.
while(ReturnedValue!=3){
ListLength = mainMenu.presentLevel->values.size();
for(i = 0;i < ListLength;i++){
cout << (i+1) << ": " << mainMenu.presentLevel->values[i].label << endl;
}
cout << "Make your selection:" << endl;
selection = 0;
while(selection <= 0 || selection > ListLength){
cin >> selection;
}
ReturnedValue = mainMenu.presentLevel->values[selection-1].returnValue;
cout << endl << "SELECTED VALUE (" << ReturnedValue << ")" << endl;
if(mainMenu.presentLevel->values[selection-1].handle != NULL){
mainMenu.presentLevel = mainMenu.presentLevel->values[selection-1].handle;
}else{
//And here, if there is no sub-menu option you would specify to the containing
//while loop that we are done. Then you can simply write a switch case for the
//option selected, or write function handles to deal with the return values
//and store that directly in the menu structure to allow for the selection to be
//completely automatic. That might be a bit above and beyond what you need/want.
}
}
cout << endl << "See You!" << endl;
return 0;
}





Please keep in mind I have no accessor functions or error checking or anything of the sort really... Well, a tad bit of error checking to make sure the range entered is correct, but if you enter 'a' for example, it will break. (I usually cast user input from string to whatever I need as it is safer.) Typically you would want to actually hide all the structures used in maintaining the handles etc and write some nice functions to handle all that mucky work for you. But this is the basic idea and should be more than enough to implement a system like you describe (it is basically what you described minus some nicer code handling.)

[Edited by - M2tM on August 17, 2008 7:43:12 PM]

Share this post


Link to post
Share on other sites
Similar to M2tM's tree structure, I might go with a data structure like this:

struct Menu;

struct MenuItem
{
std::string itemText; /* the text to display with this item, excluding listing number prefix (which sould be generated automatically) */

Menu* containingMenu; /* the menu we're inside (can't be NULL) */
Menu* nextMenu; /* the menu we go to when selected (NULL if leaf item) */

int (*onSelected)(void* context); /* function to call when this item is selected */
/* when nextMenu is non-NULL: return 0 to immediately exit menu, return non-zero to go to next menu */
/* when nextMenu is NULLL: return 0 to immediately exit menu, return non-zero to go up to parent menu (containingMenu->parentMenu) */
/* if function pointer is NULL and nextMenu is NULL, then exit the menu when this item is selected */
/* if function pointer is NULL and nextMenu is non-NULL, then go to the next menu when this item is selected */
};

struct Menu
{
Menu* parentMenu; /* the menu we came from */
MenuItem* spawnMenuItem; /* the menu item that spawned us */
/* you can probably chose to have only one of the above variables, depending on your needs */

std::vector<MenuItem> menuItems; /* a list of all our menu items */
};

Menu* currentModalMenu; /* the menu we're currently displaying, modal because you can only be interacting with one at any given time */


Aside from the data structure, there are a few other things you need to think about. For instance, is an entire menu tree going to be built at the start of an "interaction" (when a root menu is first spawned), or will they be built dynamically as the player traverses the menu tree? If two menu items point to the same sub-menu (i.e. a menu that has yes/no options, which might be quite common), do they point to the same instance, or two separate instances?

Share this post


Link to post
Share on other sites
Zipster has brought up some great points (I thought I had mentioned making a callback function handle within each node, but looking back I do not believe I did, Zipster's got it in his structure though so you can see how that integrates) and a consolidation of my more sprawling structure. Depending on what you need to do either solution would work, but I suppose this comes down to deciding what you want to do in detail before jumping ahead with something. As soon as you pick one menu system it may be difficult to break out of it without re-writing your option nodes or writing a tree dump and then creating some sort of import (which would not be terrible because you could then save menus to file instead of hard-coding it in as my example shows.)

Anyway, consider the point and come back if you have further more specific issues.

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