The Algorithms logo
The Algorithms
AboutDonate

Hangman

/**
 * @file
 * @brief C implementation of [Hangman Game](https://en.wikipedia.org/wiki/Hangman_(game))
 * @details
 * Simple, readable version of hangman.
 * Changed graphic to duck instead of traditional stick figure (same number of guesses).
 * @author [AtlantaEmrys2002](https://github.com/AtlantaEmrys2002)
*/

#include <ctype.h> /// for main() - tolower()
#include <stdio.h> /// for main(), new_word(), new_guess(), won() - I/O operations
#include <stdlib.h> /// for all functions - exit(), rand() and file functions
#include <string.h> /// for main() - for string operations strlen, strchr, strcpy
#include <time.h> /// for new_game() - used with srand() for declaring new game instance

/*
 * @brief game_instance structure that holds current state of game
 */
struct game_instance{

    char current_word[30]; ///< word to be guessed by player
    char hidden[30]; ///< hidden version of word that is displayed to player
    int size; ///< size of word
    int incorrect; ///< number of incorrect guesses
    char guesses[25]; ///< previous guesses
    int guesses_size; ///< size of guesses array

};

// function prototypes
struct game_instance new_game(void); // creates a new game
int new_guess(char, const char guesses[], int size); // checks if player has already played letter
int in_word(char, const char word[], unsigned int size); // checks if letter is in word
void picture(int score); // outputs image of duck (instead of hang man)
void won(const char word[], int score); // checks if player has won or lost

/**
 * @brief Main Function
 * @returns 0 on exit
 */
int main() {

    struct game_instance game = new_game(); // new game created
    char guess; // current letter guessed by player

    // main loop - asks player for guesses
    while ((strchr(game.hidden, '_') != NULL) && game.incorrect <= 12) {
        do {
            printf("\n****************************\n");
            printf("Your word: ");

            for (int i = 0; i < game.size; i++) {
                printf("%c ", game.hidden[i]);
            }

            if (game.guesses_size > 0) {
                printf("\nSo far, you have guessed: ");
                for (int i = 0; i < game.guesses_size; i++) {
                    printf("%c ", game.guesses[i]);
                }
            }

            printf("\nYou have %d guesses left.", (12 - game.incorrect));
            printf("\nPlease enter a letter: ");
            scanf(" %c", &guess);
            guess = tolower(guess);

        } while (new_guess(guess, game.guesses, game.guesses_size) != -1);

        game.guesses[game.guesses_size] = guess; // adds new letter to guesses array
        game.guesses_size++; // updates size of guesses array

        if (in_word(guess, game.current_word, game.size) == 1) {
            printf("That letter is in the word!");
            for (int i = 0; i < game.size; i++) {
                if ((game.current_word[i]) == guess) {
                    game.hidden[i] = guess;
                }
            }
        } else {
            printf("That letter is not in the word.\n");
            (game.incorrect)++;
        }
        picture(game.incorrect);
    }

    won(game.current_word, game.incorrect);
    return 0;
}

/**
 * @brief checks if letter has been guessed before
 * @param new_guess letter that has been guessed by player
 * @param guesses array of player's previous guesses
 * @param size size of guesses[] array
 * @returns 1 if letter has been guessed before
 * @returns -1 if letter has not been guessed before
 */
int new_guess(char new_guess, const char guesses[], int size) {

    for (int j = 0; j < size; j++) {
        if (guesses[j] == new_guess) {
            printf("\nYou have already guessed that letter.");
            return 1;
        }
    }

    return -1;
}

/**
 * @brief checks if letter is in current word
 * @param letter letter guessed by player
 * @param word current word
 * @param size length of word
 * @returns 1 if letter is in word
 * @returns -1 if letter is not in word
 */
int in_word(char letter, const char word[], unsigned int size) {

    for (int i = 0; i < size; i++) {
        if ((word[i]) == letter) {
            return 1;
        }
    }

    return -1;
}

/**
 * @brief creates a new game - generates a random word and stores in global variable current_word
 * @returns current_game - a new game instance containing randomly selected word, its length and hidden version of word
 */
struct game_instance new_game() {

    char word[30]; // used throughout function

    FILE *fptr;
    fptr = fopen("games/words.txt", "r");

    if (fptr == NULL){
        fprintf(stderr, "File not found.\n");
        exit(EXIT_FAILURE);
    }

    // counts number of words in file - assumes each word on new line
    int line_number = 0;
    while (fgets(word, 30, fptr) != NULL) {
        line_number++;
    }

    rewind(fptr);

    // generates random number
    int random_num;
    srand(time(NULL));
    random_num = rand() % line_number;

    // selects randomly generated word
    int s = 0;
    while (s <= random_num){
        fgets(word, 30, fptr);
        s++;
    }

    // formats string correctly
    if (strchr(word, '\n') != NULL){
        word[strlen(word) - 1] = '\0';
    }

    fclose(fptr);

    // creates new game instance
    struct game_instance current_game;
    strcpy(current_game.current_word, word);
    current_game.size = strlen(word);
    for (int i = 0; i < (strlen(word)); i++) {
        current_game.hidden[i] = '_';
    }
    current_game.incorrect = 0;
    current_game.guesses_size = 0;

    return current_game;
}

/**
 * @brief checks if player has won or lost
 * @param word the word player has attempted to guess
 * @param score how many incorrect guesses player has made
 * @returns void
 */
void won(const char word[], int score) {
    if (score > 12) {
        printf("\nYou lost! The word was: %s.\n", word);
    }
    else {
        printf("\nYou won! You had %d guesses left.\n", (12 - score));
    }
}

/*
 * @brief gradually draws duck as player gets letters incorrect
 * @param score how many incorrect guesses player has made
 * @returns void
 */
void picture(int score) {

    switch(score) {

        case 12:
            printf("\n      _\n"
                   "  __( ' )> \n"
                   " \\_ < _ ) ");
            break;

        case 11:
            printf("\n      _\n"
                   "  __( ' )\n"
                   " \\_ < _ ) ");
            break;

        case 10:
            printf("\n      _\n"
                   "  __(   )\n"
                   " \\_ < _ ) ");
            break;

        case 9:
            printf("\n        \n"
                   "  __(   )\n"
                   " \\_ < _ ) ");
            break;

        case 8:
            printf("\n        \n"
                   "  __(    \n"
                   " \\_ < _ ) ");
            break;

        case 7:
            printf("\n        \n"
                   "  __     \n"
                   " \\_ < _ ) ");
            break;

        case 6:
            printf("\n        \n"
                   "  _      \n"
                   " \\_ < _ ) ");
            break;

        case 5:
            printf("\n        \n"
                   "  _      \n"
                   "   _ < _ ) ");
            break;

        case 4:
            printf("\n        \n"
                   "         \n"
                   "   _ < _ ) ");
            break;

        case 3:
            printf("\n        \n"
                   "         \n"
                   "     < _ ) ");
            break;

        case 2:
            printf("\n        \n"
                   "         \n"
                   "       _ ) ");
            break;

        case 1:
            printf("\n        \n"
                   "         \n"
                   "         ) ");
            break;

        case 0:
            break;

        default:
            printf("\n      _\n"
                   "  __( ' )> QUACK!\n"
                   " \\_ < _ ) ");
            break;
    }
}