Continuing from my comment, any time you need to keep track of whether (or how many times) a value within a range has been seen, you want a Frequency Array. Such as how many times is each letter used in this document, or has the user already guessed X
?
A frequency array is simply an array with one element for each value in your range. The array is initialized all zero. When you read a value, you increment the index that corresponds to that value, e.g.
array[value]++;
So the array
simply keeps track of how many times a given value
is seen. In your case to prevent the user from entering the same number more than once and having it count as a valid try -- all you care about is whether array[value] == 0
. If it does, then value
has not been entered before. if (array[value] != 0)
(or simply if (array[value])
), you know the user has already provided that value, so it should not count as a valid try.
Think about it for a minute. It takes a bit to wrap your head around what is happening. You start with an array that has an elements that corresponds to each value in your range from 1-10
and each of the elements is initialized 0
. So if the user enters 5
, you will increment (add +1
) the array index that corresponds to 5
, e.g. array[5]++;
. So array[5]
now equals 1
. If you check the index for the value the user enters each time against the value at that index in the array -- if it isn't 0
, you know the user has already entered that value.
Frequency arrays work for many, many, many types of counting and uniqueness problems, so make sure you understand them before moving on. I have another write-up about them at How to remove duplicate char in string in C which may help if you are still unclear how they track the guesses entered by your user.
How to implement it in your code? Just add a int guessed[11] = {0};
array for your frequency array (the 11
instead of 10
just avoids having to map the indexes from a ones-based 1-10
to a zero-based 0-9
since all array indexes are zero-based in C. (plus the cost of 1 extra int
element in the array to save having to guess - 1
each time makes sense).
So with an 11
element frequency array to capture your guesses from 1-10
that allows you to track your user's guess
by incrementing guessed[guess]++;
with each valid guess and before considering the guess valid, allows you to check if (guessed[guess] != 0)
to know that the user has already used that guess
.
Putting it altogether, (with a short aside first) you could do:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NGUESSES 10 /* if you need a constant, #define one (or more) */
#define MAXC 256 /* max number of characters for each entry */
(note: don't use Magic-Numbers in your code, if you need a constant... You now have a single convenient location at the top of your code where you can change the game to use 15
, 20
, ... whatever number of guesses, without having to pick through all array declarations, if(...)
statements, loop limits, etc... to make the change)
Declare Your Frequency Array
int main (void) {
srand(time(NULL));
char buf[MAXC]; /* buffer (array) for all user-input */
int r = rand() % NGUESSES + 1,
guess,
guessed[NGUESSES + 1] = {0}, /* frequency array + 1 for range 1 - 10 */
tries = 0;
Your lastguess
is no longer needed. The hours of trying to make that work for check if the user had already entered a guess has been replaced by your frequency array guessed[]
.
Read All User Input With a Line-Oriented Input Function
/* don't use Magic-Numbers in your code, use a constant */
printf ("\nWelcome user, please guess my number from 1 to %d\n", NGUESSES);
while (1) {
printf ("\nguess[%2d] : ", tries + 1); /* prompt for entry each time */
if (fgets (buf, MAXC, stdin) == NULL) { /* read every input into buffer/validate */
puts ("(user canceled input)"); /* ctrl+d, manual EOF is valid input */
return 0; /* allow user to cancel */
}
/* use sscanf to parse guess from buf - validate EVERY conversion */
if (sscanf (buf, "%d", &guess) != 1) { /* " " not needed, %d ignores whitespace */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
Always take user-input with a line-oriented input function so what remains in your input buffer stdin
does NOT depend on the scanf()
conversion specifier used or whether a matching failure occurs. Try entering "bananas"
as your input -- and you will quickly see why. Instead read with fgets()
into an array and then use sscanf()
to parse the values you need from the array (works just like scanf()
but reads from your array instead of stdin
). Why?
That way no matter what the user inputs, your while()
loop doesn't spin off into an infinite loop if the user enters a non-integer value because --- you have read the entire line with fgets()
so nothing will remain unread in stdin
and regardless of whether the conversion succeeds or fails, stdin
is ready for the next input attempt and not full of characters that scanf()
left behind.
(with scanf()
when a matching-failure occurs, character extraction from stdin
ceases at that point so the bad input is left unread in stdin
)
That's why you use fgets()
(or POSIX getline()
) to read every user-input.
Validate The Input Is In Range
if (guess < 1 || NGUESSES < guess) { /* validate guess in range */
fprintf (stderr, " error: %d not between 1 and %d\n", guess, NGUESSES);
continue;
}
Check Your Frequency Array To See If guess
Already Entered
if (guessed[guess]) { /* check if freq array non-zero at guess index */
fprintf (stderr, " error: %d was a previous guess.\n", guess);
continue;
}
guessed[guess]++; /* increment index for guess indicating guess used */
tries++;
Only now do you know you have a valid input that will constitute a try.
Check Victory or Prompt "Higher!" or "Lower!"
if (guess == r) { /* check victory */
printf ("Congratulations, you got it in %d tries\n", tries);
return 0;
}
else if (r < guess) /* prompt for high guess */
puts (" Guess Lower!");
else /* prompt for low guess */
puts (" Guess Higher!");
}
}
That's it -- done. If you will notice the indentation - that is the complete program.
Example Use/Output
With intentional bad non-integer input and duplicate guesses and guesses out-of-range, you can exercise the program with:
$ ./bin/guess_1-10
Welcome user, please guess my number from 1 to 10
guess[ 1] : My dog has fleas and my cat has none :)
error: invalid integer input.
guess[ 1] : 6
Guess Higher!
guess[ 2] : 0
error: 0 not between 1 and 10
guess[ 2] : 11
error: 11 not between 1 and 10
guess[ 2] : 6
error: 6 was a previous guess.
guess[ 2] : 1
Guess Higher!
guess[ 3] : 3
Guess Higher!
guess[ 4] : 5
Guess Higher!
guess[ 5] : 7
Guess Higher!
guess[ 6] : 10
Guess Lower!
guess[ 7] : 8
Guess Higher!
guess[ 8] : 9 ... drum roll ... and
Congratulations, you got it in 8 tries
What if the user cancels input by generating a manual EOF
with Ctrl + d (or Ctrl + z on windows)? A manual EOF
is valid input for your program, so you should always respect a user's wish to cancel input by using it, e.g.
$ ./bin/guess_1-10
Welcome user, please guess my number from 1 to 10
guess[ 1] : 3
Guess Lower!
guess[ 2] : (user canceled input)
By using fgets()
, you simply check the return, as you do with EVERY input function you use, and if it is NULL
, you know the user generated a manual EOF
, so simply exit at that point.
Full Code
The full code to make it easy to copy/paste/compile/test:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NGUESSES 10 /* if you need a constant, #define one (or more) */
#define MAXC 256 /* max number of characters for each entry */
int main (void) {
srand(time(NULL));
char buf[MAXC]; /* buffer (array) for all user-input */
int r = rand() % NGUESSES + 1,
guess,
guessed[NGUESSES + 1] = {0}, /* frequency array + 1 for range 1 - 10 */
tries = 0;
/* don't use Magic-Numbers in your code, use a constant */
printf ("\nWelcome user, please guess my number from 1 to %d\n", NGUESSES);
while (1) {
printf ("\nguess[%2d] : ", tries + 1); /* prompt for entry each time */
if (fgets (buf, MAXC, stdin) == NULL) { /* read every input into buffer/validate */
puts ("(user canceled input)"); /* ctrl+d, manual EOF is valid input */
return 0; /* allow user to cancel */
}
/* use sscanf to parse guess from buf - validate EVERY conversion */
if (sscanf (buf, "%d", &guess) != 1) { /* " " not needed, %d ignores whitespace */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
if (guess < 1 || NGUESSES < guess) { /* validate guess in range */
fprintf (stderr, " error: %d not between 1 and %d\n", guess, NGUESSES);
continue;
}
if (guessed[guess]) { /* check if freq array non-zero at guess index */
fprintf (stderr, " error: %d was a previous guess.\n", guess);
continue;
}
guessed[guess]++; /* increment index for guess indicating guess used */
tries++;
if (guess == r) { /* check victory */
printf ("Congratulations, you got it in %d tries\n", tries);
return 0;
}
else if (r < guess) /* prompt for high guess */
puts (" Guess Lower!");
else /* prompt for low guess */
puts (" Guess Higher!");
}
}
Give it a go, let me know if it does what you need, and let me know if you have further questions. Make sure you understand theses concepts before moving on -- and I'm here to help if you are still stuck.