2
\$\begingroup\$

I have been working on this hangman game for a little while trying to get familiar with C programming. The game seems to work fine but I am looking for some pointers on my code. Are the global variables ok or should I have made them local? Should I have used more functions to make the code more readable? Is my commenting style acceptable or should I stay away from the big box style comments?

/////////////////////////////////////////////////
/// Title: Hangman
/// Author: 
/// Date: 4/19/2019
/// Description: Hangman Game
/////////////////////////////////////////////////


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

//Global Strings
char word [50];
char guessed_letters[20];
char user_guess[] = "";
char blank[1] = "-";

//Global Integers
int random_number;
int word_len;
int user_input;
int attempts = 10;

//Function Declarations
void start_game();
void get_input();
void print_blanks();
void draw_platform();
void get_word();

int main(void)
{
    //Game Loop
    while(1)
    {
        start_game();

        while(attempts > 0)
        {
            system("cls");

            //If they have guessed all the letters they win
            if(strlen(guessed_letters) == word_len - 1)
            {
                print_blanks();
                break;
            }
            //Else, decr attempts and try again
            else
            {
                printf("Attempts Remaining: %i\n", attempts);
                print_blanks();
                get_input();
            }
        }

        system("cls");

        //If they won
        if(attempts > 0)
        {
            print_blanks();
            printf("You Won! Play again?\n");
        }
        //If they lost
        else
        {
            draw_platform();
            printf("You Lost! The word was %s, Play again?\n", word);
        }

        scanf("%i", &user_input);

        switch(user_input)
        {
            case 0:
                return 0;
            default:
                continue;
        }
    }
}

void start_game()
{
    //Initializes Game
    get_word();
    word_len = strlen(word);
    memset(guessed_letters, 0, sizeof guessed_letters);
    attempts = 10;
}

void get_input()
{
    //Gets guess from user and checks
    //To see if that letter is in the word

    int i;
    int letter_hit = 0; //Used to tell if the guess letter is in the word

    printf("\nYour guess: \n");
    scanf(" %c", user_guess);

    for(i=0; i < word_len; i++)
    {
        if(user_guess[0] == word[i])
        {
            guessed_letters[i] = user_guess[0];
            letter_hit ++;
        }
    }

    if(letter_hit > 0)
    {
        return;
    }
    else
    {
        attempts --;
    }
}

void print_blanks()
{
    /////////////////////////////////////////////////
    /// Prints out a number of blanks equal to the
    /// Length of the word
    /// Then fills the blanks with the guessed letters
    /////////////////////////////////////////////////

    int i, j;

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

    for(j=0; j<word_len - 1; j++)
    {
        printf("%s", blank);
        printf(" ");
    }
    printf("\n");
}

void draw_platform()
{
    /////////////////////////////////////////////////
    /// Draws a new segment onto
    /// The platform every time
    /// The user gets a wrong guess
    /////////////////////////////////////////////////

    char *platform[]={

                     "      ===\n",

                     "        |\n"
                     "        |\n"
                     "        |\n"
                     "       ===\n",

                     "   =====|\n"
                     "        |\n"
                     "        |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "        |\n"
                     "        |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "  O     |\n"
                     "        |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "  O     |\n"
                     "  |     |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "  O     |\n"
                     "  |-    |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "  O     |\n"
                     " -|-    |\n"
                     "        |\n"
                     "       ===\n",

                     "  |=====|\n"
                     "  O     |\n"
                     " -|-    |\n"
                     "  |     |\n"
                     "       ===\n",

                     "   |=====|\n"
                     "   O     |\n"
                     "  -|-    |\n"
                     "  //     |\n"
                     "       ===\n"

    };

    switch(attempts)
    {
    case 9:
        printf("\n\n%s\n", platform[0]);
        break;
    case 8:
        printf("\n\n%s\n", platform[1]);
        break;
    case 7:
        printf("\n\n%s\n", platform[2]);
        break;
    case 6:
        printf("\n\n%s\n", platform[3]);
        break;
    case 5:
        printf("\n\n%s\n", platform[4]);
        break;
    case 4:
        printf("\n\n%s\n", platform[5]);
        break;
    case 3:
        printf("\n\n%s\n", platform[6]);
        break;
    case 2:
        printf("\n\n%s\n", platform[7]);
        break;
    case 1:
        printf("\n\n%s\n", platform[8]);
        break;
    case 0:
        printf("\n\n%s\n", platform[9]);
        break;
    }
}

void get_word()
{
    /////////////////////////////////////////////////
    /// Scans a file to get the total number of lines
    /// The line total is then used as a max range
    /// For the random number
    /// The word that is on the random line is the word
    /// That will be used for the game
    /////////////////////////////////////////////////

    FILE *fp;
    int line_number = 0;
    char current_word[50];

    fp = fopen("dictionary.txt","r");

    if(fp == NULL)
    {
        perror("Error in opening file");
    }

    //While not end of file, incr line number
    while(fgets(current_word, 50, fp) != NULL)
    {
        line_number++;
    }

    random_number = rand() % line_number;

    //Start from top of file
    rewind(fp);

    //Goes to whatever line the random number equals to find the
    //Random word
    for(line_number = 0; line_number != random_number; line_number++)
    {
        fgets(current_word, 50, fp);
    }

    strcpy(word, current_word);
    fclose(fp);
}
\$\endgroup\$
  • \$\begingroup\$ Your switch statement can be simplified to just printf("\n\n%s\n", platform[9 - attempts]); \$\endgroup\$ – Spikatrix Apr 22 at 11:22
3
\$\begingroup\$

Typo or Bug
Any time a array of characters is to be used as a string the length of the array MUST be 1 + the expected length of the string to allow for the NULL value that terminates the string.

char blank[1] = "-";

Code such the line above can cause the program to either terminate abnormally or cause all kinds of interesting problems.

Global Variables
Some people say global variables are evil and never use them, they are not quite correct, but in this case there are multiple ways around using global variables. Some of the functions could return values rather than void, variables can be passed into some functions by either by value or by reference.

Pass By Value means that a copy of the variable is used within the function and any changes made to the variable within the function won't change the value in the rest of the program. Pass By Reference means that the address of the variable is passed into the function and any changes made to the variable will change in the rest of the program. In C the contents of an array all always pass by reference because an array is a memory address.

Global variables make programs much harder to debug because it is hard to find where the variable is changed within the program, this is true even when the program is a single file such as this one, but with multiple source files it is much harder.

If you declared any of these global variable in multiple files the linking phase would report multiple definitions and then quit. If a variable needs to be global within a single file and not elsewhere it should be declared as a static variable.

It is best to limit the scope of a variable to just where it is needed. If a variable is only used in a for loop, declare it in that for loop. The variable random_number should be declared in the get_word() function because it is only referenced in that function.

It would be best if get_word returned a character array rather than setting a global variable.

Complexity
There is a good start on creating functions in the program, but main() is still too complex. In large programs main is used to set up for processing, call the processing function(s) and clean up. It might be a good practice to always limit the code in main() to this functionality. Perhaps a function called run_game() could contain most of the code in main() including the call to start_game().

Performance
The program might be faster if it read all the strings into an array of strings (char **words). The random number can then be used as an index into the array of strings rather than rereading some or all of the file. Multiple passes through a file are time consuming and should be avoided when possible. The functions fopen(), fclose(), fgets() and rewind() are all system calls and cause the program to be swapped out when they are called.

\$\endgroup\$
  • \$\begingroup\$ "Code such as line 23" Please mention the line as there are no line numbers here \$\endgroup\$ – Spikatrix Apr 22 at 11:18
  • \$\begingroup\$ Also, you should mention that char blank[1] = "-"; should be char blank = '-'; as it doesn't make sense to have a array to store a character. Note that char blank[1] = "-"; is valid though. Just the NUL-terminator won't be appended to the array which causes UB when it gets printed using %s. \$\endgroup\$ – Spikatrix Apr 22 at 11:25
  • \$\begingroup\$ @Spikatrix Changed line 23 to the line above. My view is that we are here to help the user not get UB. \$\endgroup\$ – pacmaninbw Apr 22 at 12:24
  • \$\begingroup\$ Kinda misleading. That line is perfectly valid as I said in my previous comment. UB occurs only when the user prints it using %s in printf. \$\endgroup\$ – Spikatrix Apr 22 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.