1

I'm creating a very simple number guessing game for a school project and am having trouble with the repeating main menu. I created it using a do-while loop and the problem I'm having is that the menu selection variable is an int, and so when I (or the user) enters a non-int input by accident when selecting from the menu the }while(condition) at the end of the main loop can't catch it and the program repeats infinitely. Conversely if you enter an invalid int at menu selection the program catches it displays the "invalid input" message and then repeats the main menu.

It's kind of hard to explain in writing exactly what I mean so here is the source code with relevant lines denoted with an asterisk. I'm saving as .cpp and am compiling in linux using g++ -ansi -pedantic -Wall -Werror The teacher has forbidden hardcoding in conditional statements hence the global constants.

#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
const int PLAY = 1, HIGH_SCORE = 2, EXIT = 3;
const char YES = 'y', NO = 'n';

int main()
{
// Randomly generated value
  int randomNumber;
// User input
  int userGuess, menuChoice;
  char repeat;
// Calculated value
  int numberOfGuesses;
// Place-holder values (to be replaced by calculated values)
  int score1 = 1000, score2 = 2000, score3 = 3000;

  cout << endl << endl;
  cout << "Greetings! This is a number guessing game where I think of" << endl
       << "a whole number between one and ten and you try to guess it!" << endl
       << "You can guess as many times as you like, so don't be afraid" << endl
       << "to use trial and error, but your score is based on the " << endl
       << "number of guesses you make (the lower the better) so don't " << endl
       << "guess too haphazardly. Remember, only guess whole numbers!" << endl
       << endl;

  do
  {
    cout << endl << "Main menu." << endl
         << "1. Play game" << endl
         << "2. Display high scores" << endl
         << "3. Exit game" << endl
         << "Please select an option: ";
    cin >> menuChoice;

    if (cin.fail()){     
      cout << "Please enter a valid choice" << endl;
      continue;
    } 
    cin.ignore();

    switch(menuChoice)
    {
      case PLAY:
      do
      {
        unsigned seed = time(0);
        srand(seed);
        randomNumber = 1 + rand() % 10;

        cout << endl << "Press enter when you're ready to begin!";
        cin.ignore();
        cout << "Ok I thought of one!" << endl << endl;

        numberOfGuesses = 0;

        do
        {
          numberOfGuesses++;

          cout << "Enter your guess: ";
          cin >> userGuess;
          cin.ignore();

// Check user's guess
          if (userGuess == randomNumber)
            cout << "Correct! That was impressive!" << endl << endl;
          else if (userGuess < randomNumber)
            cout << "Not quite, you guessed low." << endl << endl;
          else if (userGuess > randomNumber)
            cout << "Not quite, you guessed high." << endl << endl;
        }while (userGuess != randomNumber);

        cout << "Your score for this game was " << numberOfGuesses << endl;

// Determine if a high score was beaten
        if (numberOfGuesses <= score1)
        {
          score3 = score2;
          score2 = score1;
          score1 = numberOfGuesses;
          cout << "That's a new all time high score!" << endl;
        }
        else if (numberOfGuesses <= score2)
        {
          score3 = score2;
          score2 = numberOfGuesses;
          cout << "That's a new high score!" << endl;
        }
        else if (numberOfGuesses <= score3)
        {
          score3 = numberOfGuesses;
          cout << "That's a new high score!" << endl;
        }
        else
        {
          cout << endl; 
        }

        cout << "Would you like to play again? y/n: ";
        cin.get(repeat);
        cin.ignore();

        while (tolower(repeat) != YES && tolower(repeat) != NO)
        {
          cout << endl;
          cout << "Sorry, that is an invalid choice." << endl
               << "Please enter 'y' for yes or 'n' for no: ";
          cin.get(repeat);
          cin.ignore();
        }
      }while (tolower(repeat) == YES); 
        break;

      case HIGH_SCORE:
      cout << endl << "High Score 1: " << score1 << endl
           << "High Score 2: " << score2 << endl
           << "High Score 3: " << score3 << endl << endl;
      cout << "Press enter to continue. ";
      cin.ignore();
        break;

      case EXIT: 
      cout << endl << "Thanks for playing, I'll see you next time!" << endl << endl;
        break;

      default:
      cout << endl << "That is an invalid selection, please enter '1', '2' or '3'"
           << endl;
        break;
    } 
  }while (menuChoice != EXIT);

  return 0;
}

Code Edited in regards to current answer.

Please let me know if you need anymore information, thanks in advanced!

Willie Gross
  • 31
  • 1
  • 6
  • Just a suggestion; put things in different functions and fix your indentation. It will make the code a lot easier to read. – olevegard Jan 26 '14 at 20:34
  • How would you suggest I alter my indentation? My professor recommended 2 spaces for each level. I'm not terribly experienced and so my knowledge is pretty limited, what functions would you recommend? – Willie Gross Jan 26 '14 at 20:37
  • @WillieGross I'd recommend to try out `do {` instead of `do {`, it reduces your lines and makes it easier to read. However I don't think your indentation is so bad that it really needs fixing. Putting things into functions is probably the first thing you should improve ;-) I personally prefer 4-space indentation (Pythonista), but that is highly subjective. – Uli Köhler Jan 26 '14 at 20:39
  • @WillieGross Is your problem that `cin >> menuChoice` causes an exception if I enter e.g. "foobar" instead of 1, 2 or 3? – Uli Köhler Jan 26 '14 at 20:40
  • @UliKöhler This is mainly a matter of taste though. With the `{` on a newline I find it easier to see where the corresponding `}` is. As for indentation, I suggest putting indentation under your `case`s. But generally reducing the lines of code in the function will do wonders. – olevegard Jan 26 '14 at 20:42
  • Thank you for the input! And yes, the issues is if you enter any non-number at cin >> menuchoice the program essentially crashes, running through the menu and error message forever without stopping. If you enter any other number like 5 or 7, if properly displays the default switch at the end and then repeats the menu. – Willie Gross Jan 26 '14 at 20:44
  • @olevegard I absolutely agree. Until a few years ago I also prefered the newline-before-braces style. The problem is (IMO) for some larger projects it produces so many non-expressive lines that looking through the code is IMO quite difficult. – Uli Köhler Jan 26 '14 at 20:45
  • @WillieGross What about using try/catch to catch the exception an continue in the loop interation in case of an exception? – Uli Köhler Jan 26 '14 at 20:45
  • @UliKöhler I'll try that. As I said my knowledge is pretty limited and we're discouraged from using methods we haven't gone over in lecture or in out reading on our final submissions, but I'll see if that works. So far the only tools "officially" available to me are loops (do, do-while and for) and most of the rest of the code you see in my OP. – Willie Gross Jan 26 '14 at 20:49
  • For the record, I dont think this is a duplicate of https://stackoverflow.com/questions/2075898/good-input-validation-loop-using-cin-c because I think to the OP it was not clear what the problem is and -- see last comment -- it is not clear if it is allowed to use try/catch for solutions. – Uli Köhler Jan 26 '14 at 20:50
  • @WillieGross There was a " missing in my code below. Sorry for that, fixed that. – Uli Köhler Jan 26 '14 at 20:51
  • @WillieGross Just as an additional comment, you can use "\n" instead of `endl`, so you can use `"1. Play game\n"` instead of `"1. Play game" << endl` – Uli Köhler Jan 26 '14 at 21:30

2 Answers2

0

Use cin.fail() like this (instead of just cin >> menuChoice;) (modelled after this post):

cin >> menuChoice;
if (cin.fail()) {
  cout << "Please enter a valid choice" << endl;
  cin.clear();
  cin.ignore();
  continue;
}
//Remove the cin.ignore() at this place!

For more detailed info, see this SO thread

Community
  • 1
  • 1
Uli Köhler
  • 13,012
  • 16
  • 70
  • 120
  • What should I put in the "catch(parameter?)", it won't let me compile with that blank or with no parenthesis after "catch" Thanks! – Willie Gross Jan 26 '14 at 21:11
  • @WillieGross I just updated my answer based on the thread I linked while you wrote the comment. The new solution works without methods you haven't learnt yet. – Uli Köhler Jan 26 '14 at 21:15
  • @WillieGross For the record, you would use `catch (...)` – Uli Köhler Jan 26 '14 at 21:15
  • There must be something else wrong with my code. I implemented the cin.fail() exactly as you suggested and I still get an infinitely looping menu if I enter anything other than an integer. I'll update my OP to show my code as it is now with the fix implemented, – Willie Gross Jan 26 '14 at 21:23
  • @WillieGross So, if you repeatedly type "foobar", you get asked for a choice over and over again? Isn't that what you want? Do you want the program to EXIT if you type "foobar"? Then just replace `continue` with `break` in my code. – Uli Köhler Jan 26 '14 at 21:27
  • Apologies, I was unclear. The menu loops infinitely without giving me a chance to enter anything else after entering "foobar" only once, it just keep reloading it and reloading it without pausing at all. – Willie Gross Jan 26 '14 at 21:29
  • @WillieGross No need to apologize ;-) Is it only the part between `do{` and `cin >> menuChoice` that loops? – Uli Köhler Jan 26 '14 at 21:31
  • @WillieGross I can reproduce your issue. It will need a few minutes to fix it. – Uli Köhler Jan 26 '14 at 21:32
  • Here is what I see exactly when I enter any non integer at cin>> menuChoice, I have to hit ctrl-c to stop the program. http://i793.photobucket.com/albums/yy218/tombombodil/Unendingloop_zpsaee13f1f.png – Willie Gross Jan 26 '14 at 21:35
  • @WillieGross Fixed it. See my updated answer. please remember removing the `cin.ignore()` after the `if()` block. Sorry that it didnt work right away. – Uli Köhler Jan 26 '14 at 21:36
  • Wonderful! Problem fixed! I'll be sure to remember this for future work. Thank you so much for you help :) – Willie Gross Jan 26 '14 at 21:38
  • @WillieGross No problem, happy to help ;-) Please feel free to ask those things on StackOverflow! I'd like to mention that for a new user I found your question to be exceptionally helpful (most users post something like "Hi my program does not stop when I enter a letter, help!! I dunno what is the problem"). – Uli Köhler Jan 26 '14 at 21:41
0

Use a do-while to ensure that the loop body will run at least once.

By using a do-while and prompting a user outside the loop you assume the user wants to play the game once which may not be the case.

A cleaner approach IMO would be use a while loop. Display the menu outside the loop and at the end of the loop. The user will have the choice to exit immediately.

 cout << "Greetings.....
 cout << menu
 // Get menuChoice input here.    
    while(menuChoice != EXIT){
        ...

         cout << menu //reprompt at end to continue or exit cleanly
         // Get menuChoice input here
    }

Input Validation is a perfect time to use a do-while

do{
   if(!cin){
      cout << "Invalid input"
      cin.clear()
      cin.ignore(numeric_limits<streamsize>::max(), '\n');
   }
}while(!(cin >> menuChoice)) // This gets console input. If fail, loop.
  • Use numeric_limits<streamsize>::max() to completely clear the buffer.
  • Use cin.clear() to reset the fail flag on cin so it wont always be false.

cin.fail() is fine. However some would consider !cin more natural.

Shan L
  • 81
  • 6