How do I allow the user more attempts as opposed to just restarting the game after catching a format exception?

Viewed 49

So, I'm building this little guessing game and I recently added a try/catch to stop the console from crashing if the user inputted a wrong character (besides numbers of course). Right now it works well but it restarts the entire game, I've tried to just have the catch re-run the script starting at the "for" loop, but it breaks and the random number originally generated doesn't match.

 public static void StartGame()
    {

        Console.Clear();
        Random ran = new Random();
        decimal returnValue = (ran.Next(1, 100));

        Console.WriteLine("Welcome to my guessing game. I'm thinking of a number between 1 and 100, can you guess it?");
        Console.WriteLine("Hint: 'Way too off' means you're greater than 25 from the answer!");
        try
        {


        for (int guessnumber = 1; guessnumber < 6; guessnumber++)
        {
            var Guess = Convert.ToInt32(Console.ReadLine());
            //The Guess variable is simply whatever the user inputs, which is read and used by the for loop each time it is inputed.
            if (Guess == returnValue)
            {
                Console.WriteLine("Well done!");
                Console.ReadKey();
                Game.NewGame();
            }
            else if (Guess < returnValue)
            {

                Console.WriteLine("Guess higher!");



            }
            else if (Guess > returnValue)
            {

                Console.WriteLine("Guess lower!");

            }

            if (guessnumber == 5)
            {
                Console.WriteLine($"The correct answer was {returnValue}");
                Console.ReadKey();
                Game.NewGame();
                //This if statement activates when the "guessnumber" counter has reached 5, in which case it will tell the user the correct answer, and end with a return statement.
            }
            //result is the absolute value of 
            decimal result = Math.Abs(returnValue - Guess);

            if (result > 25)
                Console.WriteLine("Way too off!");

            if (result < 5)
            {
                Console.WriteLine("Very close!!");
            }
            if (result < 2)
            {
                Console.WriteLine("RED HOT!!!");
            }
        }
        }
        catch (System.FormatException)
        {
            Console.WriteLine("Incorrect character inputted, restarting game. . .");
            System.Threading.Thread.Sleep(2000);
            StartGame();

        }

    }
    public static void NewGame()
    {
        Console.Clear();
        string input = " ";
        Console.WriteLine("Game over, would you like to play again? Y/N");
        input = Console.ReadLine();
        input = input.ToUpper();
        if (input == "Y")
        {
            Console.WriteLine("Ok! Re-Initializing game...");

            StartGame();
        }
        else
        {
            System.Environment.Exit(0);
        }

    }

}

}

3 Answers

As another answer stated, you can just move your try/catch inside the for loop, so the loop continues.

But you really shouldn't be relying on exception handing for something that isn't exceptional, like parsing user input.

Instead, we can use the int.TryParse method to try to parse a string to an int. The nice thing about this method is that it returns true if the parse was successful, and it sets an out parameter to the parsed value.

We can also put it in it's own method that takes in a string to be used to prompt the user, which makes the code more reusable:

public static int GetIntFromUser(string prompt)
{
    int result;
    bool askedOnce = false;

    do
    {
        if (askedOnce) Console.WriteLine("Invalid input, please try again.");
        Console.Write(prompt);
        askedOnce = true;
    } while (!int.TryParse(Console.ReadLine(), out result));

    return result;
}

Now our main code is greatly simplified (also notice that the Random instance should be a class field, not created on every call to StartGame, and notice that the upper bound specified in the call to ran.Next is exclusive, so if you want to include 100 as a possible answer, make the max value 101):

private static Random ran = new Random();

public static void StartGame()
{
    Console.Clear();
    int randomNumber = ran.Next(1, 101);

    Console.WriteLine("Welcome to my guessing game");
    Console.WriteLine("I'm thinking of a number between 1 and 100, can you guess it?");

    for (int guessCount = 1; guessCount < 6; guessCount++)
    {
        var guess = GetIntFromUser($"Guess a number (try #{guessCount}): ");                

        if (guess == randomNumber)
        {
            Console.WriteLine("Well done!");
            Console.ReadKey();
            Game.NewGame();
        }
        else if (guessCount == 5)
        {
            Console.WriteLine("That was your last guess.");
            Console.WriteLine($"The correct answer was: {randomNumber}.");
            Console.ReadKey();
            Game.NewGame();
        }
        else
        {
            decimal result = Math.Abs(randomNumber - guess);

            if (result > 25) Console.Write("You're way off! ");
            else if (result < 5) Console.Write("Very close!! ");
            else if (result < 2) Console.WriteLine("You're RED HOT!!! ");

            Console.WriteLine(guess < randomNumber ? "Guess higher!" : "Guess lower!");
        }
    }
}

Move the Try Catch handling inside for loop. What this does is, that exceptions are handled during each guess iteration.

What you had originally was exception handling for all iterations.

Also, Dont call the StartGame Function again in your catch block. You want to allow 6 (per the for loop) Guesses and not Infinite.

Ex:

for (int guessnumber = 1; guessnumber < 6; guessnumber++)
{
    try
    {
    }
    catch (System.FormatException)
    {
    }
}

The simple answer is as follows:

  1. You don't control application flow with exception handling. Catching exceptions are for exceptional circumstances, a user getting input wrong isn't exceptional... it's guaranteed.
  2. Never ever ever ever ever (+1000) use Convert.... or int.Parse to take user input... Users make mistakes all the time, use the TryParse type methods instead, they return a bool to say if the data was successfully parsed so you can then deal with it.

Eg

var success = int.TryParse(Console.ReadLine(), out var guess);

However, you can take it one step further, and add a loop (add pepper and salt to taste)

int guess = 0;

while(!int.TryParse(Console.ReadLine(), out guess))
   Console.WriteLine("Omg you had one job, just enter a number");

Additional Resources

Int32.TryParse Method

Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the operation succeeded.

Related