1

For some reason scanf reads the character but the program doesn't continue past the while loop used to validate the character even though the character is valid. Sometimes it works and other times it doesn't. The weird thing is I don't do anything different. Any ideas?

do
{
    printf("Enter \"p\" if you want to sort and shuffle a list of players or enter \"s\" if you want to sort a list of slots: "); // prompt
    scanf("%c", &tmp);
} while (tmp != 'p' && tmp != 's');

new code:

printf("Enter 'p' if you want to sort and shuffle a list of players or enter 's' if you want to sort a list of slots:"); // prompt
tmp = getchar();

printf("%c ",tmp);
if (tmp == 'p')
{
    size = readPlayerFile(players); // calling readPlayerFile function
    shufflePlayers(players, size); // call shufflePlayers function
    sortPlayers(players, size); // call sortPlayers function
}
else if (tmp == 's')
{
    printf("hello");
    size = readSlotFile(slots); // calling readSlotFile function
    sortSlot(slots, size); // call sortSlots function

}
james
  • 101
  • 1
  • 8
  • 2
    Did you do any basic debugging? Like using a debugger and/or debug print statements to find out what values `tmp` takes when it works and when it doesn't? – kaylum Feb 16 '17 at 01:34
  • It's working fine on my system. – Jarvis Feb 16 '17 at 01:43
  • 1
    What did you read before this? Did it leave a newline behind in the input? You should consider using `if (scanf(" %c", &tmp) != 1) { …handle error… }` where the space before the `%c` is not an accident — it skips white space (blanks, tabs, newlines) and waits for something else (a `p` or an `s`) to be entered. – Jonathan Leffler Feb 16 '17 at 01:55
  • yeah i did printf("%c", tmp); after the scanf but before the while loop and it always prints p but i dont understand why it wont exit the while loop then. Ill try the error handling now. Thanks – james Feb 16 '17 at 07:56

2 Answers2

2

The simplest option is to add a leading space to the format string in scanf(). For example, if you want to prompt the user to enter a character again when bad input is provided, you could do this:

#include <stdio.h>

int main(void)
{

    char tmp;
    printf("Enter \"p\" if you want to sort and shuffle a list of players "
           "or enter \"s\" if you want to sort a list of slots: "); // prompt
    while (scanf(" %c", &tmp) != 1 || (tmp != 'p' && tmp != 's')) {
        printf("Please enter \"p\" or \"s\": ");
    }
    printf("You chose %c\n", tmp);

    printf("Choose \"a\"scending or \"d\"escending sort: ");
    while (scanf(" %c", &tmp) != 1 || (tmp != 'a' && tmp != 'd')) {
        printf("Please enter \"a\" or \"d\": ");
    }
    printf("You chose %c\n", tmp);

    return 0;
}

There is a problem with this simple approach though. If the user enters "aaap" then the resulting output would be:

Enter "p" if you want to sort and shuffle a list of players or enter "s" if you want to sort a list of slots: aaap
Please enter "p" or "s": Please enter "p" or "s": Please enter "p" or "s": You chose p
Choose "a"scending or "d"escending sort:

Or, worse, if the user enters a correct first character followed by other characters, such as "sfffa":

Enter "p" if you want to sort and shuffle a list of players or enter "s" if you want to sort a list of slots: sfffa
You chose s
Choose "a"scending or "d"escending sort: Please enter "a" or "d": Please enter "a" or "d": Please enter "a" or "d": You chose a

Because scanf() leaves unmatched characters in the input stream, those characters must be dealt with before reading the input stream again. In the simple solution using " %c", the initial space causes scanf() to skip over leading white-space characters, but any other characters will be picked up. And in the real world you can't count on the user cooperating by providing well-behaved input.

One typical, portable, solution is to use getchar() in a loop to clear the input stream after an input operation. You may need to change the input loop logic a bit to do this:

#include <stdio.h>

...

char tmp;
int c;
int scanf_ret;

do {
    printf("Enter \"p\" if you want to sort and shuffle a list of players "
           "or enter \"s\" if you want to sort a list of slots: "); // prompt
    scanf_ret = scanf("%c", &tmp);
    while ((c = getchar()) != '\n' && c != EOF) {
        continue;                   // discard extra characters
    }
} while (scanf_ret != 1 || (tmp != 'p' && tmp != 's'));

After the call to scanf(), a loop is entered that reads and discards any characters remaining in the input stream. Note that c is an int, and that EOF is tested for explicitly in the loop. The getchar() function may return EOF in the event of an error, or if the user signals EOF from the keyboard, or if input has been redirected from a file. Failure to test for EOF in such circumstances would result in an infinite loop condition.

This solution works, even if the user enters unpredictable input, since the input stream is always cleared after getting input. And note that there is no need for the leading space in the format string. Another solution would be to use fgets() to read a line of input into a buffer, and sscanf() to parse the buffer.

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

...

char buffer[1000];
char tmp;
int sscanf_ret;

do {
    printf("Enter \"p\" if you want to sort and shuffle a list of players "
           "or enter \"s\" if you want to sort a list of slots: "); // prompt
    if (fgets(buffer, sizeof buffer, stdin) == NULL) {
        fprintf(stderr, "Error in fgets()\n");
        exit(EXIT_FAILURE);
    }
    sscanf_ret = sscanf(buffer, "%c", &tmp);
} while (sscanf_ret != 1 || (tmp != 'p' && tmp != 's'));

The fgets() function gets an entire line of input, through the newline, or gets (sizeof buffer) - 1 characters from the input stream if the buffer is not large enough to hold all of the characters in the input stream and a \0 terminator. By providing a generously sized buffer, this should work fine. A user could still enter a very large number of characters, and while this will not overflow the buffer, characters would be left behind in the input stream for the next read operation to find. To avoid such issues, if there are extra characters left behind, the input stream should be cleared after the call to fgets() using the getchar() loop from above.

All of these techniques have their place. Clearly the last two are much more robust than the first, and as it is unlikely that a user will enter 999 characters at the input prompt (including the newline), they are almost on equal footing. But, to avoid all surprise, explicitly clearing the input stream after getting user input is best.

ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • thanks for the long explanation i didnt know that! ive gotten rid of the validator because i was still having issues so i just have the prompt and tmp = getchar(); and for whatever reason it still wont work i try to print the character straight after reading it and it still wont work. – james Feb 16 '17 at 17:05
  • If you aren't already doing so, you may need to print a newline to flush the output buffer to the screen: `printf("%c\n", tmp);`. If you add an Edit section to the end of your question with the new code, I will take a look. – ad absurdum Feb 16 '17 at 17:10
  • thanks I'll try that now I've edited the question with the new code, I'm trying to simplify it to try find the problem. the weird thing is that it works with numbers reading them as characters but not with p or s – james Feb 16 '17 at 17:19
  • It prints out the character now when I add \n but it doesn't run the rest of the code – james Feb 16 '17 at 17:22
  • I found the issue, I'm shuffling an array and I'm using srand and rand for it. it doesn't print when the conditions for the shuffle cannot be met. Thank you! sorry for all the hassle – james Feb 16 '17 at 17:28
0

Firstly, scanf() leaves a \n in the input buffer, and it remains in the input buffer the next time it is called. You need to add a space to your format specifier:

scanf(" %c", &tmp)

To skip white space characters, which can be newlines, blank spaces or tabs.

Secondly, you need to check return of scanf(), to ensure only one character was found.

Your code can then look like this:

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

int main(void) {
    char temp;

    do {
        printf("Enter \"p\" if you want to sort and shuffle a list of players or enter \"s\" if you want to sort a list of slots: "); // prompt
        if (scanf(" %c", &tmp) != 1) {
            printf("Invalid character\n");
            exit(EXIT_FAILURE);
        }
    } while (tmp != 'p' && tmp != 's');

    return 0;
}
RoadRunner
  • 25,803
  • 6
  • 42
  • 75