2
do
{
    printf("Enter a square, 1-9: ");
    scanf("%d",&choice);
    if (choice == 'q')
    {
        exit(0);
    }
}
while((choice < 1 || choice > 9) || (board[choice] == 'C' || board[choice] == 'U'));

This is a small segment of a larger program, but I wanted to ask the user to enter a number 1-9 (and a number that wasn't already chosen, but that isn't very relevant to my question), but I also wanted the program to end if the user entered 'q' Is there a way to this? Also, how would I stop the user from entering characters other than q? Currently if the user enters a character "Enter a square, 1-9:" starts looping infinitely.

user3840170
  • 26,597
  • 4
  • 30
  • 62
Wes Smith
  • 31
  • 1
  • 1
    No, In general there is no simple way to do this. You can ask `scanf` to read an integer for you, or a string, or a few other things. But there is no way to ask it to read-an-integer-or-maybe-also-the-letter-q. There's just no way to do that. The best you can do is read a string, and see if it's digits and if it is convert it to an `int`, or see if it's "q", or whatever. And to do that you don't even need `scanf`, because you're doing all the work; you might as well read the string using `fgets`. If you're trying to read a *single digit*, you can do it, but IMO that's too artificial. – Steve Summit Jan 20 '23 at 18:13
  • @SteveSummit If you are only trying to read a *single* integer digit or possibly a character like 'q' then you can just use scanf with `" %c"` – Govind Parmar Jan 20 '23 at 18:15
  • @GovindParmar Yeah, I noticed that after I wrote my comment (now updated). But to me this is the programming equivalent of learning how to balance an egg on the end of your nose — it has no bearing whatsoever on any practical problem you'll ever have to solve. (Although, the same can be said of `scanf` in general...) – Steve Summit Jan 20 '23 at 18:19
  • Q: Can you use scanf to read ints and chars at the same time? A: If you mean *digits" (0-9) and chars at the same time - sure! Just use `"%c"`. – paulsm4 Jan 20 '23 at 18:31
  • Very similar question: [How to make that user can input integer or character with scanf](https://stackoverflow.com/q/60480666/12149471) I cannot mark this question as a duplicate, because none of the answers have received any upvotes. – Andreas Wenzel Jan 20 '23 at 18:49
  • This can be done by using `scanf("%d", &choice)` to attempt a match to a numeral, and, if it fails, using `scanf("%c", &c)` to read the next character. To test whether `scanf` succeeds or not, check the return value. When a match is made and a value is assigned, it returns one. You should always be checking the return value of `scanf`. – Eric Postpischil Jan 20 '23 at 18:50
  • I would avoid ever using `scanf()` for user input. Instead read each line into a buffer, then try parsing different things as you need them (using `sscanf()` or other code). – John Bayko Jan 20 '23 at 18:59
  • @JohnBayko: I have implemented your suggestion as an answer. However, I used `strtol` instead of `sscanf` in my solution. – Andreas Wenzel Jan 20 '23 at 20:15
  • Please note that your question would be of higher quality if you simplified it to the core of the problem. In your posted code, you are referencing an array `board` without providing any information on that array, thereby forcing the reader to guess what it means. Also, as you stated in your question, additionally testing whether a number was already chosen is not that relevant to your question. Therefore, your question would probably be better if you removed that part out of your question. [continued in next comment] – Andreas Wenzel Jan 21 '23 at 12:58
  • [continued from previous comment] Normally, you should not change the question in such a way that it invalidates existing answers. However, as far as I can tell, my answer is the only one which answers that part of your question, and I am hereby explicitly giving you permission to invalidate that part of my answer. If you remove that part from your question, then I will also remove it from my answer. – Andreas Wenzel Jan 21 '23 at 13:02

3 Answers3

2

Then you should be reading a character (%c) rather than an integer (%d). You don't lose the ability to test for a valid number with > and < comparisons since numeric characters are stored consecutively on all character sets supported by C:

char input;
do
{ 
    printf("Enter a square, 1-9, or press Q to quit: ");
    if(scanf(" %c", &input) == 1) // i.e. 1 item was successfully read
    {
        if('Q' == input || 'q' == input)
        {
            exit(0);
        }
    }
}
while(input < '1' || input > '9');

Note that I prepended the scanf format string with a space (reason) and added a check to its return value.

To convert a character containing a digit back into a numeric type such as int, use:

int val;
if(isdigit(c)) // Omit this if you *KNOW* already that `c` is a digit (i.e. if it was just read from a digit-validating loop such as the one above) 
    val = c -= '0';
Govind Parmar
  • 20,656
  • 7
  • 53
  • 85
  • I don't think that this solution is ideal, because this program will accept input such as `60` and `6abc` as valid input for the number `6`. For this reason, I believe it would be better to always read an entire line of input, instead of only a single character. – Andreas Wenzel Jan 20 '23 at 20:13
  • @AndreasWenzel I would not find that behavior unexpected from an input that tells me it expects a digit from 1-9 – Govind Parmar Jan 20 '23 at 20:26
0

You cannot use scanf with the %d conversion format specifier to read a character.

Although it is possible to solve this problem with scanf, I instead recommend that you read an entire line of input as a string with the function fgets. If you determine that the user did not enter "q", then you can attempt to convert that string to an integer, and then check whether this integer is in the range 1 to 9.

Here is an example:

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

//forward declaration
void get_line_from_user( char *buffer, int buffer_size );

int main( void )
{
    char board[9] = {
        'C', ' ', 'U',
        'U', ' ', ' ',
        'C', 'U', ' '
    };

    long choice;

    //repeat until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        char line[200], *p;

        printf( "Enter a square (1-9) or \"q\" to quit: " );
        get_line_from_user( line, sizeof line );

        //determine whether user wants to quit
        if ( strcmp( line, "q" ) == 0 )
        {
            printf( "Quitting program!\n" );
            exit( EXIT_SUCCESS );
        }

        //user did not want to quit, so attempt to convert input to
        //an integer
        choice = strtol( line, &p, 10 );
        if ( p == line )
        {
            printf( "Unable to convert input to an integer!\n" );
            continue;
        }

        //verify that the remainder of the line does not contain any
        //non-whitespace characters, so that input such as "6abc"
        //gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected character encountered!\n" );

                //we cannot use "continue" here, because this would
                //continue the innermost loop, but we want to continue
                //the outer loop
                goto continue_outer_loop;
            }
        }

        //verify that input is in the desired range
        if ( choice < 1 || choice > 9 )
        {
            printf( "Please enter a number between 1 and 9!\n" );
            continue;
        }

        //verify that square is not already occupied
        if ( board[choice-1] == 'C' || board[choice-1] == 'U' )
        {
            printf( "Board square is already occupied!\n" );
            continue;
        }

        //input is ok, so we can break out of the infinite loop
        break;

    continue_outer_loop:
        continue;
    }

    printf( "Input is valid!\n" );
    printf( "You entered: %ld\n", choice );
}

//This function will read exactly one line of input from the
//user. 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( char *buffer, int buffer_size )
{
    char *p;

    //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 )
    {
        //attempt to read one more character
        int c = getchar();

        //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 != '\n' && !feof(stdin) )
        {
            printf( "Input was too long to fit in buffer!\n" );

            //discard remainder of line
            while ( c != EOF && c != '\n' )
            {
                c = getchar();
            }
        }
    }
    else
    {
        //remove newline character by overwriting it with
        //null character
        *p = '\0';
    }
}

This program has the following behavior:

Enter a square (1-9) or "q" to quit: test
Unable to convert input to an integer!
Enter a square (1-9) or "q" to quit: 6abc
Unexpected character encountered!
Enter a square (1-9) or "q" to quit: -5
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 0
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 10
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 1
Board square is already occupied!
Enter a square (1-9) or "q" to quit: 4
Board square is already occupied!
Enter a square (1-9) or "q" to quit: 5
Input is valid!
You entered: 5
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0

With scanf("%d"), if the user enters something besides a number, it will return 0 and not consume any input. So you can do things like:

while(1) {
    int val;
    char ch;
    printf("Enter a number or 'q' to quit: ");
    fflush(stdout);
    switch(scanf("%d", &val)) {
    case 1:
        // got a number in 'val', so do something with it
        break;
    case 0:
        scanf("%c", &ch);
        if (ch == 'q' || ch == 'Q') exit(0);
        fprintf(stderr, "You entered an unexpected character '%c'\n", ch);
        break;
    case EOF:
        fprintf(stderr, "Unexpected end-of-file\n");
        exit(1);
    }
}

This has the drawback that if you enter multiple numbers on one line separated by spaces, it will process them all and print out multiple copies of the prompt before processing the next line. To avoid that it is generally better to use getline or fgets for interactive input (to read whole lines) and then use sscanf to process the line.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226