0

I got up to here, but I still need to use while loop somehow. "want to play again(y/n)" and "Illegal guess. Your guess must be between 1 and 200.Try again. Your guess?" don't seem to work. Please help me with the while/do-while loop and fix my two problems above. Thank you.

#include <stdio.h>

int main()
{
    int i,number,guess,tries=5,answer;

    printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200.");
    printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low.");
    printf("\nYou have 5 tries to get the number.\n\nOK, I am thinking of a number. Try to guess it.");

    srand(time(NULL));
    number = rand() % 200 + 1;

    for (i=0;i<tries;i++) {
        printf("\n\nYour guess? ");
        scanf("%i",&guess);

        if (guess==number) {
            printf("**** CORRECT  ****\n\nWant to play again(y/n) ");
            scanf("%i",&answer);

            if (answer=='y') {
                return (i=0);
            }
            else (answer=='n'); {
                printf("Goodbye, It was fun. Play again soon.");
            }

        }
        else if (guess>number) {
            printf("Too high!");
        }
        else if (guess<number) {
            printf("Too low!");
        }
        else (guess>200); {
            printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?");
        }
    }

    printf("\n\nSorry, you ran out of tries.\n\nWant to play again?(y/n) ");
    scanf("%i",&answer);

    if (answer=='y') {
        return (i=0);
    }
    else if (answer=='n'); {
        printf("Goodbye, It was fun. Play again soon.");
    }

    return 0;
}
cs95
  • 379,657
  • 97
  • 704
  • 746
john lee
  • 23
  • 4
  • PIck a language. This has nothing to do with C#. – itsme86 Jun 25 '17 at 00:46
  • _but I still need to use while loop somehow._ then use a `while`loop there, no one is preventing you to do that. Or do you mean that you don't know how to use a `while` loop? In that case, take a book about C and try learning with it, you'll have more success that way. – Pablo Jun 25 '17 at 00:51
  • Welcome to StackOverflow. People can be a bit salty when it's clear that you still need to read [tour] then [ask] then [mcve] . – Dave S Jun 25 '17 at 01:00
  • If I understand your issue, it looks like you want to set a simple `guessok = 0;` flag and do something like `while (!guessok) { printf ("\n your guess? "); if (scanf("%i",&guess) != 1) {--handle error..; continue;} ... test guess values ...; guessok = 1; }`. You can also use a `while (1)` or `for (;;)` and just break when all good guess conditions are satisfied. – David C. Rankin Jun 25 '17 at 01:04
  • `else` doesn't have a condition. `%i` is for converting integers, not characters. `return (i=0);` doesn't make much sense, as `i` is a local variable, it's the same as `return 0` -- what do you think you achieve with that? –  Jun 25 '17 at 01:06
  • Here is a nice summary of most mistakes you can make when asking the user for a certain input: http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html If that does not help, https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ here is a nice summary of what you can do yourself to find mistakes Otherwise https://stackoverflow.com/questions/2069367/how-to-debug-using-gdb If all of that does not help, please describe in detail what does not work. In which way is it different from what you want? – Yunnosch Jun 25 '17 at 01:17
  • You should look at https://stackoverflow.com/questions/44743212/c-programming-guessing-game It seems you're both trying to solve a similar problem. – Jonathan Leffler Jun 25 '17 at 04:56

4 Answers4

3

First, and most important, turn on warnings. You have several elementary mistakes in your code that would be caught with compiler warnings. They're unfortunately off by default. -Wall turns on the basic warnings. It's not "all" warnings, because this is C! -fsanitize=address -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic is a good set of warnings to work with.


You could put a loop around the loop, but that rapidly gets hard to maintain. Instead, put the game into a function and loop around that.

void do_game(int tries) {
    int number = rand() % 200 + 1;

    for (int i=0; i < tries; i++) {
        int guess;
        printf("\n\nYour guess? ");
        scanf("%i",&guess);

        if (guess == number) {
            printf("**** CORRECT  ****\n\n");
            return;
        }
        else if (guess > number) {
            printf("Too high!");
        }
        else if (guess < number) {
            printf("Too low!");
        }
        else if (guess > 200) {
            printf("Illegal guess. Your guess must be between 1 and 200.\nTry again. Your guess?");
        }
    }

    puts("\n\nSorry, you ran out of tries.\n\n");
    return;
}

Note how the game only has to concern itself with the game. No other logic or questions about playing another game. And it can immediately return when the game is over.

Then the rest of the program is pretty simple. Run the game in an infinite loop, break out of it when you're done.

int main() {
    printf("Welcome to the game of Guess It!\nI will choose a number between 1 and 200.");
    printf("\nYou will try to guess that number.If you guess wrong, I will tell you if you guessed too high or too low.");
    printf("\nYou have 5 tries to get the number.\n\nOK, I am thinking of a number. Try to guess it.");

    srand(time(NULL));

    while(1) {
        do_game(5);

        char answer;        
        printf("Want to play again?(y/n) ");
        scanf("%c",&answer);

        if (answer == 'n') {
            printf("Goodbye, It was fun. Play again soon.");
            break;
        }
    }

    return 0;
}

There's a problem, and it's scanf. It's always scanf. scanf is such a problem, there's a whole FAQ for it.

scanf("%i") reads a single integer but not the following newline. That newline, and any other extra input, hangs around on stdin. A later scanf("%c", &answer); might then read that newline instead of their answer.

scanf("%i\n") does not solve the problem. That tells scanf to read an integer, then a newline, then look for another non-whitespace character. scanf is weird.

You're much better off reading the whole line with fgets and parsing it with sscanf. You can write a little utility function for that which gets into variadic arguments.

void line_scanf( const char *fmt, ... ) {
    // Get the list of arguments.
    va_list args;
    va_start(args, fmt);

    // Read a line.
    char line[256];
    fgets(line, sizeof(line), stdin);

    // Scan the line like sscanf() but with a va_list.
    vsscanf( line, fmt, args );

    // Close the list of arguments.
    va_end(args);
}

Then use it just like scanf. It guarantees to read the whole line and not leave newlines or partial input on the buffer.

    int guess;
    printf("\n\nYour guess? ");
    line_scanf("%i",&guess);
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • "*It guarantees to read the whole line*" -- what about ridiculously long lines? ;) still +1 for this nice function, I like the idea. –  Jun 25 '17 at 02:03
1

This is only a partial answer, but it can be a starting point. You really should have a reliable input function. Your scanf() won't do, even if you fix the obvious errors trying to get a character using %i, which is for integers. I won't go into details here, I wrote a document on this. (Basically, you will at least run into problems with unparsable input that scanf() will just leave unread.)

Here's an example how you could do reliable input for your usecase with comments along the way:

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

#define INVALIDNUMBER -1
#define READERROR -2

int readPositiveNumber(void)
{
    char buf[64];

    // read a line:
    if (!fgets(buf, 64, stdin)) return READERROR;
    size_t len = strlen(buf);

    // line was empty -> invalid:
    if (!len) return INVALIDNUMBER;

    // line was not complete (last character isn't newline):
    if (buf[len-1] != '\n')
    {
        // read the rest of the line
        do
        {
            if (!fgets(buf, 64, stdin)) return READERROR;
        } while (!buf[strcspn(buf, "\n")]);

        // input was invalid
        return INVALIDNUMBER;
    }

    // convert to number:
    char *endptr;
    long num = strtol(buf, &endptr, 10);

    // endptr == buf means no characters could be parsed as a number,
    // endptr doesn't point to newline means there were non-numeric characters later:
    if (endptr == buf || *endptr != '\n') return INVALIDNUMBER;

    // if result is out of range of int or is negative -> invalid:
    if (num > INT_MAX || num < 0) return INVALIDNUMBER;

    return (int)num;
}

int main(void)
{
    fputs("Enter a number between 1 and 200: ", stdout);
    int number = readPositiveNumber();
    if (number == READERROR) return EXIT_FAILURE;

    while (number < 1 || number > 200)
    {
        fputs("Enter a valid number between 1 and 200: ", stdout);
        number = readPositiveNumber();
        if (number == READERROR) return EXIT_FAILURE;
    }

    printf("You entered %d.\n", number);
    return EXIT_SUCCESS;
}

Try to understand this function, read the manuals for functions you don't know or understand (google "man strtol" for example will find you a manual page for strtol()).

For reading your yes/no response, use fgets() as well, but of course this function will look different, like check if the input is only 1 character (the second one has to be '\n') and return this one character.


just because it's a bit of fun, here's a possible whole game working robustly:

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

#define INVALIDINPUT -1
#define READERROR -2

static int readLine(char *buf, size_t bufsize)
{
    if (!fgets(buf, bufsize, stdin)) return READERROR;
    size_t len = strlen(buf);
    if (!len) return INVALIDINPUT;
    if (buf[len-1] != '\n')
    {
        do
        {
            if (!fgets(buf, bufsize, stdin)) return READERROR;
        } while (!buf[strcspn(buf, "\n")]);
        return INVALIDINPUT;
    }
    return 0;
}

static int readPositiveNumber(void)
{
    char buf[64];
    int rc = readLine(buf, 64);
    if (rc < 0) return rc;
    char *endptr;
    long num = strtol(buf, &endptr, 10);
    if (endptr == buf || *endptr != '\n') return INVALIDINPUT;
    if (num > INT_MAX || num < 0) return INVALIDINPUT;
    return (int)num;
}

static int readYesNo(void)
{
    char buf[64];
    int rc = readLine(buf, 64);
    if (rc < 0) return rc;
    if (buf[0] == 'y' || buf[0] == 'Y')
    {
        if (buf[1] == '\n') return 1;
        if ((buf[1] == 'e' || buf[1] == 'E')
                && (buf[2] == 's' || buf[2] == 'S')
                && buf[3] == '\n') return 1;
        return INVALIDINPUT;
    }
    if (buf[0] == 'n' || buf[0] == 'N')
    {
        if (buf[1] == '\n') return 0;
        if ((buf[1] == 'o' || buf[1] == 'O')
                && buf[2] == '\n') return 0;
        return INVALIDINPUT;
    }
    return INVALIDINPUT;
}

int main(void)
{
    srand(time(0));

    for (;;)
    {
        int number = rand() % 200 + 1;
        int tries = 5;
        int found = 0;
        while (tries--)
        {
            int guess = INVALIDINPUT;
            while (guess < 1 || guess > 200)
            {
                fputs("guess [1..200]: ", stdout);
                guess = readPositiveNumber();
                if (guess == READERROR) return EXIT_FAILURE;
            }
            if (guess == number)
            {
                puts("Correct!");
                found = 1;
                break;
            }
            else if (guess < number) puts ("Too low!");
            else puts("Too high!");
        }
        if (!found)
        {
            puts("No luck!");
        }
        int yn = INVALIDINPUT;
        while (yn < 0)
        {
            fputs("play again (y/n)? ", stdout);
            yn = readYesNo();
            if (yn == READERROR) return EXIT_FAILURE;
        }
        if (!yn)
        {
            puts("Bye!");
            return EXIT_SUCCESS;
        }
    }
}
1

This exercise is an exercise to ingrain in your mind why scanf is generally a bad choice for taking mixed user input! You can do it, but you must be very careful to account for any characters that remain in the input buffer (i.e. stdin) -- especially when taking character input... Why?

When you enter a value that is read by scanf, the '\n' will always remain in the input buffer (unless accounted for in your format string). Further, on a failed conversion -- all characters will remain in the input buffer. Further, the user can do something stupid like entering "4 is my guess" when prompted leaving is my guess\n for you to deal with.

Further, what if the user cancels input by pressing ctrl + d (or ctrl + z on windoze) generating a manual EOF? You must account for all possibilities for each and every input.

You must also use the correct format specifier to read input. You are not going to read 'y' or 'n' with %d or %i. When you want to read an int use %d when you want to read a char, use %c. You must also take into account that %c never skips leading whitespace.

(you beginning to understand why it's better to use fgets and then call sscanf for user input??)

How do you handle the characters that remain in the input buffer? Well generally you will use getchar() to read until you have read '\n' (generated by pressing Enter) or until EOF is encountered. You can make it easy on yourself by writing a short function like the following:

/* empty characters that remain in stdin */
void fflushstdin ()
{
    for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}

If you call fflushstdin after each input, you will always take care of any characters that remain. If you know chars remain from a prior input that have not been removed, then call it before taking input.

Don't use magic numbers in your code (e.g. 1, 5, 200), instead define any needed constants at the beginning of your code and use the constants in your code. Why? If they change, then you have a single readily accessible place to change them and you don't have to go picking though your code to find them. You can use a #define or an enum like the following:

enum {LOW = 1, TRIES = 5, HIGH = 200 }; 

The remainder of your problems are simply logic problems that you can work out. Incorporating the above, you can handle (what I think you are attempting to do) as follows:

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

enum {LOW = 1, TRIES = 5, HIGH = 200 }; 

/* empty characters that remain in stdin */
void fflushstdin ()
{
    for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}

int main (void) {

    int i, number, guess, ret;
    char answer;

    printf ("Welcome to the game of Guess It!\n"
            "I will choose a number between %d and %d.\n"
            "You will try to guess that number.\n"
            "I will tell you if you guessed too high or too low.\n"
            "You have %d tries to get the number.\n\n"
            "OK, I am thinking of a number. Try to guess it.\n\n", 
            LOW, HIGH, TRIES);

    srand(time(NULL));

    while (1) {                         /* outer loop until user quits  */
        number = rand() % HIGH + 1;     /* set number INSIDE loop       */
        for (i = 0; i< TRIES; i++) {    /* loop for set number of TRIES */

            while (1) {      /* validate user guess, handle cancelation */
                printf ("Your guess no. %d? ", i + 1);      /* prompt   */
                if ((ret = scanf (" %d", &guess)) != 1) { /* chk return */
                    if (ret == EOF) {          /* check for cancelation */
                        printf ("input canceled, exiting.\n");
                        return 0;
                    }
                    fprintf (stderr, "  error: invalid input.\n");
                    fflushstdin();    /* empty chars remaining in stdin */
                    continue;
                }
                if (guess < LOW || guess > HIGH)        /* check limits */
                    printf("Illegal guess. Your guess must be between "
                           "%d and %d.\nTry again. Your guess?", LOW, HIGH);
                break;
            }

            if (guess == number) {  /* correct answer */
                printf ("\n**** CORRECT  ****\n\nWant to play again(y/n) ");
                fflushstdin();
                /* validate answer, you are reading a `char` NOT `int` */
                while ((ret = scanf (" %c", &answer)) != 1 || 
                        (answer != 'y' && answer != 'n')) {
                    fprintf (stderr, "error: invalid answer, play again (y/n) ");
                    if (ret == EOF) {          /* check for cancelation */
                        printf ("input canceled, exiting.\n");
                        return 0;
                    }
                    fflushstdin();    /* empty chars remaining in stdin */
                }
                if (answer == 'y')  /* use goto for breaking nested loops */
                    goto done;

                printf ("Goodbye, It was fun. Play again soon.\n"); /* no */
                return 0;
            }

            if (guess > number)         /* provide > and < feedback */
                printf ("Too high!\n");

            if (guess < number)
                printf("Too low!\n");
        }
        printf ("Sorry, you exhausted all your tries, number was: %d\n"
                "play again (y/n) ", number);

        fflushstdin();
        /* validate answer, you are reading a `char` NOT `int` */
        while ((ret = scanf (" %c", &answer)) != 1 || 
                (answer != 'y' && answer != 'n')) {
            fprintf (stderr, "error: invalid answer, play again (y/n) ");
            if (ret == EOF) {
                printf ("input canceled, exiting.\n");
                return 0;
            }
            fflushstdin();
        }
        if (answer != 'y')
            break;

        done:;  /* goto lable to play again after correct asnwer */
    }

    return 0;
}

Example Use/Output

$ ./bin/guess
Welcome to the game of Guess It!
I will choose a number between 1 and 200.
You will try to guess that number.
I will tell you if you guessed too high or too low.
You have 5 tries to get the number.

OK, I am thinking of a number. Try to guess it.

Your guess no. 1? onehundred
error: invalid input.
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too high!
Your guess no. 3? 125
Too low!
Your guess no. 4? 137
Too high!
Your guess no. 5? 131
Too low!
Sorry, you exhausted all your tries, number was: 132
play again (y/n) y
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too low!
Your guess no. 3? 175
Too low!
Your guess no. 4? 187
Too high!
Your guess no. 5? 181

**** CORRECT  ****

Want to play again(y/n) y
Your guess no. 1? 100
Too low!
Your guess no. 2? 150
Too high!
Your guess no. 3? 125
Too high!
Your guess no. 4? 112
Too high!
Your guess no. 5? 106
Too low!
Sorry, you exhausted all your tries, number was: 110
play again (y/n) n

Note, the above handles stupid user input (like onehundred) and adds number to the failure output to let the user know what he missed.

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

scanf("%i", ...) reads integers in base 10, not characters or strings.

You need to organize your loops. You have 2 main loops, one that runs while the user wants to keep playing, and another that runs while the a game is on.

You program in a nutshell:

int main()
{
  // loop until player has had enough
    // pick a number
    // game loop :
      // get a number from user:
        // user entry loop:
          // print prompt
          // get user entry
          // validate
       // loop number from user: until 0 <= entry <= 200

     // if number is ok
       // user has won, exit game loop
     // if too low
       // say 'low'
     // if too high
       // say high
     // if # of attempts > MAX
       // say 'lost' exit game loop 
   // end game loop

   // want to contine?
     // user entry loop:
       // print prompt
       // get user entry
       // validate
     // loop user entry loop until 0 <= entry <= 200
  // end loop
}

You could start your loops within main a bit like this:

int attempts;
char yesno = 0;
int guess;

do                    // loop until player has had enough
{
   // generate a number here

   attempts = 0;
   while(1)           // loop while game is on
   {
     while (1)        // loop until user entered a valid entry
     {
        // prompt
        // get user guess
        if (0 <= guess && guess <= 200)
          break;
     }

    if (guessed right)
     {
        // game over!
        break;
     }

     // tell if high or low.

     if (++attempts <= MAX)
     {
        // game over!
        break;
     }    
   }

   do                  // loop until user entered a valid entry.
   {
     printf("Another game (y/n)?");
     yesno = fgetc();
   } while(yesno != 'y' && yesno != 'n');  // could make case-insensitive ?

} while (yesno != 'n');

There are probably as many ways to do this as there are numbers between 0 and 200. A good strategy is to start by writing comments in your C file that describe step by step what the program needs to do. Going through them one by one is much easier than having the program only in your head, especially when you are starting to code. It will get easier with time as you get used to juggle the concepts and basic blocks your mind.

Michaël Roy
  • 6,338
  • 1
  • 15
  • 19