This exercise is an exercise to ingrain in your mind why scanf
is generally a bad choice for taking mixed user input! You can do it, but you must be very careful to account for any characters that remain in the input buffer (i.e. stdin
) -- especially when taking character input... Why?
When you enter a value that is read by scanf
, the '\n'
will always remain in the input buffer (unless accounted for in your format string). Further, on a failed conversion -- all characters will remain in the input buffer. Further, the user can do something stupid like entering "4 is my guess"
when prompted leaving is my guess\n
for you to deal with.
Further, what if the user cancels input by pressing ctrl + d
(or ctrl + z
on windoze) generating a manual EOF
? You must account for all possibilities for each and every input.
You must also use the correct format specifier to read input. You are not going to read 'y'
or 'n'
with %d
or %i
. When you want to read an int
use %d
when you want to read a char
, use %c
. You must also take into account that %c
never skips leading whitespace.
(you beginning to understand why it's better to use fgets
and then call sscanf
for user input??)
How do you handle the characters that remain in the input buffer? Well generally you will use getchar()
to read until you have read '\n'
(generated by pressing Enter) or until EOF
is encountered. You can make it easy on yourself by writing a short function like the following:
/* empty characters that remain in stdin */
void fflushstdin ()
{
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}
If you call fflushstdin
after each input, you will always take care of any characters that remain. If you know chars remain from a prior input that have not been removed, then call it before taking input.
Don't use magic numbers in your code (e.g. 1, 5, 200
), instead define any needed constants at the beginning of your code and use the constants in your code. Why? If they change, then you have a single readily accessible place to change them and you don't have to go picking though your code to find them. You can use a #define
or an enum
like the following:
enum {LOW = 1, TRIES = 5, HIGH = 200 };
The remainder of your problems are simply logic problems that you can work out. Incorporating the above, you can handle (what I think you are attempting to do) as follows:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum {LOW = 1, TRIES = 5, HIGH = 200 };
/* empty characters that remain in stdin */
void fflushstdin ()
{
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}
int main (void) {
int i, number, guess, ret;
char answer;
printf ("Welcome to the game of Guess It!\n"
"I will choose a number between %d and %d.\n"
"You will try to guess that number.\n"
"I will tell you if you guessed too high or too low.\n"
"You have %d tries to get the number.\n\n"
"OK, I am thinking of a number. Try to guess it.\n\n",
LOW, HIGH, TRIES);
srand(time(NULL));
while (1) { /* outer loop until user quits */
number = rand() % HIGH + 1; /* set number INSIDE loop */
for (i = 0; i< TRIES; i++) { /* loop for set number of TRIES */
while (1) { /* validate user guess, handle cancelation */
printf ("Your guess no. %d? ", i + 1); /* prompt */
if ((ret = scanf (" %d", &guess)) != 1) { /* chk return */
if (ret == EOF) { /* check for cancelation */
printf ("input canceled, exiting.\n");
return 0;
}
fprintf (stderr, " error: invalid input.\n");
fflushstdin(); /* empty chars remaining in stdin */
continue;
}
if (guess < LOW || guess > HIGH) /* check limits */
printf("Illegal guess. Your guess must be between "
"%d and %d.\nTry again. Your guess?", LOW, HIGH);
break;
}
if (guess == number) { /* correct answer */
printf ("\n**** CORRECT ****\n\nWant to play again(y/n) ");
fflushstdin();
/* validate answer, you are reading a `char` NOT `int` */
while ((ret = scanf (" %c", &answer)) != 1 ||
(answer != 'y' && answer != 'n')) {
fprintf (stderr, "error: invalid answer, play again (y/n) ");
if (ret == EOF) { /* check for cancelation */
printf ("input canceled, exiting.\n");
return 0;
}
fflushstdin(); /* empty chars remaining in stdin */
}
if (answer == 'y') /* use goto for breaking nested loops */
goto done;
printf ("Goodbye, It was fun. Play again soon.\n"); /* no */
return 0;
}
if (guess > number) /* provide > and < feedback */
printf ("Too high!\n");
if (guess < number)
printf("Too low!\n");
}
printf ("Sorry, you exhausted all your tries, number was: %d\n"
"play again (y/n) ", number);
fflushstdin();
/* validate answer, you are reading a `char` NOT `int` */
while ((ret = scanf (" %c", &answer)) != 1 ||
(answer != 'y' && answer != 'n')) {
fprintf (stderr, "error: invalid answer, play again (y/n) ");
if (ret == EOF) {
printf ("input canceled, exiting.\n");
return 0;
}
fflushstdin();
}
if (answer != 'y')
break;
done:; /* goto lable to play again after correct asnwer */
}
return 0;
}
Example Use/Output
$ ./bin/guess
Welcome to the game of Guess It!
I will choose a number between 1 and 200.
You will try to guess that number.
I will tell you if you guessed too high or too low.
You have 5 tries to get the number.
OK, I am thinking of a number. Try to guess it.
Your guess no. 1? onehundred
error: invalid input.
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too high!
Your guess no. 3? 125
Too low!
Your guess no. 4? 137
Too high!
Your guess no. 5? 131
Too low!
Sorry, you exhausted all your tries, number was: 132
play again (y/n) y
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too low!
Your guess no. 3? 175
Too low!
Your guess no. 4? 187
Too high!
Your guess no. 5? 181
**** CORRECT ****
Want to play again(y/n) y
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too high!
Your guess no. 3? 125
Too high!
Your guess no. 4? 112
Too high!
Your guess no. 5? 106
Too low!
Sorry, you exhausted all your tries, number was: 110
play again (y/n) n
Note, the above handles stupid user input (like onehundred
) and adds number
to the failure output to let the user know what he missed.
Look things over and let me know if you have further questions.