Random guessing game (integer) - bug when entering string - c

I am making a typical guessing game to learn C for the first time in my life and I noticed a bug. If you enter an integer you will get Guess a higher value or Guess a lower value and that works just fine. But if you put in a string, it goes insane and prints out a lot of strings saying Guess a higher value.
What I am trying to do now is to make a check for if the user enters a string, it will say to the user something like Enter a number, not text. How do I do this?
Is there anything you see that I can improve in this code?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main () {
int secret, answer;
srand((unsigned)time(NULL));
secret = rand() % 10 + 1;
do {
printf ("Guess a number between 1 and 10");
scanf ("%d",&answer);
if (secret<answer) puts ("Guess a higher value");
else if (secret>answer) puts ("Guess a lower value");
} while (secret!=answer);
puts ("Congratz!");
return 0;
}

You are ignoring the return value of scanf, which will tell you if the conversion went well or not. scanf will return the number of items assigned, so if it returns 1 you know that answer was assigned to a number. If it returns 0, you know that something went wrong.

What I am trying to do now is to make a check for if the user enters a
string, it will say to the user something like Enter a number, not
text. How do I do this?
scanf does formatted input. You may also want to check out advanced formatting specifiers in scanf -- such as ignoring parts of the input, specifying buffer size for reading in strings, specifying only a particular set of characters that you want to read etc. If you want to verify input, you are probably best of reading a line from the console using fgets and then parsing the line.
Is there anything you see that I can improve in this code?
You may want to improve your random number generation algorithm. Read the C FAQ.

Check the return value of scanf . It will return the number of items successfully matched and assigned. In this case if you enter a string then it will not be matched and assigned, but the string stays in the input buffer, and the scanf tries to read it in each iteration and fails, and thus the problem arises.
You may change the code to:
int main () {
int secret, answer;
srand((unsigned)time(NULL));
secret = rand() % 10 + 1;
do {
printf ("Guess a number between 1 and 10");
if (scanf ("%d",&answer) != 1)
{
printf ("\nPlease enter an integer\n\n");
scanf ("%*s");
continue;
}
if (secret<answer) puts ("Guess a higher value");
else if (secret>answer) puts ("Guess a lower value");
} while (secret!=answer);
puts ("Congratz!");
return 0;
}
Note that inside the if the line scanf ("%*s"); is done. In the %*s the * is the input supression indicator, this tells that a string (stands for %s) will be read but the * tells that although the string will be read from the input, it will be discarded. This is being done to simply discard the previously entered string which is not read by the scanf ("%d",&answer) . In case you do not discard the string in the buffer, it will remain, and each iteration the scanf ("%d",&answer) will fail to match any integer, as it will encounter the leftover string in the input buffer.
On the other hand you may read a string and convert the string to an integer.
Like as below:
int main () {
int secret, answer;
char buff[128];
srand((unsigned)time(NULL));
secret = rand() % 10 + 1;
do {
printf ("Guess a number between 1 and 10");
scanf ("%s",buff);
if ((answer = atoi (buff)) == 0)
{
printf ("\nPlease enter an integer\n\n");
continue;
}
if (secret<answer) puts ("Guess a higher value");
else if (secret>answer) puts ("Guess a lower value");
} while (secret!=answer);
puts ("Congratz!");
return 0;
}
atoi () will convert a string to an integer (in 10 base). If the characters which comprises the integer does not contain valid digits, it will return 0. Checking for it we can detect if the user entered correctly. Also because your application needs to enter an integer within 1 and 10 therefore 0 is not included, therefore it is okay to take 0 as an invalid. To get better control on detecting an invalid integer format, and the location of the error in the string use strtol ()

You may want to make a function to check if the input is numeric. Like:
int isNumeric( char *str){
while(*str)
{
if (!isdigit(*str))
return 0;
str++;
}
return 1;
}
int main(){
char guess[3];
int iGuess;
do {
printf("Guess a number between 1 and 10");
gets(guess);
if (!isNumeric(guess)){
printf("Invalid input");
continue;
}
iGuess = atoi(guess);
if (iGuess<secret)
printf("Higher");
else if (iGuess>secret)
printf("Lower");
} while (secret!=iGuess);
printf("Congrats");
return 0;
}
You need to include ctype.h and string.h

Related

Isdigit() not working for values in range of 48- 58 in C

I am trying to create program which checks if input from user is a number. This works fine for all of the number and charatcers entered, except for numbers in range of 48 to 57. I've been looking through StackOverflow forum and could not find the answer. Could you please advise, what I am doing wrong?
Here is my code:
#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
int main(){
int tab[100];
int input;
int index = 0,count=0;
printf("Podaj liczby:\n");
do{
scanf("%i", &input);
if(!isdigit(input)){
if(input!=0){
tab[index] = (int)input;
index++;
}
}else{
printf("Incorrect input");
return 1;
}
}while(input!=0 && index<100);
if(index<2){
printf("not enough data available");
return 2;
}
for(int i = 0; i <index; i++){
count = 0;
for(int j = i+1;j< index; j++){
if((tab[i] == tab[j]) && tab[i]!=0) {
count++;
tab[j] = 0;
}
}
if(count>0){
printf("%i ",tab[i]);
}
}
return 0;
}
scanf("%i", &input);
scanf with the format %i converts its input to an integer. If the user presses 4 2 Enter, that makes the scanf call equivalent to input = 42.
If the input can't be converted to an integer, scanf returns EOF and doesn't set input. Since you don't check the return value of scanf, your program continues with an indeterminate value in input. (In theory that's undefined behavior, but in practice your program will run with whatever happened to be in the memory location of input.)
if(!isdigit(input)){
This tests whether the number input is the numerical code of a digit character. In practice, the correspondence between characters and their numerical codes on your computer is ASCII, so digits occupy the range from 48 to 57 inclusive. So the body of this if clause only runs if input is not between 48 and 57.
I have no idea why you'd do that. If you meant to check whether the input is valid, check the return value of scanf. If you thought you were checking the first character of the input, then 1. no you aren't, you're checking the result of the conversion, you can't access the raw input from the user; and 2. you'd only be checking one character anyway so your check couldn't possibly be correct.
The if(!isdigit(input)) clause is checking whether input is a digit, and it jumps to the else part if it is. So, if this was ever going to work, you would have to get rid of the '!' operator.
Which leaves us with the next problem: you are using scanf() to read ints. There is no more checking to be done to see whether an int is a number. An int is already a number.
But what you do, is that you treat the int as a char and pass it to isdigit(), which will of course only succeed if the char is between 0 and 9, which corresponds to int numbers between 48 and 57. So, only these numbers succeed, and then you negate the result of isdigit(), so you think that only these numbers fail.
Long story short: quit checking whether your numbers are numbers, they are already numbers.

Having Difficulty with isdigit() in C

I have been trying to add some experience in C to my experience with Python and started with a basic addition program. One thing that I'm trying to do is check if the input is a number or a character as seen here:
#include <stdio.h>
#include <ctype.h>
int main()
{
int n, sum=0,c,value;
printf("Enter the Number of Integers You Want to Add\n");
scanf("%d", &n);
if(isdigit(n))
{
printf("Enter %d Integers\n", n);
for(c=1; c<=n; c++)
{
scanf("%d", &value);
if(isalpha(value))
{
printf("ENTER INTEGER NOT CHARACTER\n");
break;
}
else
{
sum = sum + value;
}
}
printf("Sum of Entered Integers = %d\n",sum);
}
else
{
printf("ENTER INTEGER NOT CHARACTER\n");
break;
}
return 0;
}
Initially I had tried this using isalpha(), and the program worked fine when adding numbers but interpreted characters as zeros instead of printing the "not an integer" statement. However, now that I reworked it to use isdigit(), it does not recognize ANY input as an integer, whether or not it is. Is there something that I'm just doing wrong?
When you use scanf to read an integer, you get just that, an integer. (To read a single character, you need %c and a pointer-to-char).
When you use isdigit(), you need to supply the representation of that character (e.g. in ASCII, the character '0' has the representation 48, which is indeed its value as an integer). To recap:
isdigit(0) is false
isdigit(48) is true (for ASCII, ISO8859, UTF-8)
isdigit('0') is true (no matter the character set)
isdigit('0' + n) is true for integers n = 0 ... 9
PS: Not testing the return value from scanf is asking for trouble...
Neither isdigit nor isalpha work as you think they do. The intent of those library functions is to check whether a given code point, represented as an int, is within a subset of points defined by the standard to be digit characters or alpha characters.
You should be checking the results of your scanf calls rather than assuming they just work, and acting on those results accordingly. If you request an integer and one is successfully scanned, then it will tell you so. If that fails, your course of action is probably to consume the rest of the line (through newline or EOF) and possibly try again:
#include <stdio.h>
int main()
{
int n,value,sum=0;
printf("Enter the Number of Integers You Want to Add\n");
if (scanf("%d", &n) == 1 && n > 0)
{
printf("Enter %d Integers\n", n);
while (n--)
{
if (scanf("%d", &value) == 1)
{
sum = sum + value;
}
else
{
// consume the rest of the line. if not EOF, we
// loop around and try again, otherwise break.
while ((value = fgetc(stdin)) != EOF && value != '\n');
if (value == EOF)
break;
++n;
}
}
printf("Sum of Entered Integers = %d\n", sum);
}
return 0;
}
Properly done you should be able to enter valid integers beyond single digits (i.e. values > 10 or < 0), which the above allows.
The %d marker to scanf tells it to interpret the input as a number (more accurately, it indicates that the pointer in the arguments points to an integer type). It can't do anything but put an integer into that argument. If it can't interpret the input as a number, scanf stops scanning the input and returns immediately.
isdigit() evaluates its argument as a character code, as Jens points out above. However, scanf already turned the character code into a pure number.
From the scanf man page:
On success, the function returns the number of items of the argument list
successfully filled.
In your program, you are trying to read just one item from stdin, so scanf should return 1. So check for that and you'll know that it all worked out ok:
printf("Enter the Number of Integers You Want to Add\n");
while(scanf("%d", &n) != 1) {
printf("That's not a valid integer. Please try again.\n");
}
You cannot use isdigit() the way you are using it because you're already using scanf to convert the user input to an integer. If the user had not input an integer, scanf would have already failed.
Look at the man pages for all the C functions you are using, they will show you what the function expects and what the return values will be under different circumstances.
In the case of isdigit(), the input is expected to be an unsigned char representing an ASCII character. This can be a bit confusing because ASCII characters are in fact represented as a type of integer, and a string is an array of those. Unlike languages like Python which hide all that from you. But there is a big difference between the STRING of a number (array of characters that contain the characters of the digits of the number) and the INTEGER itself which is in a form the processor actually uses to do math with... (simplified explanation, but you get the idea).

scanf test failing inside a function in C

I'm trying to do a program with a simple game for a user to guess the number. My code is below:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX 30
#define TRYING 5
void guessnumber(int, int, int *);
int main(void) {
int mytry = 1;
guessnumber(MAX, TRYING, &mytry);
if (mytry <= TRYING)
printf("Congratulations! You got it right in %d tries\n", mytry);
else
printf("Unfortunately you could not guess the number in the number of tries predefined\n");
printf("End\n");
return EXIT_SUCCESS;
}
void guessnumber(int _n, int _m, int *_mytry) {
srandom(time(NULL));
int generated = 0, mynum = 0, test = 0;
generated = rand() % (_n + 1);
printf("Welcome to \"Guess the number\" \n");
printf("A number between 0 and %d was generated\n", _n);
printf("Guess the number:\n");
while (*_mytry <= TRYING) {
test = scanf(" %d", &mynum);
if (test != 1 || mynum < 0 || mynum > MAX)
printf("ERROR: please enter a valid number \n");
else
if (mynum > generated)
printf("Wrong! The number your trying to guess is smaller\n");
else
if (mynum < generated)
printf("Wrong ! The number your trying to guess is bigger\n");
else
break;
*_mytry = *_mytry + 1;
}
}
Okay, now the program is working pretty ok except for one thing: the scanf test.
It works if I try to enter a number out of my range (negative or above my upper limit) but it fails if I for example try to enter a letter. What it does is that it prints the message of error _m times and then it prints "Unfortunately you could not guess the number in the number of tries predefined" and "End".
What am I doing wrong and how can I fix this?
In case, a character is entered, you're trying to detect it correctly
if(test!=1 ......
but you took no action to correct it.
To elaborate, once a character is inputted, it causes a matching failure. So the input is not consumed and the loop falls back to the genesis position, only the loop counter is increased. Now, the previous input being unconsumed, is fed again to the scanf() causing failure once again.
This way, the loop continues, until the loop condition is false. Also, for every hit to scanf(), as unconsumed data is already present in the input buffer, no new prompt is given.
Solution: You need to clean the input buffer of existing contents when you face a failure. You can do something like
while ((c = getchar()) != '\n' && c != EOF);
to clean the buffer off existing contents.
When you enter a letter, scanf() leaves the letter in the input stream since it does not match the %d conversion specifier. The simplest thing to do is use getchar() to remove the unwanted character:
if (test != 1) {
getchar();
}
A better solution would be to use fgets() to get a line of input, and sscanf() to parse the input:
char buffer[100];
while (*_mytry<=TRYING)
{
if (fgets(buffer, sizeof buffer, stdin) == NULL) {
fprintf(stderr, "Error in fgets()");
exit(EXIT_FAILURE);
}
test=sscanf(buffer, "%d", &mynum);
if(test!=1 || mynum<0 || mynum>MAX)
printf ("ERROR: please enter a valid number \n");
else if(mynum>generated)
printf("Wrong! The number your trying to guess is smaller\n");
else if(mynum<generated)
printf("Wrong ! The number your trying to guess is bigger\n");
else
break;
*_mytry=*_mytry+1;
}
In the above code, note that the leading space has been removed from the format string. A leading space in a format string causes scanf() to skip leading whitespaces, including newlines. This is useful when the first conversion specifier is %c, for example, because any previous input may have left a newline behind. But, the %d conversion specifier (and most other conversion specifiers) already skips leading whitespace, so it is not needed here.
Additionally, your code has srandom() instead of srand(); and the call to srand() should be made only once, and probably should be at the beginning of main(). And, identifiers with leading underscores are reserved in C, so you should change the names _m, _n, and _mytry.

How to ensure user-input data is an integer

I am new to C, but I know C#. In C#, I could use TryParse to ensure that the user typed in the correct datatype.
Here is the basic code I have for C:
int shallowDepth;
do
{
printf ("\nEnter a depth for the shallow end between 2-5 feet: ");
scanf ("%d", &shallowDepth);
if (shallowDepth < 2 || shallowDepth > 5)
{
printf("\nThe depth of the shallow end must be between 2-5 feet.");
}
}
while (shallowDepth < 2 || shallowDepth > 5);
The problem is if I type characters, such as "asdf", the program goes crazy and repeatedly says "Enter a depth for the shallow end between 2-5 feet: ". I'm not sure why this is exactly happening, but it has to be because it expects an int and I'm passing characters.
So how do I verify that the user inputted data is of int type before trying to store it in a variable? Thanks.
This is happening because with %d scanf will refuse to touch anything that does not look like a number and leaves the text in the buffer. The next time around it will again reach the same text and so on.
I recommend that you ditch scanf for now and try something like fgets and then one of the functions in the strtoXXX family such as strtoul or strtoumax. These functions have a well-defined way of reporting errors and you can easily prompt the user for more text.
For example you could do:
char str[LENGTH];
long x;
if (!fgets(str, sizeof str, stdin)) {
/* Early EOF or error. Either way, bail. */
}
x = strtol(line, NULL, 10);
At this point you could use your number, but be aware that:
You can specify a pointer for strtol to fill and it will point to the first unacceptable character
If the result cannot be represented as a long then strtol will set errno = ERANGE. If you plan to test for this you must set errno = 0 before the strtol
If you want to use scanf you can't test it before. But you don't need too!
In your code if the user doesn't enter a number (or something that starts with a number), scanf returns 0, because it returns the number of parameters it could read.
So you need to check the return value of scanf to check if anything could be read.
Second, you need to remove everything that's still in the puffer.
You can use something like this for that:
while(getchar()!='\n');
If you want to handle files as well, you should catch EOF there, too.
int shallowDepth;
int invalid;
do {
int stat;
invalid = 0;
printf ("\nEnter a depth for the shallow end between 2-5 feet: ");
stat = scanf ("%d", &shallowDepth);
if(stat != 1){
invalid = 1;
while(getchar() != '\n');//clear stdin
} else if (shallowDepth < 2 || shallowDepth > 5){
invalid = 1;
printf("\nThe depth of the shallow end must be between 2-5 feet.");
}
}while (invalid);

How validate user input when the expected value is of type int and the entered value is not of type int?

I have the following code:
#include <stdio.h>
#define MIN 0
#define MAX 9
int main()
{
int n;
while (1) {
printf("Enter a number (%d-%d) :", MIN, MAX);
scanf("%d", &n);
if (n >= MIN && n <= MAX) {
printf("Good\n");
} else {
printf("Damn you!\n");
break;
}
}
return 0;
}
The above code works as expected as long as the user inputs an integer value. For example,
$ ./a.out
Enter a number (0-9) :15
Damn you!
$ ./a.out
Enter a number (0-9) :5
Good
Enter a number (0-9) :3
Good
Enter a number (0-9) :-1
Damn you!
$ ./a.out
But, when the user enters any unexpected input (like <up-arrow> - which is ^[[A, or any string like abc or abc def, etc), it fails and goes in to an infinite loop.
$ ./a.out
Enter a number (0-9) :2
Good
Enter a number (0-9) :^[[A
Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
^C
One thing to note: when the use enters <up-arrow> for the first time, it works as expected! For example,
$ ./a.out
Enter a number (0-9) :^[[A
Damn you!
$
Why is this odd behavior? How should we handle the case where user enters something that is unappropriate?
My advice would be to check the return value of scanf(). If it is zero, there has been a matching failure (ie the user didn't input an integer).
The reason it is succeeding is because n is not altered by scanf() when the match fails, so the check is performed on an uninitialised 'n'. My advice -there- would be to always initialise everything so that you don't end up getting weird logic results like you have there.
For example:
if (scanf("%d",&n) != 1))
{
fprintf(stderr,"Input not recognised as an integer, please try again.");
// anything else you want to do when it fails goes here
}
Personally, I advise ditching scanf altogether for interactive user input, especially for numeric input. It just isn't robust enough to handle certain bad cases.
The %d conversion specifier tells scanf to read up to the next non-numeric character (ignoring any leading whitespace). Assume the call
scanf("%d", &val);
If your input stream looks like {'\n', '\t', ' ', '1', '2', '3', '\n'}, scanf will skip over the leading whitespace characters, read and convert "123", and stop at the trailing newline character. The value 123 will be assigned to val, and scanf will return a value of 1, indicating the number of successful assignments.
If your input stream looks like {'a', 'b', 'c', '\n'}, scanf will stop reading at the a, not assign anything to val, and return 0 (indicating no successful assignments).
So far, so good, right? Well, here's an ugly case: suppose your user types in "12w4". You'd probably like to reject this entire input as invalid. Unfortunately, scanf will happily convert and assign the "12" and leave the "w4" in the input stream, fouling up the next read. It will return a 1, indicating a successful assignment.
Here's another ugly case: suppose your user types in an obnoxiously long number, like "1234567890123456789012345678901234567890". Again, you'd probably like to reject this input outright, but scanf will go ahead and convert and assign it, regardless of whether the target data type can represent that value or not.
To properly handle those cases, you need to use a different tool. A better option is to read the input as text using fgets (protecting against buffer overflows), and manually convert the string using strtol. Advantages: you can detect and reject bad strings like "12w4", you can reject inputs that are obviously too long and out of range, and you don't leave any garbage in the input stream. Disadvantages: it's a bit more work.
Here's an example:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
...
#define DIGITS ... // maximum number of digits for your target data type;
// for example, a signed 16-bit integer has up to 5 digits.
#define BUFSIZ (DIGITS)+3 // Account for sign character, newline, and 0 terminator
...
char input[BUFSIZ];
if (!fgets(input, sizeof input, stdin))
{
// read error on input - panic
exit(-1);
}
else
{
/**
* Make sure the user didn't enter a string longer than the buffer
* was sized to hold by looking for a newline character. If a newline
* isn't present, we reject the input and read from the stream until
* we see a newline or get an error.
*/
if (!strchr(input, '\n'))
{
// input too long
while (fgets(input, sizeof input, stdin) && !strchr(input, '\n'))
;
}
else
{
char *chk;
int tmp = (int) strtol(input, &chk, 10);
/**
* chk points to the first character not converted. If
* it's whitespace or 0, then the input string was a valid
* integer
*/
if (isspace(*chk) || *chk == 0)
val = tmp;
else
printf("%s is not a valid integer input\n", input);
}
}
I would use a char buffer to get the input and then convert it to an integer with e.g. atoi.
Only problem here is that atoi returns 0 on failure (you can't determine if it's 0 because of failure or because the value is 0).
Also you could just compare the strings with strncmp.
// edit:
As suggested in the comments you can do the check with isdigit()
Since I'm a bit in a hurry I couldn't implemented my example in your use case, but I also doubt that this causes any troubles.
Some example code would be:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(void)
{
int x;
char buf[4] = {0};
scanf("%s",buf);
if(isdigit(buf[0]))
{
x = atoi(buf);
if( x > 9)
{
// Not okay
}
else
{
// okay
}
}
else
{
// Not okay
}
return 0;
}
If the first element of the buffer is not a digit you know its wrong input anyway.
Otherwise you check the value now with atoi and look if its greater than 9. ( You don't need to check the lower value since -1 would already be detected in the isdigt call ( buf[0] would be "-" )
I have updated the code as follows (checked scanf() return value) and it works fine.
#include <stdio.h>
#include <errno.h>
#define MIN 0
#define MAX 9
int main()
{
int n, i;
while (1) {
errno = 0;
printf("Enter a number (%d-%d) :", MIN, MAX);
if (scanf("%d", &n) != 1) {
printf("Damn you!\n");
break;
}
if (n >= MIN && n <= MAX) {
printf("Good\n");
} else {
printf("Damn you!\n");
break;
}
}
return 0;
}
The following are few things to note from the scanf() man page!
man scanf
The format string consists of a sequence of directives which describe how to process the sequence of input characters. If processing of a directive fails, no further input is read, and scanf() returns. A "failure" can be either of the following: input failure, meaning that input characters were unavailable, or matching failure, meaning that the input was inappropriate.
RETURN VALUE:
scanf return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure. The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs. EOF is also returned if a read error occurs, in which case the error indicator for the stream is set, and errno is set indicate the error.
scanf return the number of fields it read, so you can do something like
if (scanf("%d",&n)<1) exit(1)
or even:
while(scanf("%d",&n)!=1);

Resources