Valid Command Line Input

Started by
6 comments, last by Chad Smith 11 years, 3 months ago

I haven't done a lot of C++ Command Line I/O in quite a while so I have forgotten a couple of things.

Anyway I started to work on a game just for the command line because I wanted to work on my code design skills more. I felt having to worry about a graphics API would only get in the way. The code design would be close to the same and I'd just have to trade in graphics when I felt like it mostly.

Well the game ended up getting bigger than I thought it would for such a simple game and now I find myself posing this question. I have a game that has a lot of "menus" (with the command line that's almost all their can be) and I find myself repeating a lot of code in places just to wait until I get valid input from the user. Meaning making sure the user inputs an integer (that's no problem) but also making sure the user does a valid selection for that menu. Quick example of some code from a menu in my game.

From the menu from where the user is selecting their game mode:


//GetMode asks the user what mode they want to play on and returns mode selected
GameMode Game::GetGameMode()
{
	bool validInput=false;
	GameMode gameMode;

	while(!validInput)
	{
		int choice=ToInt(StringInput());

		switch(choice)
		{
		case GameMode::COMPUTER_PLAYER:
			gameMode=GameMode::COMPUTER_PLAYER;
			validInput=true;
			break;
		case GameMode::PLAYER_COMPUTER:
			gameMode=GameMode::PLAYER_COMPUTER;
			validInput=true;
			break;
		default:
			std::cout<<"Please enter a valid choice using the number next to your desired game mode."<<std::endl;
			std::cout<<"Press ENTER to try again..."<<std::endl;
			std::cin.get();
			DisplayGameMode();
		}
	}
	return gameMode;
}

Also here is an example when the user is selecting their difficulty:

Difficulty Game::GetDifficulty()
{
	bool validInput=false;
	Difficulty gameDifficulty;

	while(!validInput)
	{
		int choice=ToInt(StringInput());
		switch(choice)
		{
			case Difficulty::EASY:
			{
				gameDifficulty=Difficulty::EASY;
				validInput=true;
				break;
			}
			case Difficulty::MEDIUM:
			{
				gameDifficulty=Difficulty::MEDIUM;
				validInput=true;
				break;
			}
			case Difficulty::HARD:
			{
				gameDifficulty=Difficulty::HARD;
				validInput=true;
				break;
			}
			case Difficulty::EXPERT:
			{
				gameDifficulty=Difficulty::EXPERT;
				validInput=true;
				break;
			}
			case Difficulty::GOD:
			{
				gameDifficulty=Difficulty::GOD;
				validInput=true;
				break;
			}
			default:
			{
				std::cout<<"Please enter a valid choice using the number next to your desired difficulty."<<std::endl;
				std::cout<<"Press ENTER to try again..."<<std::endl;
				std::cin.get();
				DisplayDifficulty();
			}
		}
	}
	return gameDifficulty;
}

As you can see they both share A LOT of things in common. Only difference is what they return and the different cases. I feel I am repeating entirely too much code over and over just to make sure the user selects valid input. I know I could easily write a function that checks to see if the input is an integer. That's no problem. Though because every menu is different and every menu doesn't have the same amount of choices to chose from what could I do to keep from repeating the same valid Input code and the user did valid input for that given menu?
I might not just be thinking in a Command Line I/O mindset right now which might be why I'm stumped by something so easy. Though from what I've been taught in programming that once you start repeating the same type of code it usually means it could somehow be wrapped into a function. That's not the only place I repeat it. I repeat it in multiple places.
Is their something I'm missing? What would be the best way to make sure the user selected a valid input for that menu without having to repeat code over and over?
Thanks for any help!
EDIT: ToInt and StringInput are both functions that I wrote myself. StringInput just gets the input as a string and returns in. ToInt just takes a string and returns an int from it using a std::stringstream
EDIT 2: switched to using code tags, forgot source tags were removed.
Advertisement

I would probably write several variants of input for common cases, such as a function that get's input as a string(as you have now), a function that gets input as a number(this function would validate integer data), and an input that take's an input as an integer, and checks that value between a min/max values.

you can also use variable args/arrays to create lists of strings, or numbers for validation.

but yes, function that stuff big time!

Check out https://www.facebook.com/LiquidGames for some great games made by me on the Playstation Mobile market.

I just want to point out: Plugging in graphics after isn't as easy as you think. You can't take something in the command line and add graphics without almost completely rewriting it. When I got started, that's what I thought, and it's not true.

However, you're approaching this in a very smart way. Starting with a small, text-based Role Playing Game will help you understand game design, programming, and you're language of choice far better. It will also cause you to have a better work ethic. So many people stop game programming because they start too big and don't get the basics down. Your work ethic is important.

Throughout my current project I've had to channel my work ethic, mainly because when you start more serious projects I've found that despite being fun, it can get hard to be motivated when you're getting down the framework and there isn't much output for a while. I've made it a goal to make instant-gratification a large part of my software design process.

I can't wait to play your game, I love going through games like this. Please use your developer journal to keep me updated, your game looks great! Cheers :)!

I'm a game programmer and computer science ninja !

Here's my 2D RPG-Ish Platformer Programmed in Python + Pygame, with a Custom Level Editor and Rendering System!

Here's my Custom IDE / Debugger Programmed in Pure Python and Designed from the Ground Up for Programming Education!

Want to ask about Python, Flask, wxPython, Pygame, C++, HTML5, CSS3, Javascript, jQuery, C++, Vimscript, SFML 1.6 / 2.0, or anything else? Recruiting for a game development team and need a passionate programmer? Just want to talk about programming? Email me here:

hobohm.business@gmail.com

or Personal-Message me on here !

I just want to point out: Plugging in graphics after isn't as easy as you think. You can't take something in the command line and add graphics without almost completely rewriting it. When I got started, that's what I thought, and it's not true.

This is true. However, once you've worked with graphics based programs for a while it's easy to write a graphics-ish program that uses the console as a place-holder for the graphics routines. What you're doing here (OP) is actually close to the correct way of going about this because of things like "Difficulty Game::GetDifficulty()". Once you switch to a graphics based program you can replace that function with one that gets the desired input from a GUI widget. However, I'd suggest putting the string-grab and int conversion on the outside of the function here and then passing them in as arguments. This way once you want to get the selection from a GUI widget you can just pass that as an index into the function you already have, yes?

All that being said, the two functions you showed here are using an awful lot of space and not doing much. Maybe something like this:


enum GameMode : unsigned { //enums will automatically define each successive value as the previous value + 1
  COMPUTER_PLAYER = 0,
  PLAYER_COMPUTER,
  GAMEMODE_ENTRY_COUNT //if new entries are added place them prior to this one
};

GameMode Game::GetGameMode() {
  while(1) {
    //get user input
    GameMode gameMode = (GameMode)(ToInt(StringInput()));
    
    //Check for validity
    if(gameMode < GAMEMODE_ENTRY_COUNT) {
      return gameMode;
    }
    
    //validity failed, so show message and reloop
    cout << "Please enter a valid choice using the number next to your desired game mode." << endl;
    cout << "Press ENTER to try again..." << endl;
    cin.get();
    DisplayGameMode();
  }

  return SOME_INVALID_VALUE; //to shut the compiler up
}
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
I would probably write several variants of input for common cases, such as a function that get's input as a string(as you have now), a function that gets input as a number(this function would validate integer data), and an input that take's an input as an integer, and checks that value between a min/max values.

That was going to be my next thing to do if another solution didn't really present itself.

I just want to point out: Plugging in graphics after isn't as easy as you think. You can't take something in the command line and add graphics without almost completely rewriting it. When I got started, that's what I thought, and it's not true.

While this is true, though like stated from the poster directly above me you can write a program that uses the console as a place holder. While yes somethings will differently need to be changed a lot of the original code design will more than likely be close to the same. That's more what I am going for. I'm actually not planning to replace it with graphics just wanted to work on my code design. I myself have worked with a couple different graphics API's and I have created some simple 2D games. I actually just wanted to create a game that was complete as in had all the information that games today really have. Most games these days tend to have difficulties, so I wanted to try to work on a way to change difficulties. Games also have different modes. I wanted to try to have the user change what mode they were playing in. Games today also seem to have a profiles attached to certain users. So I wanted to create a way for the user create a profile or change which profile is loaded. Finally a lot of games tend to have a way to view some stats about the game and how they are doing. So I wanted to create a way for the user to view stats on how they are doing plus just some general game stats.

The actual game? It's actually just a simple guess the number game. lol. Extremely basic. I just wanted all aspects of what seems to make a game inside of it. It is entirely over engineered for such a simple game (that I could make in less than 10 minutes). That's really what I am going for. Thanks for the words of encouragement though.

I also appreciate the words of encouragement though. Thanks a lot!

What you're doing here (OP) is actually close to the correct way of going about this because of things like "Difficulty Game::GetDifficulty()". Once you switch to a graphics based program you can replace that function with one that gets the desired input from a GUI widget. However, I'd suggest putting the string-grab and int conversion on the outside of the function here and then passing them in as arguments. This way once you want to get the selection from a GUI widget you can just pass that as an index into the function you already have, yes?

All that being said, the two functions you showed here are using an awful lot of space and not doing much. Maybe something like this:


enum GameMode : unsigned { //enums will automatically define each successive value as the previous value + 1
  COMPUTER_PLAYER = 0,
  PLAYER_COMPUTER,
  GAMEMODE_ENTRY_COUNT //if new entries are added place them prior to this one
};

GameMode Game::GetGameMode() {
  while(1) {
    //get user input
    GameMode gameMode = (GameMode)(ToInt(StringInput()));
    
    //Check for validity
    if(gameMode < GAMEMODE_ENTRY_COUNT) {
      return gameMode;
    }
    
    //validity failed, so show message and reloop
    cout << "Please enter a valid choice using the number next to your desired game mode." << endl;
    cout << "Press ENTER to try again..." << endl;
    cin.get();
    DisplayGameMode();
  }

  return SOME_INVALID_VALUE; //to shut the compiler up
}

That did make it a lot smaller and got rid of the huge switch-case. That was the next thing. something just seemed weird have that huge switch-case and that also seemed to be repeating it's self a lot.

Couple questions:

1: I can change my enum to start at 1 can't I? Just saying because that is what my menu choices start from.

2: Instead of returning some invalid value to have the compiler not complain (I assume it'd be from not all paths returning a value, unless I am wrong?) could it be better to just set the choice to a temporary gameMode variable and then return that temp? I say that only because I've never seen (though I also haven't looked at a lot of peoples code) a function just return some junk value, even though that part of the code would never get executed (unless I am missing something). Though now that I think about it, since that function should never return the junk value I guess it really wouldn't matter.

Thanks for all the input so far everyone.

That did make it a lot smaller and got rid of the huge switch-case. That was the next thing. something just seemed weird have that huge switch-case and that also seemed to be repeating it's self a lot.

Couple questions:

1: I can change my enum to start at 1 can't I? Just saying because that is what my menu choices start from.

2: Instead of returning some invalid value to have the compiler not complain (I assume it'd be from not all paths returning a value, unless I am wrong?) could it be better to just set the choice to a temporary gameMode variable and then return that temp? I say that only because I've never seen (though I also haven't looked at a lot of peoples code) a function just return some junk value, even though that part of the code would never get executed (unless I am missing something). Though now that I think about it, since that function should never return the junk value I guess it really wouldn't matter.

Thanks for all the input so far everyone.

1) Yes, you can define the value of any enum member like I did to set the zero there for COMPUTER_PLAYER. If you do that in this case though you'll need to remember to check that the value is non-zero when you test for validity (or use it to signal invalidity: see below). If you do decide to change the start value then that last value should probably be renamed, since it no longer represents the count of valid members.

2) Some people get edgy about functions that have more than one exit point. In large or complex functions this can definitely be a hazard since someone changing the function later may add something that needs to be done every call but they don't notice that there's a possible return at an earlier point. You could just as easily do this:


enum GameMode : unsigned {
  GAMEMODE_INVALID = 0,
  COMPUTER_PLAYER,
  PLAYER_COMPUTER,
  GAMEMODE_END_VALUE
};

GameMode Game::GetGameMode() {
  GameMode mode = GAMEMODE_INVALID;
  while(mode == GAMEMODE_INVALID) {
    //get user input
    GameMode gameMode = (GameMode)(ToInt(StringInput()));
    
    //handle invalid input
    if(gameMode >= GAMEMODE_ENTRY_COUNT) {
      gameMode = GAMEMODE_INVALID;
      cout << "Please enter a valid choice using the number next to your desired game mode." << endl;
      cout << "Press ENTER to try again..." << endl;
      cin.get();
      DisplayGameMode();
    }
  }

  return gameMode;
}
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Another approach if you have a lot of such menus is to create a generic menu function that you pass the parsed input to as well as a struct that contains function pointers to functions that implement whatever instructions need to be executed for that menu option.

Here's a very basic example of what I mean:
#include <iostream>
#include <vector>

typedef void (* funcptr)(void);

struct menu_object {

    int num_options;
    std::vector<funcptr> funclist;

};

void menu_1() {

    std::cout << "Menu Function One" << std::endl;

}

void menu_2() {

    std::cout << "Menu Function Two" << std::endl;

}

void menu_3() {

    std::cout << "Menu Function Three" << std::endl;

}

funcptr generic_menu(int input, menu_object menu);

int main() {

    funcptr run_menu_option;

    menu_object main_menu;
    main_menu.num_options = 3;
    main_menu.funclist.push_back(menu_1);
    main_menu.funclist.push_back(menu_2);
    main_menu.funclist.push_back(menu_3);

    run_menu_option = generic_menu(0, main_menu);
    run_menu_option();

    return 0;
}

funcptr generic_menu(int input, menu_object menu) {

    if ((input >= 0) && input < menu.num_options)
        return menu.funclist[input];
    else return NULL;

}

Thanks everyone. This information has given me some ideas. Appreciate it!

Chad.

This topic is closed to new replies.

Advertisement