Homework Assignment #2 for Program Design Class
Part 1:
Prompt user for 8 digit account number (0-9), repeat until valid.
Part 2:
Prompt user to set 4 digit pin number (0-9), repeat until valid.
Prompt user to verify pin number, return to Part 2 if invalid.
The program works, however, I'd like to validate for some extra things:
Accept leading zeros ex., '00123456'
Reject additional letters ex., '12345678a'
Reject additional 'words' ex., '12345678 123abc'
I'm thinking, prompt for a string input, check the length of it (4 or 8) and if it passes that test, convert it to an integer and proceed with the tests in place.
Any thoughts?
[ A lot of you dislike the use of scanf, I know. I'm more interested in how I can make minimal changes to my program instead of reinventing [my] wheel! :) ]
#include <stdio.h>
int main()
{
int return_val = 0;
int account_number = 0;
int pin_number = 0;
int pin_number_verify = 0;
int valid_pin = 0;
// Account # Validation
while(1)
{
printf ("Please enter your 8 digit account number:\n");
return_val = scanf("%d", &account_number);
if((account_number > 9999999) && (account_number < 99999999))
{
if (return_val == 1)
{
break;
}
}
printf("Invalid account number. Account number must be 8 digits.\n\n");
while (getchar() != '\n'); /* Clear keyboard input buffer */
}
return_val = 0;
// Pin # Validation
while(1)
{
printf ("\nPlease choose a 4 digit pin number:\n");
return_val = scanf("%d", &pin_number);
while (getchar() != '\n'); /* Clear keyboard input buffer */
if((pin_number > 999) && (pin_number < 9999))
{
if (return_val == 1)
{
while(1)
{
printf("Re-enter pin number:\n");
return_val = scanf("%d", &pin_number_verify);
while (getchar() != '\n'); /* Clear keyboard input buffer */
if(pin_number != pin_number_verify)
{
printf("Pin setup unsuccessful\n\n");
break;
}
else
{
valid_pin = 1;
break;
}
}
}
}
if (valid_pin == 1) {
break;
}
printf("Invalid pin number. Pin number must be 4 digits.\n");
while (getchar() != '\n'); /* Clear keyboard input buffer */
}
// Successful account setup prompt
printf("\nPin setup successful!\n");
printf("Account #: %d\n", account_number);
printf("Pin #: %d\n", pin_number);
printf("Have a nice day.\n");
return 0;
}
I have done a similar approach by having a char array, then used a function that accept the right amount of digit, which can include alphabets or special signs, and used a validating function to validate the input all a series of digits to pass validation. all depending on the requirements!
you also can use a function that read all digits of the right amount and save each digit into the char array and increment a pointer, if you receive any invalid input while typing, you can ignore it and keep taking input for up to a number of ignoring time till breaking out, or return an error.
after all I think you should use a char array because 0001 is still a valid pass key.
If you want to check if they've entered the right number of characters, read a line of input and check the length. Then you can check the contents for validity (all numeric).
you can use isalpha() to check there is any character or not.
Related
I'm writing a program in C where I want a user to be able to change their existing PIN, the only requirement to allow pin to be able to be changed is that user must enter a new PIN that must be a 4 digit number of any combination including those that start with 0 (eg: 0000, 0297, 0005, 0050...) and the PIN must not contain any alphabetical characters
they must then re-enter their new PIN to confirm it
if re-entered pin matches the first newly entered pin then users will be assigned new pin.
if (temppin1 == temppin2)
I have initialised the temp pins to be an int, for comparison arguments like above.
here is a snippet of my code
case '2':
//program asks user to enter their new PIN.
printf("Enter your new PIN:\n");
scanf("%04d", &temp_pin1);
//program asks user to re-enter their new PIN.
printf("Please re-enter to confirm your new PIN:\n");
scanf("%04d", &temp_pin2);
//if the re-entered pin matches the temp_pin1 then then the program will assign the new PIN to the users actual_pin.
if (temp_pin1 == temp_pin2 && (isalpha(temp_pin1) == 0) && (temp_pin1 >= 1000 && temp_pin1 < 9999)) {
printf("\n\n New PIN has been confirmed\n\n");
actual_pin = temp_pin1;
}
//if the user input as letter, some other character or a number outside of the four digit including number starting with 0 range the program will give an appropriate error message.
else if ((temp_pin1 != temp_pin2) && ((temp_pin1 > 1000) && (temp_pin1 < 9999))) {
printf("error: your new PIN didn't match\n");
printf("We couldn't confirm your new PIN\n\n");
}
//all possible 4 digit are between this range and if a number is entered outside this range the user will be given an appropriate error message.
else if ((temp_pin1 < 1000) || (temp_pin1 > 9999)) {
printf("error: Your new pin didn't meet our 4 digit PIN criteria\n\n");
}
break;
I figured out you can use isalpha() == 0 to make sure the user input doesn't accept alphabetical numbers, I also figured out the range of of all possible 4 digit numbers that begin with (1,2,3,4,5,6,7,8,9). the last part I just need to figure out is how to let user 4 digit number that can begin with 0 (eg: 0000, 0297, 0005, 0050...) and store it with the initial 0, I know C will take inputed int numbers beginning with 0 as a null value so but I also need to compare the two variable and see if they are the same, maybe a different datatype needs to be used... but I'm not sure.
Any help, or insight into what I can do to solve this tricky validation problem would be very much appreciated.
scanf can be done using %n to capture the number of characters processed. This will reject inputs of 12, 005 or 12345. %d will only accept digits so abc will be rejected. No need for isalpha.
Clean the input with getchar. If any characters other than a newline are in the input stream. the input is rejected to prevent an input of 1234abc.
fgets can also be used to read a line. strspn will count the consecutive matching characters. The matching digits must be followed by a newline or the input is rejected.
Normally DO NOT MIX FGETS and SCANF. This uses both and will work only because after scanf, getchar cleans the input stream of pending characters.
#include <stdio.h>
#include <string.h>
int main ( void) {
char line[100] = "";
int temp_pin1 = 0;
int temp_pin2 = 0;
int result = 0;
int start = 0;
int end = 0;
int index = 0;
int clean = 0;
do {
if ( temp_pin1 != temp_pin2) {
printf ( "PIN does not match. re-enter PIN\n");
}
do {
if ( clean) {
printf ( "try again\n");
}
end = 0;
start = 0;
printf("Enter your new PIN:\n");
fflush ( stdout);
if ( ( result = scanf ( " %n%d%n", &start, &temp_pin1, &end))) {
if ( 4 != end - start) {
result = 0;
}
}
while ( ( clean = getchar ( ))) {
if ( '\n' == clean) {
break;
}
else {
result = 0;//found trailing characters
}
if ( EOF == clean) {
fprintf ( stderr, "End Of File\n");
return 1;
}
}
} while ( 1 != result);
clean = 0;
do {
if ( clean) {
printf ( "try again\n");
}
index = 0;
clean = 0;
printf("Please re-enter to confirm your new PIN:\n");
fflush ( stdout);
if ( fgets ( line, sizeof line, stdin)) {
index = strspn ( line, "0123456789");
clean = 1;
}
else {
fprintf ( stderr, "End Of File\n");
return 1;
}
} while ( 4 != index || '\n' != line[index]);
sscanf ( line, "%d", &temp_pin2);
} while ( temp_pin1 != temp_pin2);
printf ( "new PIN: %04d\n", temp_pin1);
return 0;
}
Testing isalpha(temp_pin1) makes no sense: you are testing if the PIN number is the ASCII code for a letter, which is irrelevant: 0065 is a valid PIN, but also happens to be the ASCII code for 'A'.
You should just read the PIN as an int an verify that it is in the range 0 to 9999 inclusive:
case '2':
//program asks user to enter their new PIN.
printf("Enter your new PIN:\n");
if (scanf("%d", &temp_pin1) != 1 || temp_pin1 < 0 || temp_pin1 > 9999) {
printf("invalid PIN: must be 4 digits\n");
break;
}
//program asks user to re-enter their new PIN.
printf("Please re-enter to confirm your new PIN:\n");
if (scanf("%d", &temp_pin2) != 1 || temp_pin1 != temp_pin2) {
printf("error: your new PIN didn't match\n");
printf("We couldn't confirm your new PIN\n\n");
break;
}
printf("\n\n New PIN has been confirmed\n\n");
actual_pin = temp_pin1;
break;
Your program is fully functional; there is no need for any change. 0008 is same as 8, as it is stored internally as an integer. There will not be any problem during comparison. If needed, you can change int to char[4] (or char[5] for the terminating null byte if you want to process it like a string) if necessary. Read the pin using
char temp_pin1[5];
scanf("%4s", temp_pin1);
or
char temp_pin1[4];
scanf("%c%c%c%c", &temp_pin1[0], &temp_pin1[1], &temp_pin1[2], &temp_pin1[3]);
And then check if the characters are numbers.
Here's a small portion of a practice I'm doing preventing erroneous inputs.
while(1) {
printf("Choose From 1 to 7 ");
if( scanf("%d", &nNum ) != 1) {
printf("Please only choose from the numbers 1-7.");
fgets(sErraticInputs, 100 , stdin);
} else if (nNum > 7 || nNum <= 0) {
printf("Please only choose from the numbers 1-7.");
} else {
break;
}
}
I was doing a good job, until I entered "6;p". It executed the 6 portion and ran correctly, but technically speaking it should have taken the whole thing as the input, and proceeded with the error message.
First of all I don't think the posted code can give the said result. The break statement will end the while(1) when 6 has been read so there will not be printed an error message.
If we assume that the break isn't part of your real code this is what happens:
When scanf is told to read an integer, it will continue reading from the input stream as long as the next character (together with the previous read characters) can be converted into an integer. As soon as the next character can not be used as part of an integer, scanf will stop and give you the result of what it has parsed so far.
In your case the input stream contains
6;p\n
So scanf will read the 6 and stop (i.e. return 6). The input stream now contains:
;p\n
Consequently this will be the input for your next scanf and cause the input error, you saw.
One way to solve this would be to flush stdin after all scanf - both on success and on failure:
nNum = 0;
while(nNum != 7) // Just as an example I use input 7 to terminate the loop
{
printf("Choose From 1 to 7 ");
if( scanf("%d", &nNum ) != 1 || nNum > 7 || nNum <= 0)
{
printf("Please only choose from the numbers 1-7.");
}
else
{
printf("Valid input %d\n", nNum);
// **************************** break;
}
fgets(sErraticInputs, 100 , stdin); // Always empty stdin
}
note: Using fgets with size 100 doesn't really ensure a complete flush... you should actually use a loop and continue until a '\n' is read.
With the change above input like 6;p will be taken as a valid input with value 6 and the ;p will be thrown away.
If that's not acceptable, you could drop the use of scanf and do the parsing yourself. There are several options, e.g. fgets or fgetc
The example below uses fgetc
#include <stdio.h>
#include <stdlib.h>
int get_next()
{
int in = fgetc(stdin);
if (in == EOF) exit(1); // Input error
return in;
}
void empty_stdin()
{
while(get_next() != '\n') {};
}
int main(void) {
int in;
int nNum = 0;
while(nNum != 7)
{
printf("Choose From 1 to 7 \n");
in = get_next();
if (in == '\n' || in <= '0' || in > '7') // First input must be 1..7
{
printf("Please only choose from the numbers 1-7.\n");
if (in != '\n') empty_stdin();
}
else
{
nNum = in - '0';
in = get_next();
if (in != '\n') // Second input must be \n
{
printf("Please only choose from the numbers 1-7.\n");
empty_stdin();
}
else
{
printf("Valid input: %d\n", nNum);
}
}
}
return 0;
}
This code will only accept a number (1..7) followed by a newline
Here's why the "whole thing" is not taken as the input. From the man pages:
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...
Here's the full text. Have a look at this as well.
One approach would be to read in the whole input using fgets and check whether the length of the input is greater than 1. For an input of length 1, check if the input is a number and so on...
I got up to here, but I still need to use while loop somehow. "want to play again(y/n)" and "Illegal guess. Your guess must be between 1 and 200.Try again. Your guess?" don't seem to work. Please help me with the while/do-while loop and fix my two problems above. Thank you.
#include <stdio.h>
int main()
{
int i,number,guess,tries=5,answer;
printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200.");
printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low.");
printf("\nYou have 5 tries to get the number.\n\nOK, I am thinking of a number. Try to guess it.");
srand(time(NULL));
number = rand() % 200 + 1;
for (i=0;i<tries;i++) {
printf("\n\nYour guess? ");
scanf("%i",&guess);
if (guess==number) {
printf("**** CORRECT ****\n\nWant to play again(y/n) ");
scanf("%i",&answer);
if (answer=='y') {
return (i=0);
}
else (answer=='n'); {
printf("Goodbye, It was fun. Play again soon.");
}
}
else if (guess>number) {
printf("Too high!");
}
else if (guess<number) {
printf("Too low!");
}
else (guess>200); {
printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?");
}
}
printf("\n\nSorry, you ran out of tries.\n\nWant to play again?(y/n) ");
scanf("%i",&answer);
if (answer=='y') {
return (i=0);
}
else if (answer=='n'); {
printf("Goodbye, It was fun. Play again soon.");
}
return 0;
}
First, and most important, turn on warnings. You have several elementary mistakes in your code that would be caught with compiler warnings. They're unfortunately off by default. -Wall turns on the basic warnings. It's not "all" warnings, because this is C! -fsanitize=address -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic is a good set of warnings to work with.
You could put a loop around the loop, but that rapidly gets hard to maintain. Instead, put the game into a function and loop around that.
void do_game(int tries) {
int number = rand() % 200 + 1;
for (int i=0; i < tries; i++) {
int guess;
printf("\n\nYour guess? ");
scanf("%i",&guess);
if (guess == number) {
printf("**** CORRECT ****\n\n");
return;
}
else if (guess > number) {
printf("Too high!");
}
else if (guess < number) {
printf("Too low!");
}
else if (guess > 200) {
printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?");
}
}
puts("\n\nSorry, you ran out of tries.\n\n");
return;
}
Note how the game only has to concern itself with the game. No other logic or questions about playing another game. And it can immediately return when the game is over.
Then the rest of the program is pretty simple. Run the game in an infinite loop, break out of it when you're done.
int main() {
printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200.");
printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low.");
printf("\nYou have 5 tries to get the number.\n\nOK, I am thinking of a number. Try to guess it.");
srand(time(NULL));
while(1) {
do_game(5);
char answer;
printf("Want to play again?(y/n) ");
scanf("%c",&answer);
if (answer == 'n') {
printf("Goodbye, It was fun. Play again soon.");
break;
}
}
return 0;
}
There's a problem, and it's scanf. It's always scanf. scanf is such a problem, there's a whole FAQ for it.
scanf("%i") reads a single integer but not the following newline. That newline, and any other extra input, hangs around on stdin. A later scanf("%c", &answer); might then read that newline instead of their answer.
scanf("%i\n") does not solve the problem. That tells scanf to read an integer, then a newline, then look for another non-whitespace character. scanf is weird.
You're much better off reading the whole line with fgets and parsing it with sscanf. You can write a little utility function for that which gets into variadic arguments.
void line_scanf( const char *fmt, ... ) {
// Get the list of arguments.
va_list args;
va_start(args, fmt);
// Read a line.
char line[256];
fgets(line, sizeof(line), stdin);
// Scan the line like sscanf() but with a va_list.
vsscanf( line, fmt, args );
// Close the list of arguments.
va_end(args);
}
Then use it just like scanf. It guarantees to read the whole line and not leave newlines or partial input on the buffer.
int guess;
printf("\n\nYour guess? ");
line_scanf("%i",&guess);
This is only a partial answer, but it can be a starting point. You really should have a reliable input function. Your scanf() won't do, even if you fix the obvious errors trying to get a character using %i, which is for integers. I won't go into details here, I wrote a document on this. (Basically, you will at least run into problems with unparsable input that scanf() will just leave unread.)
Here's an example how you could do reliable input for your usecase with comments along the way:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INVALIDNUMBER -1
#define READERROR -2
int readPositiveNumber(void)
{
char buf[64];
// read a line:
if (!fgets(buf, 64, stdin)) return READERROR;
size_t len = strlen(buf);
// line was empty -> invalid:
if (!len) return INVALIDNUMBER;
// line was not complete (last character isn't newline):
if (buf[len-1] != '\n')
{
// read the rest of the line
do
{
if (!fgets(buf, 64, stdin)) return READERROR;
} while (!buf[strcspn(buf, "\n")]);
// input was invalid
return INVALIDNUMBER;
}
// convert to number:
char *endptr;
long num = strtol(buf, &endptr, 10);
// endptr == buf means no characters could be parsed as a number,
// endptr doesn't point to newline means there were non-numeric characters later:
if (endptr == buf || *endptr != '\n') return INVALIDNUMBER;
// if result is out of range of int or is negative -> invalid:
if (num > INT_MAX || num < 0) return INVALIDNUMBER;
return (int)num;
}
int main(void)
{
fputs("Enter a number between 1 and 200: ", stdout);
int number = readPositiveNumber();
if (number == READERROR) return EXIT_FAILURE;
while (number < 1 || number > 200)
{
fputs("Enter a valid number between 1 and 200: ", stdout);
number = readPositiveNumber();
if (number == READERROR) return EXIT_FAILURE;
}
printf("You entered %d.\n", number);
return EXIT_SUCCESS;
}
Try to understand this function, read the manuals for functions you don't know or understand (google "man strtol" for example will find you a manual page for strtol()).
For reading your yes/no response, use fgets() as well, but of course this function will look different, like check if the input is only 1 character (the second one has to be '\n') and return this one character.
just because it's a bit of fun, here's a possible whole game working robustly:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define INVALIDINPUT -1
#define READERROR -2
static int readLine(char *buf, size_t bufsize)
{
if (!fgets(buf, bufsize, stdin)) return READERROR;
size_t len = strlen(buf);
if (!len) return INVALIDINPUT;
if (buf[len-1] != '\n')
{
do
{
if (!fgets(buf, bufsize, stdin)) return READERROR;
} while (!buf[strcspn(buf, "\n")]);
return INVALIDINPUT;
}
return 0;
}
static int readPositiveNumber(void)
{
char buf[64];
int rc = readLine(buf, 64);
if (rc < 0) return rc;
char *endptr;
long num = strtol(buf, &endptr, 10);
if (endptr == buf || *endptr != '\n') return INVALIDINPUT;
if (num > INT_MAX || num < 0) return INVALIDINPUT;
return (int)num;
}
static int readYesNo(void)
{
char buf[64];
int rc = readLine(buf, 64);
if (rc < 0) return rc;
if (buf[0] == 'y' || buf[0] == 'Y')
{
if (buf[1] == '\n') return 1;
if ((buf[1] == 'e' || buf[1] == 'E')
&& (buf[2] == 's' || buf[2] == 'S')
&& buf[3] == '\n') return 1;
return INVALIDINPUT;
}
if (buf[0] == 'n' || buf[0] == 'N')
{
if (buf[1] == '\n') return 0;
if ((buf[1] == 'o' || buf[1] == 'O')
&& buf[2] == '\n') return 0;
return INVALIDINPUT;
}
return INVALIDINPUT;
}
int main(void)
{
srand(time(0));
for (;;)
{
int number = rand() % 200 + 1;
int tries = 5;
int found = 0;
while (tries--)
{
int guess = INVALIDINPUT;
while (guess < 1 || guess > 200)
{
fputs("guess [1..200]: ", stdout);
guess = readPositiveNumber();
if (guess == READERROR) return EXIT_FAILURE;
}
if (guess == number)
{
puts("Correct!");
found = 1;
break;
}
else if (guess < number) puts ("Too low!");
else puts("Too high!");
}
if (!found)
{
puts("No luck!");
}
int yn = INVALIDINPUT;
while (yn < 0)
{
fputs("play again (y/n)? ", stdout);
yn = readYesNo();
if (yn == READERROR) return EXIT_FAILURE;
}
if (!yn)
{
puts("Bye!");
return EXIT_SUCCESS;
}
}
}
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.
scanf("%i", ...) reads integers in base 10, not characters or strings.
You need to organize your loops. You have 2 main loops, one that runs while the user wants to keep playing, and another that runs while the a game is on.
You program in a nutshell:
int main()
{
// loop until player has had enough
// pick a number
// game loop :
// get a number from user:
// user entry loop:
// print prompt
// get user entry
// validate
// loop number from user: until 0 <= entry <= 200
// if number is ok
// user has won, exit game loop
// if too low
// say 'low'
// if too high
// say high
// if # of attempts > MAX
// say 'lost' exit game loop
// end game loop
// want to contine?
// user entry loop:
// print prompt
// get user entry
// validate
// loop user entry loop until 0 <= entry <= 200
// end loop
}
You could start your loops within main a bit like this:
int attempts;
char yesno = 0;
int guess;
do // loop until player has had enough
{
// generate a number here
attempts = 0;
while(1) // loop while game is on
{
while (1) // loop until user entered a valid entry
{
// prompt
// get user guess
if (0 <= guess && guess <= 200)
break;
}
if (guessed right)
{
// game over!
break;
}
// tell if high or low.
if (++attempts <= MAX)
{
// game over!
break;
}
}
do // loop until user entered a valid entry.
{
printf("Another game (y/n)?");
yesno = fgetc();
} while(yesno != 'y' && yesno != 'n'); // could make case-insensitive ?
} while (yesno != 'n');
There are probably as many ways to do this as there are numbers between 0 and 200. A good strategy is to start by writing comments in your C file that describe step by step what the program needs to do. Going through them one by one is much easier than having the program only in your head, especially when you are starting to code. It will get easier with time as you get used to juggle the concepts and basic blocks your mind.
if there is no initialization ("mod=0") , this code go infinite loop.
I can't understand why this code go loop, even if I used getchar();
to erase the buffer.
when I typed "1" first, and typed "a" next, there goes an infinite loop.
Can anybody help me with understanding this situation?
int main()
{
srand((unsigned)time(NULL));
int mod = 0;
int val = 0;
do {
printf("\t-----------------------------\n");
printf("\t|%5s %5s %5s %5s|\n", "1.create", "2.modify", "3.print", "4.quit");
printf("\t|%15s","Input command : ");
scanf("%d", &mod);
printf("\t-----------------------------\n");
switch (mod){
case 1: random(); val++; break;
case 2: if(val != 0) { modify(); break; }
case 3: if(val != 0) { print(); break; }
default: getchar(); printf("\tUnknown Command!! Retry!! \n"); break;
}
} while (mod != 4);
}
I compiled this code with Visual Studio 2015.
When you input a, it's an invalid input for mod as scanf() expects an int for %d. So it's not read into mod. So the mod is left with the value of mod inputted in the previous iteration.
And the reason it goes in an infinite loop is because scanf() doesn't discard the invalid input. So repeatedly attempts to read a and fails and loop goes on.
Check the return value of scanf() and discard any invalid input(s).
scanf() is notoriously bad for reading user input and proper handling input failures is generally harder using it.
A better approach is to read a line input using fgets() and then parse it using sscanf().
do {
...
printf("\t|%15s","Input command : ");
fgets(line, sizeof line, stdin);
char *p = strchr(line, '\n');
if(p) *p = 0; /* remove tailing newline, if present */
if( sscanf(line, "%d", &mod) != 1) {
printf("Invalid input\n");
continue;
}
printf("\t-----------------------------\n");
....
}while (mod != 4);
The problem with your code is, that once you typed in an number, which is a valid menu option, the variable mod is always equal to the same number, that was input during the first time you entered it, if you enter a wrong input the second time. This behavior comes from the fact, that
scanf(%d, &mod);
tries to read an integer, but as you entered an 'a' as a second option for example, the input is not able to read an integer from your Standard input. So it will not enter the default case of your switch method, as the variable mod is equal to the input from the first valid input you entered.
int getIndex(){
int index = 0;
do{
printf("Enter your Index(0..80),-1 to STOP,-2 to RESTART,-3 to START NEW BOARD: ");
scanf("%d", &index);
} while (!(index >= -3 && index <=80));
return index;
}
Hello, given that i have written the above method in C for a sudoku game board . what can i do to prevent the user from entering a alphabet? and keep prompting until the valid input is gotten. i have just started C .what is limiting me is the scanf flag specifier, i specified a int flag which means if a user enters a string, im screwed.
All you need is to check the return value of scanf and then clear the character from the input buffer(stdin) if any invalid input was entered. So change your code to the following:
int getIndex()
{
int index = 0;
while(1) //infinite loop
{ printf("Enter your Index(0..80),-1 to STOP,-2 to RESTART,-3 to START NEW BOARD: ");
if(scanf("%d", &index)==1) //scanf is successful scanning a number(input is a number)
{
if(index >= -3 && index <= 80) // if input is in range
break; //break out of while loop
printf("Number must be in range of -3 to 80\n"); //input is a number,but not in range
}
else //input is not a number
{
scanf("%*s"); //Clear invalid input
//printf("Invalid input\n");
fprintf(stderr, "Invalid input\n"); //printf also works but errors are meant to be in the stderr
}
}
return index;
}