Scanf skipped in loop (Hangman)

2019-08-20 05:08发布

This program essentially asks for a secret string, then asks a user to repeatedly guess single chars of that string until he guesses it all. It works however every second time the while loop is run it skips user input for the guessed char. How do I fix this?

int main(){
  char guess;
  char test2 [50];
  char * s = test2;

  char output [50];
  char * t = output;


  printf("Enter the secret string:\n");
  fgets(test2, 50, stdin);
  for (int i=0;i<49;i++){    //fills ouput with _ spaces
    *(output +i)='_';

  while(strcmp(s,t) != 0){
    printf("Enter a guess:");
    scanf("%c",&guess);
    printf("You entered: %c\n", guess);
    showGuess(guess,s, t );           // makes a string "output" with guesses in it
    printf("%s\n",t);
  }
  printf("Well Done!");
}

标签: c loops scanf
4条回答
2楼-- · 2019-08-20 05:36

Newline character entered in the previous iteration is being read by scanf. You can take in the '\n' by using the getc() as follows:

scanf("%c",&guess);
getc(stdin);

..

This changed worked for me. Though the right explanation and c leaner code is the one given by @7heo.tk

查看更多
趁早两清
3楼-- · 2019-08-20 05:44

As pointed out in some other answers and comments, you need to "consume" the "newline character" in the input.

The reason for that is that the input from your keyboard to the program is buffered by your shell, and so, the program won't see anything until you actually tell your shell to "pass the content of its buffer to the program". At this point, the program will be able to read the data contained in the previous buffer, e.g. your input, followed by one the character(s) used to validate your input in the shell: the newline. If you don't "consume" the newline before you do another scanf, that second scanf will read the newline character, resulting in the "skipped scanf" you've witnessed. To consume the extra character(s) from the input, the best way is to read them and discard what you read (what the code below does, notice the

while(getc(stdin) != '\n');

line after your scanf. What this line does is: "while the character read from stdin is not '\n', do nothing and loop.").

As an alternative, you could tell your shell to not buffer the input, via the termios(3) functions, or you could use either of the curses/ncurses libraries for the I/O.

So here is what you want:

int main(){
  char guess;
  char test2 [50];
  char * s = test2; // 3. Useless

  char output [50];
  char * t = output; // 3. Useless

  int i;  // 8. i shall be declared here.

  printf("Enter the secret string:\n");
  fgets(test2, 50, stdin);
  for (i=0;i<50;i++) if (test2[i] == '\n') test2[i] = '\0'; // 4. Remove the newline char and terminate the string where the newline char is.
  for (int i=0;i<49;i++){ // 5. You should use memset here; 8. You should not declare 'i' here.
    *(output +i)='_';
  } // 1. Either you close the block here, or you don't open one for just one line.
  output[49] = '\0'; // 6. You need to terminate your output string.

  while(strcmp(s,t) != 0){ // 7. That will never work in the current state.
    printf("Enter a guess:");
    scanf("%c",&guess);
    while(getc(stdin) != '\n');
    printf("You entered: %c\n", guess);
    showGuess(guess,s, t );
    printf("%s\n",t);
  }
  printf("Well Done!");
  return 0; // 2. int main requires that.
}

Other comments on your code:

  1. You opened a block after your for loop and never closed it. That might be causing problems.
  2. You declared your main as a function returning an integer... So you should at least return 0; at the end.
  3. You seem to have understood that char * t = output; copies output's value and uses t as a name for the new copy. This is wrong. You are indeed copying something, but you only copy the address (a.k.a reference) of output in t. As a result, output and t refer to the same data, and if you modify output, t will get modified; and vice versa. Otherwise said, those t and s variables are useless in the current state.
  4. You also need to remove the newline character from your input in the test2 buffer. I have added a line after the fgets for that.
  5. Instead of setting all the bytes of an array "by hand", please consider using the memset function instead.
  6. You need to actually terminate the output string after you "fill" it, so you should allocate a '\0' in last position.
  7. You will never be able to compare the test2 string with the output one, since the output one is filled with underscores, when your test2 is NULL terminated after its meaningful content.
  8. While variables at the loop scope are valid according to C99 and C11, they are not standard in ANSI C; and it is usually better to not declare any variable in a loop.

Also, "_ spaces" are called "underscores" ;)


Here is a code that does what you want:

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

#define LEN 50

int main()
{
    char phrase[LEN];
    char guessed[LEN];
    char guess;
    int i, tries = 0;

    puts("Please enter the secret string:");
    if(fgets(phrase, LEN, stdin) == NULL)
      return 1;
    for(i = 0; i < LEN && phrase[i] != '\n'; i++); // Detect the end of input data.
    for(; i < LEN; i++)                            // For the rest of the input data,
      phrase[i] = '_';                             // fill with underscores (so it can be compared with 'guessed' in the while loop).
    phrase[LEN - 1] = '\0';                        // NULL terminate 'phrase'

    memset(guessed, '_', LEN);                     // Fill 'guessed' with underscores.
    guessed[LEN - 1] = '\0';                       // NULL terminate 'guessed'

    while(strcmp(phrase, guessed) != 0)            // While 'phrase' and 'guessed' differ
    {
      puts("Enter a guess (one character only):");
      if(scanf("%c", &guess) != 1)
      {
        puts("Error while parsing stdin.");
        continue;
      }
      if(guess == '\n')
      {
        puts("Invalid input.");
        continue;
      }
      while(getc(stdin) != '\n');                  // "Eat" the extra remaining characters in the input.
      printf("You entered: %c\n", guess);
      for(i = 0; i < LEN; i++)                     // For the total size,
        if(phrase[i] == guess)                     // if guess is found in 'phrase'
          guessed[i] = guess;                      // set the same letters in 'guessed'
      printf("Guessed so far: %s\n", guessed);
      tries++;
    }
    printf("Well played! (%d tries)\n", tries);
    return 0;
}

Feel free to ask questions in the comments, if you are not getting something. :)

查看更多
Root(大扎)
4楼-- · 2019-08-20 05:47

Change

scanf("%c",&guess);

with

scanf(" %c",&guess);

It should ignore '\n'.

查看更多
5楼-- · 2019-08-20 05:48

For a quick and dirty solution try

// the space in the format string consumes optional spaces, tabs, enters
if (scanf(" %c", &guess) != 1) /* error */;

For a better solution redo your code to use fgets() and then parse the input.

查看更多
登录 后发表回答