Search code examples
cinputscanffgetsgets

C - Is there a way to read a single character of user input, and not have the rest "pushed down" to the next request for input?


So, I'm working on a simple hangman game in C, and I have the function read_guess, shown below.

void read_guess(char *guesses, char *p_current_guess)
{
    int valid_guess = 0;

    // Repeatedly takes input until guess is valid
    while (valid_guess == 0)
    {
        printf(">>> ");
        fgets(p_current_guess, 2, stdin);

        if (!isalpha(*p_current_guess)) printf("Guesses must be alphabetic. Please try again.\n\n");
        else
        {
            valid_guess = 1;

            // Iterates over array of guesses and checks if letter has already been guessed
            for (int i = 0; guesses[i] != '\0'; i++)
            {
                if (guesses[i] == *p_current_guess)
                {
                    printf("You have already guessed this letter. Please try again.\n\n");
                    valid_guess = 0;
                    break;
                }
            }
        }
    }
}

I've tried all the standard input functions (including getchar), but with all of them, when an input larger than one character is supplied, instead of taking just the first character and moving on (or asking again), the rest of the input is "pushed back", and the next time input is requested, whether it be because the input contained a non-alphabetic character or the next round begins, the rest of the input is automatically processed. This repeats for each character of the input.

How can I avoid this?


Solution

  • You are using fgets which is good, but unfortunately not the right way...

    fgets reads up to an end of line or at most 1 less the the number of character asked. And of course remaining characters are left for the next read operation...

    The idiomatic way would be to ensure reading up to the end of line, whatever the length, or at least up to a much larger length.

    1. Simple but could fail in more than SIZE characters on input:

      #define SIZE 64
      ...
      
      void read_guess(char *guesses, char *p_current_guess)
      {
          char line[SIZE];
          int valid_guess = 0;
      
          // Repeatedly takes input until guess is valid
          while (valid_guess == 0)
          {
              printf(">>> ");
              fgets(line, SiZE, stdin);                // read a line of size at most SIZE-1
              p_current_guess[0] = line[0];            // keep first character
              p_current_guess[1] = '\0';
              ...
      
    2. Robust but slightly more complex

      /**
       * Read a line and only keep the first character
       *
       * Syntax: char * fgetfirst(dest, fd);
       *
       * Parameters:
       *  dest: points to a buffer of size at least 2 that will recieve the
       *         first character followed with a null
       *  fd  : FILE* from which to read
       *
       * Return value: dest if one character was successfully read, else NULL
       */
      char *readfirst(dest, fd) {
      #define SIZE 256              // may be adapted
          char buf[SIZE];
          char *cr = NULL;          // return value initialized to NULL if nothing can be read
          for (;;) {
              if(NULL == fgets(buff, sizeof(buff), fd)) return cr; // read error or end of file
              if (0 == strcspn(buff, "\n")) return cr;             // end of file
              if (cr == NULL) {                                    // first read:
                  cr = dest;                        //  prepare to return first char
                  dest[0] = buff[0];
                  dest[1] = 0;
              }
          }
      }
      

      You can then use it simply in your code:

      void read_guess(char *guesses, char *p_current_guess)
      {
          int valid_guess = 0;
      
          // Repeatedly takes input until guess is valid
          while (valid_guess == 0)
          {
              printf(">>> ");
              fgetfirst(p_current_guess, stdin);