1

I have been stuck on this problem for hours and don't know what to do. The setup I have completes the guessing game and everything works fine except when the user guesses the same guess twice, it is not supposed to use a "try". I don't want to do anything too advanced, I understand that I have to save my last guess in a variable but do not know how to approach that. Thank you for the help.

         srand(time(NULL));
         int r = rand() %10 + 1 ;
         int guess;
         int tries = 0;
         int lastguess = 0;


         printf("Welcome user, please guess my number from 1 to 10\n"); ;



         while(1){

         scanf(" %d", &guess);
         tries++;
         if (guess == r) {
         printf("Congratulations, you got it in %d tries\n", tries);

         return 0;
         }

         else if( guess == lastguess) {
         printf("You have already tried this value, try another");
         tries - 1;
         }
         else if( guess < 11 && guess > r) {
         guess = lastguess;
         printf("Guess Lower!\n");

         }
         else if( guess > 0 && guess < r ){
         guess = lastguess;
         printf("Guess Higher!\n ");
         }


         else if( guess > 11) {
         printf("The game will  not continue since you chose\n");
         printf("a number that is not between 1 and 10 :(\n");
         return 0;
         }

         else if( guess < 1){
         printf("The game will not continue since you chose\n");
         printf("a number that is not bewtween 2 and 10 :(\n");
         return 0;
         }
 }

MenzD
  • 19
  • 2
  • Use a *Frequency Array*, see this answer [How to remove duplicate char in string in C](https://stackoverflow.com/a/63255183/3422102) Let me know if you are still stuck afterwards and I'll help further, if needed. Basically you need `int guessed[10] = {0};` as your frequency array and then increment the index corresponding to the guess. Before you record a guess you check `if (guessed[guess]) { /* already guessed */ }` If not, it's a valid guess and you just increment `guessed[guess]++;` (you still need your range checks -- this just addresses the duplicate guess issue) – David C. Rankin Feb 14 '21 at 02:34
  • So Would I set guessed[10] = 0; . I should also place a guessed++; after both of the right guess checks. What should i set before the scan to check the guesses? – MenzD Feb 14 '21 at 03:02
  • To access it with `guessed[guess]`it should be `int guessed[11] = {0};`, otherwise you'd have to access it with `guessed[guess-1]`. Anyway you should do nothing before the scan – Jack Lilhammers Feb 14 '21 at 03:10

1 Answers1

2

Continuing from my comment, any time you need to keep track of whether (or how many times) a value within a range has been seen, you want a Frequency Array. Such as how many times is each letter used in this document, or has the user already guessed X?

A frequency array is simply an array with one element for each value in your range. The array is initialized all zero. When you read a value, you increment the index that corresponds to that value, e.g.

array[value]++;

So the array simply keeps track of how many times a given value is seen. In your case to prevent the user from entering the same number more than once and having it count as a valid try -- all you care about is whether array[value] == 0. If it does, then value has not been entered before. if (array[value] != 0) (or simply if (array[value])), you know the user has already provided that value, so it should not count as a valid try.

Think about it for a minute. It takes a bit to wrap your head around what is happening. You start with an array that has an elements that corresponds to each value in your range from 1-10 and each of the elements is initialized 0. So if the user enters 5, you will increment (add +1) the array index that corresponds to 5, e.g. array[5]++;. So array[5] now equals 1. If you check the index for the value the user enters each time against the value at that index in the array -- if it isn't 0, you know the user has already entered that value.

Frequency arrays work for many, many, many types of counting and uniqueness problems, so make sure you understand them before moving on. I have another write-up about them at How to remove duplicate char in string in C which may help if you are still unclear how they track the guesses entered by your user.

How to implement it in your code? Just add a int guessed[11] = {0}; array for your frequency array (the 11 instead of 10 just avoids having to map the indexes from a ones-based 1-10 to a zero-based 0-9 since all array indexes are zero-based in C. (plus the cost of 1 extra int element in the array to save having to guess - 1 each time makes sense).

So with an 11 element frequency array to capture your guesses from 1-10 that allows you to track your user's guess by incrementing guessed[guess]++; with each valid guess and before considering the guess valid, allows you to check if (guessed[guess] != 0) to know that the user has already used that guess.

Putting it altogether, (with a short aside first) you could do:

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

#define NGUESSES 10     /* if you need a constant, #define one (or more) */
#define MAXC 256        /* max number of characters for each entry */

(note: don't use Magic-Numbers in your code, if you need a constant... You now have a single convenient location at the top of your code where you can change the game to use 15, 20, ... whatever number of guesses, without having to pick through all array declarations, if(...) statements, loop limits, etc... to make the change)

Declare Your Frequency Array

int main (void) {

    srand(time(NULL));
    
    char buf[MAXC];                             /* buffer (array) for all user-input */
    int r = rand() % NGUESSES + 1,
        guess,
        guessed[NGUESSES + 1] = {0},            /* frequency array + 1 for range 1 - 10 */
        tries = 0;

Your lastguess is no longer needed. The hours of trying to make that work for check if the user had already entered a guess has been replaced by your frequency array guessed[].

Read All User Input With a Line-Oriented Input Function

    /* don't use Magic-Numbers in your code, use a constant */
    printf ("\nWelcome user, please guess my number from 1 to %d\n", NGUESSES);

    while (1) {
        printf ("\nguess[%2d] : ", tries + 1);  /* prompt for entry each time */
        
        if (fgets (buf, MAXC, stdin) == NULL) { /* read every input into buffer/validate */
            puts ("(user canceled input)");     /* ctrl+d, manual EOF is valid input */
            return 0;                           /* allow user to cancel */
        }
        
        /* use sscanf to parse guess from buf - validate EVERY conversion */
        if (sscanf (buf, "%d", &guess) != 1) {  /* " " not needed, %d ignores whitespace */
            fputs ("  error: invalid integer input.\n", stderr);
            continue;
        }

Always take user-input with a line-oriented input function so what remains in your input buffer stdin does NOT depend on the scanf() conversion specifier used or whether a matching failure occurs. Try entering "bananas" as your input -- and you will quickly see why. Instead read with fgets() into an array and then use sscanf() to parse the values you need from the array (works just like scanf() but reads from your array instead of stdin). Why?

That way no matter what the user inputs, your while() loop doesn't spin off into an infinite loop if the user enters a non-integer value because --- you have read the entire line with fgets() so nothing will remain unread in stdin and regardless of whether the conversion succeeds or fails, stdin is ready for the next input attempt and not full of characters that scanf() left behind.

(with scanf() when a matching-failure occurs, character extraction from stdin ceases at that point so the bad input is left unread in stdin)

That's why you use fgets() (or POSIX getline()) to read every user-input.

Validate The Input Is In Range

        if (guess < 1 || NGUESSES < guess) {    /* validate guess in range */
            fprintf (stderr, "  error: %d not between 1 and %d\n", guess, NGUESSES);
            continue;
        }
        

Check Your Frequency Array To See If guess Already Entered

        if (guessed[guess]) {   /* check if freq array non-zero at guess index */
            fprintf (stderr, "  error: %d was a previous guess.\n", guess);
            continue;
        }
        guessed[guess]++;       /* increment index for guess indicating guess used */
        tries++;

Only now do you know you have a valid input that will constitute a try.

Check Victory or Prompt "Higher!" or "Lower!"

        if (guess == r) {       /* check victory */
            printf ("Congratulations, you got it in %d tries\n", tries);
            return 0;
        }
        else if (r < guess)     /* prompt for high guess */
            puts ("  Guess Lower!");
        else                    /* prompt for low guess */
            puts ("  Guess Higher!");
    }
}

That's it -- done. If you will notice the indentation - that is the complete program.

Example Use/Output

With intentional bad non-integer input and duplicate guesses and guesses out-of-range, you can exercise the program with:

$ ./bin/guess_1-10

Welcome user, please guess my number from 1 to 10

guess[ 1] :  My dog has fleas and my cat has none :)
  error: invalid integer input.

guess[ 1] : 6
  Guess Higher!

guess[ 2] : 0
  error: 0 not between 1 and 10

guess[ 2] : 11
  error: 11 not between 1 and 10

guess[ 2] : 6
  error: 6 was a previous guess.

guess[ 2] : 1
  Guess Higher!

guess[ 3] : 3
  Guess Higher!

guess[ 4] : 5
  Guess Higher!

guess[ 5] : 7
  Guess Higher!

guess[ 6] : 10
  Guess Lower!

guess[ 7] : 8
  Guess Higher!

guess[ 8] : 9 ... drum roll ... and
Congratulations, you got it in 8 tries

What if the user cancels input by generating a manual EOF with Ctrl + d (or Ctrl + z on windows)? A manual EOF is valid input for your program, so you should always respect a user's wish to cancel input by using it, e.g.

$ ./bin/guess_1-10

Welcome user, please guess my number from 1 to 10

guess[ 1] : 3
  Guess Lower!

guess[ 2] : (user canceled input)

By using fgets(), you simply check the return, as you do with EVERY input function you use, and if it is NULL, you know the user generated a manual EOF, so simply exit at that point.

Full Code

The full code to make it easy to copy/paste/compile/test:

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

#define NGUESSES 10     /* if you need a constant, #define one (or more) */
#define MAXC 256        /* max number of characters for each entry */

int main (void) {

    srand(time(NULL));
    
    char buf[MAXC];                             /* buffer (array) for all user-input */
    int r = rand() % NGUESSES + 1,
        guess,
        guessed[NGUESSES + 1] = {0},            /* frequency array + 1 for range 1 - 10 */
        tries = 0;
    
    /* don't use Magic-Numbers in your code, use a constant */
    printf ("\nWelcome user, please guess my number from 1 to %d\n", NGUESSES);

    while (1) {
        printf ("\nguess[%2d] : ", tries + 1);  /* prompt for entry each time */
        
        if (fgets (buf, MAXC, stdin) == NULL) { /* read every input into buffer/validate */
            puts ("(user canceled input)");     /* ctrl+d, manual EOF is valid input */
            return 0;                           /* allow user to cancel */
        }
        
        /* use sscanf to parse guess from buf - validate EVERY conversion */
        if (sscanf (buf, "%d", &guess) != 1) {  /* " " not needed, %d ignores whitespace */
            fputs ("  error: invalid integer input.\n", stderr);
            continue;
        }
        
        if (guess < 1 || NGUESSES < guess) {    /* validate guess in range */
            fprintf (stderr, "  error: %d not between 1 and %d\n", guess, NGUESSES);
            continue;
        }
        
        if (guessed[guess]) {   /* check if freq array non-zero at guess index */
            fprintf (stderr, "  error: %d was a previous guess.\n", guess);
            continue;
        }
        guessed[guess]++;       /* increment index for guess indicating guess used */
        tries++;
        
        if (guess == r) {       /* check victory */
            printf ("Congratulations, you got it in %d tries\n", tries);
            return 0;
        }
        else if (r < guess)     /* prompt for high guess */
            puts ("  Guess Lower!");
        else                    /* prompt for low guess */
            puts ("  Guess Higher!");
    }
}

Give it a go, let me know if it does what you need, and let me know if you have further questions. Make sure you understand theses concepts before moving on -- and I'm here to help if you are still stuck.

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