1

I'm new to C and programming in general. It's a guessing game where the user has a certain amount of guesses to guess whatever the hidden message is.

Unfortunately, even if I enter the correct word, it does not register it as being correct.

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


Lost(){
    printf("You lost");
}

Won(){
    printf("You won");
}

int main(void)
{
    char HiddenMessage[7] = "Cameron";
    char Guess[50] = "";
    int Tries = 0;
    int MaxTries = 5;

    while(Tries != MaxTries && strcmp(HiddenMessage, Guess) != 0){
        printf("Guess the secret word:");
        scanf("%s", Guess);
        Tries++;
        if (Tries == MaxTries){
            Lost();
        }
        else if (strcmp(HiddenMessage, Guess) == 0){
            Won();
        }
    }
    return 0;
}

How do I structure this to ensure that if a Guess occurs correctly within the number of tries it runs Won();

danblack
  • 12,130
  • 2
  • 22
  • 41
  • 2
    __Not work__ how? – u_mulder Jan 01 '20 at 09:16
  • 1
    The question lacks a [mre], i.e. it does not tell *how* the code does not work. – Antti Haapala -- Слава Україні Jan 01 '20 at 09:32
  • 1
    `strcmp` compares strings. `HiddenMessage`, defined as `char HiddenMessage[7] = "Cameron";` does not represent a string since it is not terminated by null character (there is no room in the array for the null character). You can correct it by defining the array as `char HiddenMessage[8] = "Cameron";` or `char HiddenMessage[] = "Cameron";` – Lxer Lx Jan 01 '20 at 13:04
  • `scanf("%s", Guess);` cannot be used safely. It should have been `scanf ("%49s%*[^\n]", guess);` – Lxer Lx Jan 01 '20 at 13:37

5 Answers5

2

You should have received a warning about:

char HiddenMessage[7] = "Cameron";

if your compiler was doing its job properly :-)

Seven characters is not enough to store Cameron and the end-of-string marker. Try it again with:

char HiddenMessage[] = "Cameron";

This will make sure enough characters are set aside for the full string.


If you're interested, this is covered in C11 6.7.9 Initialization /14 (emphasis added):

An array of character type may be initialized by a character string literal or UTF−8 string literal, optionally enclosed in braces. Successive bytes of the string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array.

So what your code will give you is the sequence of characters { 'C', 'a', 'm', 'e', 'r', 'o', 'n' } but with no \0 at the end to make it a C string. That means strcmp cannot be guaranteed to give you the results you want (it will possibly run off the end of the array during the comparison).


And, as an aside, scanf with the unbounded %s format specifier is considered rather dangerous in non-trivial code, since there's no way to protect against buffer overflow.

If you're looking for a fairly bullet-proof user input function that can detect and mitigate this, have a look at this earlier answer of mine.

It allows prompting, prevents buffer overflow, properly handles line-based user input (no half lines delivered to user) and so on.

It's what I use for simple console applications (where no better user input method is available).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Thank you that worked. So that means something is added on to the end so even though its 7 characters in message theres one more character added on? Also is there an advantage of specifying the amount of characters in my string rather then just leaving it empty like HiddenMessage[]. – Profaneprayer Jan 01 '20 at 09:28
  • @Profaneprayer: yes, a C string is defined as the characters that compose it *plus* the `\0` terminator at the end. The only real advantage of specifying length is when you need it to be larger, like `char goodLookingPerson[50] = "paxdiablo";` where it will pad out the rest of the string in case you want to make the name larger later on. – paxdiablo Jan 01 '20 at 09:30
  • @paxdiablo `char HiddenMessage[] = "Cameron";` - I did not know that this statement does also set the end-of-string marker. Is this really so? – RobertS supports Monica Cellio Jan 01 '20 at 10:28
  • 1
    @RobertS, yes. It allocates enough space for the entire string literal (including terminator) and copies all the characters in (again, including terminator). – paxdiablo Jan 01 '20 at 10:30
  • @paxdiablo Thank you very much. Would it also set NUL in the array if I would provide a field more like: `char HiddenMessage[8] = "Cameron";`? – RobertS supports Monica Cellio Jan 01 '20 at 10:36
  • 1
    @RobertS: yes. As per the quote from C11, it always copies the terminator if there is room for it. Since `Cameron` is only seven characters, that is the case. – paxdiablo Jan 01 '20 at 10:42
  • @paxdiablo Thank you very much, pax. This was worth for me to make a own question for it https://stackoverflow.com/questions/59551879/is-nul-set-automatically-if-i-provide-an-extra-element-for-it-in-the-declaratio and I have tested the things while you answered. – RobertS supports Monica Cellio Jan 01 '20 at 10:59
2

There are, essentially, four errors in your code:

  1. As paxdiablo has stated, your HiddenMessage array is not large enough to hold the text you have given plus the nul terminator.
  2. You need to define (or at least declare) your Lost and Won functions before you use them.
  3. The definitions of these two functions are incorrect, as you have not specified a return type: void is suitable for functions that don't return anything.
  4. You must include stdio.h to get the definitions of printf and scanf

Here's a fixed (and working) version of your code with the corrections made:

#include <stdlib.h>
#include <stdio.h>  // MUST include this for "printf" and "scanf" functions

// You can keep these DEFINITIONS after "main" if you like, but then you'll need to 
// have "pre-declarations" of them (the two commented lines immediately below).
// void Lost();
// void Won();

void Lost() {
    printf("You lost");
}

void Won() {
    printf("You won");
}

int main()
{
    char HiddenMessage[8] = "Cameron"; // Added enough space for the nul terminator
    char Guess[50] = "";
    int Tries = 0;
    int MaxTries = 5;

    while (Tries != MaxTries && strcmp(HiddenMessage, Guess) != 0) {
        printf("Guess the secret word:");
        scanf("%s", Guess);
        Tries++;
        if (Tries == MaxTries) {
            Lost();
        }
        else if (strcmp(HiddenMessage, Guess) == 0) {
            Won();
        }
    }

    return 0;
}

Feel free to ask for further clarification and/or explanation.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Good catch. Might also be a good idea to `break` after calling `Won` since it will otherwise keep nagging you for the answer :-) – paxdiablo Jan 01 '20 at 09:33
  • 1
    @paxdiablo the `strcmp(HiddenMessage, Guess) != 0` will end the `while` loop when the answer is correct. – Adrian Mole Jan 01 '20 at 09:35
  • @Profaneprayer If you don't forward-declare the functions, they will be assumed to return an `int`. If you define them later so they do, that will work (but not recommended, IMHO). Also, as `printf` and `scanf` *do* return an `int`, leaving out the `#include ` won't break your code ***in this instance*** - but it would if you had other calls to either of them with different arguments. – Adrian Mole Jan 01 '20 at 09:39
  • Regarding defining the functions before its use and not specifying a return type, are these things that are mainly for good form? Like could not doing those things cause a program to not work? Obviously especially since im new to this I want to have a good base and ill try to do things with good form and habits but im just curious because in this case it didnt affect my program(or atleast not that i saw). – Profaneprayer Jan 01 '20 at 09:42
  • @Profaneprayer It ***is*** good form but not doing can cause major problems in more complex programs. Make it a habit to *never* use undeclared functions. – Adrian Mole Jan 01 '20 at 09:44
2

Another significant problem is your failure to validate any of the input. Any time (means ANY time) you take input, you must validate the input succeeds before relying on the data. Failure to validate invites undefined behavior.

Validations are simple, if you read input with a function check the return to validate you got all the input you were expecting. For example:

        printf ("  try no. %d - Guess the seceret word: ", tries + 1);
        if (scanf ("%49s", guess) != 1) {   /* read/validate word */
            fputs ("(error: EOF encountered reading guess.)\n", stderr);
            return 1;
        }

(note: the use of the field-width modifier to limit the number of characters that can be read. Failing to control the number of characters than can be input will read to attempt to write beyond the bounds of your array if the user enters 50 (or more) characters.)

Your logic is a bit awkward. All you need to do is loop continually until the user either wins the game or exhausts the number of tries available. Rearranging your logic slightly, you could do something like:

(Edited to empty-stdin)

    printf ("HANGMAN - you have %d tries to guess the word.\n\n", MAXTRIES);

    for (;;) {  /* loop continually until win or tries exhausted */
        printf ("  try no. %d - Guess the seceret word: ", tries + 1);
        if (scanf ("%49s", guess) != 1) {   /* read/validate word */
            fputs ("(error: EOF encountered reading guess.)\n", stderr);
            return 1;
        }
        if (strcmp (hiddenmsg, guess) == 0) /* compare hiddenmsg & guess */
            break;                          /* if matched, break loop */
        if (++tries == MAXTRIES) {          /* test if all tries exhausted */
            fputs ("\nyou have exhausted all guesses - you lost :(\n", stderr);
            return 1;
        }
        /* empty stdin */
        for (int c = getchar(); c != EOF && c != '\n'; c = getchar()) {}
    }
    puts ("\ncongratulations - you won.\n");  /* issue congrats & exit */

Putting it altogether in an example, you could do:

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

#define MAXTRIES  5
#define MAXGUESS 50

int main (void) {

    char hiddenmsg[] = "Cameron",
        guess[MAXGUESS] = "";
    int tries = 0;

    printf ("HANGMAN - you have %d tries to guess the word.\n\n", MAXTRIES);

    for (;;) {  /* loop continually until win or tries exhausted */
        printf ("  try no. %d - Guess the seceret word: ", tries + 1);
        if (scanf ("%49s", guess) != 1) {   /* read/validate word */
            fputs ("(error: EOF encountered reading guess.)\n", stderr);
            return 1;
        }
        if (strcmp (hiddenmsg, guess) == 0) /* compare hiddenmsg & guess */
            break;                          /* if matched, break loop */
        if (++tries == MAXTRIES) {          /* test if all tries exhausted */
            fputs ("\nyou have exhausted all guesses - you lost :(\n", stderr);
            return 1;
        }
        /* empty stdin */
        for (int c = getchar(); c != EOF && c != '\n'; c = getchar()) {}
    }
    puts ("\ncongratulations - you won.\n");  /* issue congrats & exit */
}

Example Use/Output

Typical try and fail game:

$ ./bin/hangman
HANGMAN - you have 5 tries to guess the word.

  try no. 1 - Guess the seceret word: Alligator
  try no. 2 - Guess the seceret word: Campers
  try no. 3 - Guess the seceret word: Scam
  try no. 4 - Guess the seceret word: Horses
  try no. 5 - Guess the seceret word: Bananna

you have exhausted all guesses - you lost :(

A lucky win:

$ ./bin/hangman
HANGMAN - you have 5 tries to guess the word.

  try no. 1 - Guess the seceret word: Lightning
  try no. 2 - Guess the seceret word: Thunder
  try no. 3 - Guess the seceret word: Geroge
  try no. 4 - Guess the seceret word: Cameron

congratulations - you won.

(Testcase After Edit w/Example from Comment)

$./bin/hangman
HANGMAN - you have 5 tries to guess the word.

  try no. 1 - Guess the seceret word: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCameron
  try no. 2 - Guess the seceret word: foo
  try no. 3 - Guess the seceret word: Cameron

congratulations - you won.

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

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • What happens if the input is `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCameron` ? (there are 49 `x`s) – Lxer Lx Jan 01 '20 at 13:27
  • Well, that obviously will not match `"Cameron"`, for that try, `"Cameron"` is left unread in `stdin` and it will match on the next try if `tries` is currently 4 or less. You can empty- stdin after each try. I'll add a short example. – David C. Rankin Jan 02 '20 at 00:57
  • I think the loop to empty `stdin` is not necessary. `scanf ("%49s%*[^\n]", guess)` would consume characters until (but not including) the `'\n'` character. The `'\n'` will remain on the `stdin` but that doesn't matter for this question. – Lxer Lx Jan 02 '20 at 01:41
  • That would. It's just another way of approaching it. There is nothing wrong with using the *assignment suppression* operator `'*'`. In each case you are just scanning forward from the first of a whitespace or the 50th character to the end of line and discarding characters. (personally, the correct way to handle it is to declare a sufficiently sized buffer and read with `fgets()` and trim the trailing `'\n'` with `guess[strcspn (guess, "\n")] = 0;` if the length is less than 50, or `memcpy` and nul-terminate at 50) – David C. Rankin Jan 02 '20 at 01:49
1

As strcmp compares the difference between each character till the end of string that is \0 So your buffer needs to be large enough to hold your string i.e. char HiddenMessage[8] = "Cameron";

PS you can avoid a lot of lengthy code

#include <stdlib.h>

int main()
{
    char HiddenMessage[8] = "Cameron";
    char Guess[50] = "";
    int Tries = 0;
    int MaxTries = 5;

    while (Tries != MaxTries)
    {
        printf("Guess the secret word:");
        scanf("%s", Guess);
        Tries++;
        if (strcmp(HiddenMessage, Guess) == 0)
        {
            Won();
            return 0;
        }
    }
    Lost();
    return 0;
}

void Lost()
{
    printf("You lost");
}

void Won()
{
    printf("You won");
}
Glitchfix
  • 21
  • 2
1

There are multiple problems with your code. The size of HiddenMessage reported by @paxdiablo is just one of them.

  • You should use fgets instead of scanf because scanf won't consume the newline. You will be stuck on the second iteration.

  • You increment Tries and test it against MaxTries before testing if the guess was correct. As a consequence the program will tell that the user lost before testing the validity of the last guess.

  • Once the user guessed the word and won, you must break from the while loop and terminate the program. With your code, after the program reported that the user won, it will ask for another guess if it wasn't the last guess.

chmike
  • 20,922
  • 21
  • 83
  • 106
  • @AdrianMole sorry, didn't see you answer – chmike Jan 01 '20 at 09:39
  • First bullet point: I think `scanf` skips white space before reading a string, does it not? Don't know of the top of my head since I don't use `scanf`, *especially* since it's so dangerous with `%s` :-) – paxdiablo Jan 01 '20 at 09:39
  • @paxdiablo I also avoid using `scanf` unless I decode data on a single line. When I do I also check the returned value. So I'm not sure this point is correct. I only have an iPad and can't verify it. From what I have read, `scanf` is source of problems – chmike Jan 01 '20 at 09:43