Hangman game with C functions

Asked

Viewed 12,161 times

4

I’m trying to make a game of hanging in C to go learning better, even because I need to learn more functions such.
While the game is running, I wanted when the user put a letter, it would appear side by side. And when it was wrong too. Example:

Word I want you to hit: cadeira

On the screen will be _ _ _ _ _ _ _
If the user type c, I want the c replace the first _. And if he types W (not in the word), I want it to appear below the _ _ _ _ _ _ a message, like:

Erros:
w f g (que são letras que não tem na palavra)

My code was also giving error in fgets that as I’ve been explained, he puts a \n in front of the string, but I did the Trim in the code to take the \n. The first times I ran with Trim in the code he took the \n and everything was perfect, on each other’s side. But now that I try to run the code it is the same as it was before. So:

_
_
_
_

Can someone tell me why he gets like this?

Follows the code:

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

char palavra[20];
char forca[20];
char tentativa;
int chances = 5;
int letras, i, j=0, cont;

int start(void)
{

    printf("\nDigite uma palavra: ");
    fgets(palavra, 20, stdin);

    trimEnd(palavra);

    strcpy(forca, palavra);
    letras = strlen(forca);

    for (i=0; i<letras; i++)
    {

        forca[i]=  '_';

    }
}

int jogo(void)
{

    while(chances > 0)
    {

        __fpurge(stdin);
        printf("\nChances: %d - palavras tem %d letras\n\n", chances, letras);

        for (i=0; i<letras; i++)
        {

            printf("\n%c ", forca[i]);

        }

        printf("\n\nDigite uma letra: ");
        scanf("%c", &tentativa);


        cont = 0;

        for (i=0; i<letras; i++)
        {

            if (palavra[i] == tentativa)
            {

                forca[i] == tentativa;
                cont = cont + 1;
                j++;

            }

        }

        if (cont <= 0)
        {

            chances = chances - 1;

        }

        system("clear");

        if (j == letras)
        {

            break;
        }

    }
}

int resultado(void)
{

    if(chances == 0)
    {

        __fpurge(stdin);
        printf("\nChances: %d - palavra tem %d letras\n\n", chances, letras);
        puts(forca);
        printf("\nVocê perdeu. \nA palavra era: ");
        puts(palavra);


    }
    else
    {

        printf("\nParabens, voce acertou a palavra ", chances, letras);
        puts(palavra);

    }

}

void trimEnd(char *str) { //Tira o \n que o fgets lê junto com a variavel pra ir para a ultima linha
    char *end = str + strlen(str) - 1;
    while (end > str && isspace(*end)) end--;
    end++;
    *end = 0;
}

int main()
{

    start();
    system("clear");
    jogo();
    resultado();

    return(0);
}

NOTE: I just made the program ask for the word (which doesn’t make much sense if I want the user to type) because I’m solving this problem first and when everything is ok, then I’ll look for how to randomly draw any word I have inside an array, maybe...

Thanks in advance.

1 answer

10


I gave him a general review of his code and he went like this:

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

char palavra[20];
char forca[20];
char erros[27];

#if defined(__MINGW32__) || defined(_MSC_VER)
#define limpar_input() fflush(stdin)
#define limpar_tela() system("cls")
#else
#include <stdio_ext.h>
#define limpar_input() __fpurge(stdin)
#define limpar_tela() system("clear")
#endif

void limparBuffer(char *buf, int tamanho) {
    int i = 0;
    for (i = 0; i < tamanho; i++) {
        buf[i] = 0;
    }
}

void trimEnd(char *str) { //Tira o \n que o fgets lê junto com a variavel pra ir para a ultima linha
    int p;
    for (p = strlen(str); isspace(str[p]); p--) {
        str[p] = 0;
    }
}

int ehLetra(char c) {
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

char maiuscula(char c) {
    return (c >= 'a' && c <= 'z') ? (c - 32) : c;
}

void start(void) {
    limparBuffer(palavra, 20);
    limparBuffer(forca, 20);
    limparBuffer(erros, 27);

    printf("\nDigite uma palavra: ");
    fgets(palavra, 20, stdin);
    limpar_input();

    trimEnd(palavra);

    int i;
    for (i = 0; palavra[i] != 0; i++) {
        char c = palavra[i];
        forca[i] = ehLetra(c) ? '_' : c;
    }
}

int jogo(void) {
    char tentativa;
    int chances = 5;

    int letras = 0;
    int i;
    for (i = 0; palavra[i] != 0; i++) {
        if (ehLetra(palavra[i])) letras++;
    }

    while (chances > 0) {
        limpar_tela();
        printf("\nChances: %d - palavras tem %d letras\n\n", chances, letras);

        printf("%s", forca);
        if (strlen(erros) > 0) {
            printf("\nErros: %s", erros);
        }

        printf("\n\nDigite uma letra: ");
        scanf("%c", &tentativa);
        limpar_input();

        // Se o usuário digitou algo que não é letra, apenas insiste sem queimar uma chance.
        if (!ehLetra(tentativa)) continue;

        // Se o usuário digitou algo que ele já tentou antes (seja errando ou acertando), apenas insiste sem queimar uma chance.
        int jaTentou = 0;
        for (i = 0; erros[i] != 0; i++) {
            if (erros[i] == maiuscula(tentativa)) {
                jaTentou = 1;
                break;
            }
        }
        if (jaTentou) continue;
        for (i = 0; forca[i] != 0; i++) {
            if (maiuscula(forca[i]) == maiuscula(tentativa)) {
                jaTentou = 1;
                break;
            }
        }
        if (jaTentou) continue;

        int ganhou = 1;
        int achou = 0;
        for (i = 0; palavra[i] != 0; i++) {
            if (!ehLetra(palavra[i])) continue;
            if (forca[i] == '_') {
                if (maiuscula(palavra[i]) == maiuscula(tentativa)) {
                    forca[i] = palavra[i];
                    achou = 1;
                } else {
                    ganhou = 0;
                }
            }
        }

        if (ganhou) {
            return 1; // Ou seja, ganhou.
        }

        if (!achou) {
            chances--;
            erros[strlen(erros)] = maiuscula(tentativa);
        }
    }
    return 0; // Ou seja, perdeu.
}

void mostrarResultado(int resultado) {
    limpar_input();
    if (resultado == 0) {
        printf("\nVoce perdeu. \nA palavra era %s", palavra);
    } else {
        printf("\nParabens, voce acertou a palavra %s ", palavra);
    }
}

int main() {
    start();
    int resultado = jogo();
    mostrarResultado(resultado);
    return 0;
}

The changes I made were:

  1. I am developing in a windows environment. Because of this, for me instead of using __fpurge, should I wear fflush and instead of system("clear"), use system("cls"). To solve this problem, I use the following block:

    #if defined(__MINGW32__) || defined(_MSC_VER)
    #define limpar_input() fflush(stdin)
    #define limpar_tela() system("cls")
    #else
    #include <stdio_ext.h>
    #define limpar_input() __fpurge(stdin)
    #define limpar_tela() system("clear")
    #endif
    

    So that from there I have the macros limpar_input and limpar_tela defined for the correct call independent of the compiler environment.

    As mentioned in a comment by Pablo, use the fflush(stdin) is in general undefined behavior. However, in windows (it does not have the __fpurge), it is defined. That #if also ensures that the fflush(stdin) will not be used in a non-windows environment.

  2. This snippet of code doesn’t do what you want it to:

    forca[i] == tentativa;
    

    Note that you used the same double instead of the same single!

  3. His logic had some serious problems: It was possible to use several times the same letter without burning attempts, messing with the j and can win without finding all letters; uppercase letters were considered different from lowercase and words containing symbols other than letters (in particular _) could make the game not playable. In my new logic, these problems are solved.

  4. Avoid using printf with most unused parameters, such as here:

    printf("\nParabens, voce acertou a palavra ", chances, letras);
    
  5. Try to declare variables in the smallest possible scope. Global variables should be avoided whenever possible. I was able to transform tentativa, i, j, chances and letras in local variables. The variable cont was eliminated.

  6. Do not declare a function with the return type int if you do not return anything from it. In this case use void.

  7. There is nothing wrong with using pointer arithmetic, but it is a good thing to be avoided, since it is one of the things that make it very easy to do stupid things in C. Because of this I rewrote its function trimEnd not to use pointer arithmetic.

  8. Is it better to have to clean something before using or cleaning after using? Well, we can say that is worth the saying "Got dirty, clean". Because of that, I put the limpar_input (which is the __fpurge) shortly after the fgets and the scanf, and not just before. This way the code worries about cleaning the input buffer after it is used, and not before. On the other hand, in cases where it is not possible or not easy to do the cleaning after use, then it is convenient to do it before use, and that is why I put the limpar_tela (which is the system("clear")) before the first printf and used the function limparBuffer I created to clean up palavra, forca and erros before its uses.

  9. For the code to ask only the user for letters, I use the function ehLetra. Thus, if the word chosen is cata-vento or something else that contains some symbols that are not letters, this will not cause frustration or problems for the player.

    When checking the typed letter, the if (!ehLetra(palavra[i])) continue; causes special symbols to be ignored.

    The forca[i] = ehLetra(c) ? '_' : c; makes the special symbols appear already filled when the game starts.

    The if (ehLetra(palavra[i])) letras++; ensures that only the letters of the word typed will be counted.

  10. To avoid frustration, the function maiuscula is used to convert what the user type to uppercase. The purpose is that upper and lower case letters can be accepted without differentiation, which is achieved once the verification is done with if (maiuscula(palavra[i]) == maiuscula(tentativa)). On the other hand, how the assignment occurs with forca[i] = palavra[i];, it is ensured that the spelling displayed will be the same as the typed word, regardless of whether the user tried an uppercase or lowercase letter.

  11. Instead of checking whether the player won or burned a chance using letter counting, I preferred to use the flags achou and ganhou. At each iteration, they are initially defined as false and true, respectively. And then, for every letter not revealed in the answer, I do the following:

    If the letter typed by the user matches an undisclosed letter, then this demonstrates that he discovered at least one new letter that had not been found before, and therefore achou passes to real in that case.

    On the other hand, if the typed letter does not match the undisclosed letter, then this demonstrates that there is at least one letter that has not yet been discovered, and therefore the ganhou becomes false in this case.

    At the end of the loop, if ganhou is true, so I already declare that the player has won. Otherwise (ganhou is false) and achou is also false, so it is because he chose a wrong letter, and then burn an attempt of it and also add the letter tempted (and incorrect) in the array/string erros.

  12. Using loops to traverse strings like this:

    for (i = 0; palavra[i] != 0; i++)
    

    I can avoid having to worry about the size of strings in many places.

  13. To prevent the user from burning attempts unnecessarily by typing something that is not a letter, or that is a letter that he has tried, I use this:

        // Se o usuário digitou algo que não é letra, apenas insiste sem queimar uma chance.
        if (!ehLetra(tentativa)) continue;
    
        // Se o usuário digitou algo que ele já tentou antes (seja errando ou acertando), apenas insiste sem queimar uma chance.
        int jaTentou = 0;
        for (i = 0; erros[i] != 0; i++) {
            if (erros[i] == maiuscula(tentativa)) {
                jaTentou = 1;
                break;
            }
        }
        if (jaTentou) continue;
        for (i = 0; forca[i] != 0; i++) {
            if (maiuscula(forca[i]) == maiuscula(tentativa)) {
                jaTentou = 1;
                break;
            }
        }
        if (jaTentou) continue;
    

    The first loop checks if the letter is some previous wrong attempt and the second if it is a previous correct attempt (it is wrong already tried before if it is somewhere within erros and correct already tried before if you are somewhere forca).

  14. The reason for their _ appear one beneath the other was here:

        for (i=0; i<letras; i++)
        {
    
            printf("\n%c ", forca[i]);
        }
    

    What makes them appear underneath each other is \n. In my code I simply put this:

        printf("%s", forca);
    

    But if you prefer, you can use this:

        for (i = 0; forca[i] != 0; i++) {
            printf("%c ", forca[i]);
        }
    
  • Beware! You used fflush in an input buffer. That’s undefined behavior.

  • 2

    @That’s why he’s protected by #ifdef. In windows it is defined: https://msdn.microsoft.com/en-us/library/9yky46tz.aspx - See the part that says // You must flush the input buffer before using gets. - // fflush on input stream is an extension to the C standard. Really, use fflush(stdin) is a horrible thing, but there’s just no portable way to clean stdin (and yes, I researched about it). If windows had the __fpurge would be a good start, but he doesn’t have it.

  • 1

    I didn’t know about this one. It explains a lot. Anyway, I usually use this: http://stackoverflow.com/a/26081123/1796236

  • 3

    Victor Stafusa, thank you so much. I am beginner level even and understood everything you explained. Sure, I’ll read some more to improve my understanding... and try to do according to your explanations. Thank you very much. ;)

  • 1

    When I start reading an answer and I think, "Wow, this guy tried"; "Wow, this guy knows"; "Wow, I couldn’t write an answer like that"... in the end, it’s always written: "Victor Stafusa"... Wtf!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.