3

A number guessing C program; After the 4th iteration which prompts “well, is it 5 then?”, the character \n (enter) still remains in the buffer which then gets read by the first getchar() in the while loop, which leave nothing in the buffer to be read by the second getchar(), but still somehow, the flow enters the loop and the “well, is it 6 then?” executes, i don’t understand how. I fixed the code to the purpose i had written, but still i wanna know what causes this exact issue.

Code in Question:

#include <stdio.h>

int main() {
    int guess = 1;
    char c;
    
    printf("The program guesses the number between 1 and 100.\n");
    printf("Respond it with Y if the guess is correct and N if the guess is wrong.\n");
    printf("is your number %d?\n", guess);
    
    while((c = getchar()) != 'Y' || (c = getchar()) != 'y') {
        printf("well, is it %d then?\n", ++guess);
        if((c = getchar()) == '\n')
            continue;
    }
    printf("I knew it was %d", guess);

    return 0;
}

Output:

The program guesses the number between 1 and 100.
Respond with Y if the guess is correct and N if the guess is wrong.
Is your number 1?
n
Well, is it 2 then?
n
Well, is it 3 then?
n
Well, is it 4 then?
nn
Well, is it 5 then?
Well, is it 6 then?
n
Well, is it 7 then?

I was expecting the while loop to terminate, prompt the last printf() statement ultimately leading to end the program but rather with this output.

The program guesses the number between 1 and 100.
Respond it with Y if the guess is correct and N if the guess is wrong.
is your number 1?
n
well, is it 2 then?
n
well, is it 3 then?
n
well, is it 4 then?
nn
well, is it 5 then?
5
  • What do you think which letters are not "y" or not "Y". I say all of them. You might enjoy the fact that e.g. "n" and "N" are both not "y" AND not "Y". Also I suspect that you might be happier with onyl one call to getchar(). Please try those changes and describe the changes to behaviour. Commented Feb 15 at 19:24
  • Your (c = getchar()) != 'Y' || (c = getchar()) != 'y' will read another character if you entered 'y'. Commented Feb 15 at 20:17
  • The line should be while((c = getchar()) != 'Y' && c != 'y') {, which continues looping while c is not equal to 'y' AND not equal to 'Y', and the getchar() function is only called once, so that only 1 character typed by the user is read. Commented Feb 15 at 20:27
  • 1
    Do not use getchar() in programs that communicate with the user. Users type lines of input. Read lines, one at a time. Process a line before you read the next one. fgets() is your friend. Commented Feb 16 at 8:09
  • @n.m.: I have now added a second solution to my answer which does exactly what you suggest. Commented Feb 16 at 19:12

3 Answers 3

5

You have an endless loop, because all letters are not "y" or not "Y".
Try to look for letters which are not "y" AND not "Y".

If you change that you still have problems with your choice of calling getchar() twice, e.g. you have to enter "n" twice to get any feedback.

To change that you need to only once read into a variable and do both checks on that variable.

To do that I recommend to do the reading and checking at the end of the loop.

To do that I recommend to switch from while to do while().

Sign up to request clarification or add additional context in comments.

Comments

5

The condition

while((c = getchar()) != 'Y' || (c = getchar()) != 'y')

will always be true. You probably mean:

while((c = getchar()) != 'Y' && (c = getchar()) != 'y')

Also, you are usually calling getchar 3 times per loop iteration. Since every call of getchar attempts to read exactly one character, calling getchar 3 times per loop iteration would only make sense on input lines which contain 3 characters (including the newline character).

Therefore, you should instead first call getchar once in order to determine whether this first character is valid input. If it appears valid, then you should call getchar a second time to also consume the newline character, but you should also verify that it is indeed the newline character.

If you determine that the input is invalid and you have not yet consumed the newline character, then you should consume and discard the remainder of the line as well as the newline character.

Another problem with your code is that your program will treat all input that is not Y or y as a "no", even if the user did not enter N or n. It would probably be more appropriate to reprompt the user for input if the user did not answer the question properly.

Also, the function getchar returns an int, not a char. So you should generally not store it in a char. Otherwise, if getchar returns the value EOF, you may have trouble distinguishing this value from an actual character value. See this answer of mine to another question for further information.

After doing all of the above, and making a few other minor changes, your code should look like this:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

int main( void )
{
    const char *prompt = "Is your number %d? ";
    int guess = 1;
    
    printf( "The program guesses the number between 1 and 100.\n" );
    printf( "Respond with Y if the guess is correct and N if the guess is wrong.\n" );

    // repeat until user answers "no"
    for (;;)
    {
        int c1, c2;

        // print guess and prompt for input
        printf( prompt, guess );

        // read first character of the input line
        c1 = getchar();
        if ( c1 == EOF )
        {
            printf( "Input error!\n" );
            exit( EXIT_FAILURE );
        }

        // verify that input line is not empty
        if ( c1 == '\n' )
        {
            printf( "Invalid input: Please enter a charater!\n" );
            continue;
        }

        // read second character of the input line
        c2 = getchar();
        if ( c2 == EOF )
        {
            printf( "Input error!\n" );
            exit( EXIT_FAILURE );
        }

        // verify that the second character is the newline character
        if ( c2 != '\n' )
        {
            printf( "Please enter only a single character of input!\n" );

            // discard remainder of the input line
            while ( (c1=getchar()) != EOF && c1 != '\n' )
                ;

            continue;
        }

        // test whether guess was correct
        if ( c1 == 'Y' || c1 == 'y' )
        {
            break;
        }

        // test whether guess was incorrect
        if ( c1 == 'N' || c1 == 'n' )
        {
            // guess a different number in the next loop iteration
            guess++;

            // change prompt for next loop iteration
            prompt = "Well, is it %d then? ";

            continue;
        }

        // if we reach this line, then the input character
        // was invalid
        printf( "Input invalid: Invalid character!\n" );
    }

    // gloat to user
    printf( "I knew it was %d!\n", guess );

    return EXIT_SUCCESS;
}

This program has the following behavior:

The program guesses the number between 1 and 100.
Respond with Y if the guess is correct and N if the guess is wrong.
Is your number 1? 
Invalid input: Please enter a charater!
Is your number 1? sdfhkih 
Please enter only a single character of input!
Is your number 1? q
Input invalid: Invalid character!
Is your number 1? n
Well, is it 2 then? y
I knew it was 2!

However, when dealing with line-based user input, it is generally advisable not to call the input functions of the C standard library (such as getchar) directly, but rather to use your own function which always reads exactly one line of user input at once. An example of such a function is my function get_line_from_user from this answer of mine to another Stack Overflow question. In your case, I would recommend wrapping (a slightly modified version of) this function in a function present_guess_to_user, which always reads an entire line of user input at once, but returns the bool value true if the user ansered "yes", and returns the value false if the user answered "no". If the user did not provide a valid answer, the function should automatically reprompt the user for input. Using this function makes your code in the function main a lot cleaner and easier to understand:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>

bool present_guess_to_user( const char *question, int guess );
void get_line_from_user( const char *prompt, char *buffer, int buffer_size, ... );

int main( void )
{
    const char *question = "Is your number %d? ";
    int guess = 1;

    printf( "The program guesses the number between 1 and 100.\n" );
    printf( "Respond with Y if the guess is correct and N if the guess is wrong.\n" );

    // repeat until user answers "yes"
    while ( ! present_guess_to_user( question, guess ) )
    {
        // guess a different number in the next loop iteration
        guess++;

        // change question for next loop iteration
        question = "Well, is it %d then? ";
    }

    // gloat to user
    printf( "I knew it was %d!\n", guess );

    return EXIT_SUCCESS;
}

// This function will present the guess to the user and if
// the user responds with
// - Y/y, then it will return true,
// - n/n, then it will return false.
// Otherwise, the input is invalid and the function will
// automatically reprompt the user.
bool present_guess_to_user( const char *question, int guess )
{
    // repeat until input is valid
    for (;;)
    {
        char line[1024];

        // get one line of input from user
        get_line_from_user( question, line, sizeof line, guess );

        // verify that user entered exactly one character
        if ( line[0] == '\0' || line[1] != '\0' )
        {
            printf( "Input invalid: Please enter a single character.\n" );
            continue;
        }

        // test whether guess was correct
        if ( line[0] == 'Y' || line[0] == 'y' )
        {
            return true;
        }

        // test whether guess was incorrect
        if ( line[0] == 'N' || line[0] == 'n' )
        {
            return false;
        }

        // if we reach this line, then the character was invalid
        printf( "Input invalid: Invalid character!\n" );
    }
}

// This function will read exactly one line of input from the
// user. It will remove the newline character, if it exists. If
// the line is too long to fit in the buffer, then the function
// will automatically reprompt the user for input. On failure,
// the function will never return, but will print an error
// message and call "exit" instead.
void get_line_from_user( const char *prompt, char *buffer, int buffer_size, ... )
{
    for (;;) //infinite loop, equivalent to while(1)
    {
        va_list vl;
        char *p;

        // prompt user for input
        va_start( vl, buffer_size );
        vprintf( prompt, vl );
        va_end( vl );

        // explicitly flushing the output stream
        // may be necessary on some platforms
        fflush( stdout );

        // attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        // attempt to find newline character
        p = strchr( buffer, '\n' );

        // make sure that entire line was read in (i.e. that
        // the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            // a missing newline character is ok if the next
            // character is a newline character or if we have
            // reached end-of-file (for example if the input is
            // being piped from a file or if the user enters
            // end-of-file in the terminal itself)
            if ( (c=getchar()) != '\n' && !feof(stdin) )
            {
                if ( c == EOF )
                {
                    printf( "Error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

                printf( "Input was too long to fit in buffer!\n" );

                // discard remainder of line
                do
                {
                    c = getchar();

                    if ( c == EOF )
                    {
                        // this error message will be printed if either
                        // a stream error or an unexpected end-of-file
                        // is encountered
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                } while ( c != '\n' );

                // reprompt user for input by restarting loop
                continue;
            }
        }
        else
        {
            // remove newline character by overwriting it with
            // null character
            *p = '\0';
        }

        // input was ok, so break out of loop
        break;
    }
}

This code has very similar behavior as the previous version of my code.

Comments

1

while((c = getchar()) != 'Y' || (c = getchar()) != 'y') { short circuits.
When you type n<enter> the first getchar reads the n and since n!=Y is true, the while condition is true and the second getchar is skipped.
if((c = getchar()) == '\n') then reads the newline and the while loop continues.

If you type Y<enter>, the first part of the while, Y!=Y, is false.
The newline is read by the second getchar and \n!=y is true and the program stays in the while block.
The guess is printed and if((c = getchar()) == '\n') waits for input.
If you type <enter>, the program goes back to the while and waits for input. If you type n<enter>, the n is read and the program goes back to the while where the newline is read and short circuits again.

You can escape the while by typing Yy<enter>, otherwise the while as has been explained is an infinite loop, that with one exception, is always true.

Another solution is to use fgetc in a function. The main difference between getchar and fgetc is fgetc can read from a file or stdin.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int fgetcn ( char *input, size_t size, FILE *pf) {
    int c = 0;
    size_t used = 0;
    size_t limit = size;

    used = 0;
    if ( size > 1) {
        --limit;
    }
    while( EOF != ( c = fgetc ( pf))) {
        if ( c == '\n') {
            if ( used > limit) {
                return 0;
            }
            break;//stop on newline
        }
        if ( used < limit) {
            input[used] = c;//store character
            ++used;//increment for next character
            if ( size > 1) {
                input[used] = 0;//zero terminate
            }
        }
        else { // extra characters consumed and discarded
            ++used;
        }
    }
    if ( EOF == c && 0 == used) {
        return EOF;
    }
    return 1;
}

int main ( void) {
    int guess = 0;
    char c;

    printf("The program guesses the number between 1 and 100.\n");
    printf("Respond it with Y if the guess is correct and N if the guess is wrong.\n");

    while ( 1) {
        ++guess;
        printf("is your number %d?\n", guess);
        if ( EOF == fgetcn ( &c, sizeof c, stdin)) {
            fprintf ( stderr, "problem EOF\n");
            return 1;
        }
        if ( 'y' == c || 'Y' == c) {
            break;
        }
    }
    printf("I knew it was %d\n", guess);

    return 0;
}

1 Comment

Thanks, although i googled the issue and have found out that the second getchar() from the loop wasn't being evaluated because of short-circuit evaluation, link: developerhelp.microchip.com/xwiki/bin/view/software-tools/… , which skips the evaluation once the result is known, the over-reliance on chatgpt was never an option but here i am, thanks alot

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.