Hello,

I've been going through K.N. King's C Programming: A Modern Approach and, after completing the first 10 chapters, I wanted to give my knowledge a try and write something I've always wanted to: a Hangman game. It turned out more of a "Mystery Word" game, as I haven't created the different phases of the hanging :)

This being said, I'm looking for some code review. I want to know how I could have written it better, within the bounds of my current knowledge (no pointers or strings, no file input/output, no use of databases, to name the few constrains which I think would have allowed me to do better). One thing I'm trying to do better is the handling of the output: I think I could make it easier to read and I'm also thinking of printing a header. Thanks in advance!

/**
 *  Author:     Cezar El-Nazli <cezar.elnazli@googlemail.com>
 *    Name:     Mystery Word
 *    Date:     Sat, 31st July 2010 - Wed, 4th August 2010
 * Purpose:     This is a very minimalist "Mystery Word" implementation
 */

/**************************************************************************
 *                          HEADER FILES                                  *
 **************************************************************************/

/**
 * The standard I/O header
 *
 * printf(): Print messages to the user
 *  scanf(): Get input from the user
 */
#include<stdio.h>

/**
 * The standard library header
 *
 * srand(): Seed the random number generator
 *  rand(): Generate a random number
 */
#include<stdlib.h>

/**
 * The time header
 *
 * time(): Set the time as the seed for the random number generator
 */
#include<time.h>

/**
 * The type-formatting header
 *
 * isalpha(): checks whether a given argument is a letter
 * tolower(): convert word to all-lowercase characters
 * toupper(): convert word to all-uppercase characters
 */
#include<ctype.h>

/**************************************************************************
 *                      CONSTANTS DEFINITION                              *
 **************************************************************************/

/**
 * The maximum number of bad guesses. There are two implementations of the
 * game: the one with a maximum of 6 bad guesses, having the first and last
 * characters of the word printed out, and the one with a maximum of 12 bad
 * guesses, without any words printed out. Currently, only the former is
 * implemented :-)
 */
#define MAX_BAD 6

/**
 * The number of words the game has "learnt". It must be equal to the
 * number of rows the dictionary[][] array has
 */
#define NUM_WORDS 5

/**
 * The maximum number of characters a word can have. 25 seems reasonable
 * enough, but feel free to adjust this to your own needs
 */
#define MAX_CHARS 25

/**************************************************************************
 *                          GLOBAL VARIABLES                              *
 **************************************************************************/

/**
 * char dictionary[][]: this multi-dimensional array of characters is "the
 *                      dictionary" of the game, containing all the words
 *                      which the game has "learnt", and from which it can
 *                      pick one to play the current game with. The first
 *                      dimension represents the maximum number of words the
 *                      dictionary can hold, a constant defined in the
 *                      above section. The second dimension, also a constant
 *                      defined above, represents the maximum number of
 *                      letters (or "characters") a word can hold. Feel free
 *                      to adjust both of them at your own leisure
 *
 * How to add a new word: first of all, make sure it fits inside the
 * restrictions above (MAX_WORDS and MAX_CHARS). If everything's fine, take
 * your word and divide it into letters, making each word an array row, and
 * each letter an array column. Let's take today's (31st of July, 2010)
 * Dictionary.com word of the day, "blithe", meaning merry in disposition.
 * After following the steps above, we end up with something like this:
 *
 * {'b', 'l', 'i', 't', 'h', 'e'}
 */
char dictionary[NUM_WORDS][MAX_CHARS] = {
{'u', 'n', 'f', 'o', 'r', 't', 'u', 'n', 'a', 't', 'e', 'l', 'y'},
{'p', 'r', 'o', 'g', 'r', 'a', 'm', 'm', 'i', 'n', 'g'},
{'f', 'r', 'i', 'e', 'n', 'd'},
{'p', 'h', 'o', 'n', 'e'},
{'s', 'y', 'l', 'p', 'h'}
};

/**
 * char word[]: this array of characters stores the letters of the current
 *              word. It is initialized in the init_arrays() function
 */
char word[MAX_CHARS];

/**
 * int cur_word: a value between 0 and MAX_WORDS - 1, representing the 
 * number in the dictionary of the randomly chosen word, initially 0
 */
int cur_word = 0;

/**************************************************************************
 *                          FUNCTION PROTOTYPES                           *
 **************************************************************************/

/**
 * void init_arrays(int num_letters, int alphabet[26])
 *
 * Arg 1: the number of letters the current word has
 *
 * This function initializes the word[] and alphabet[] arrays by filling
 * word[] with underscores (_) and the first and last letters, and alphabet
 * with zeros
 */
void init_arrays(int num_letters, int alphabet[26]);

/**
 * int in_dictionary(char letter, int num_letters)
 *
 * Arg 1: the letter to be checked against the word
 * Arg 2: the number of letters the current word has
 *
 * This function checks whether a given letter exists within the dictionary.
 * It returns 1 if it exists and 0 if it doesn't. Also, if the letter
 * exists, it replaces the underscores in the word[] array
 */ 
int in_dictionary(char letter, int num_letters);

/**
 * int word_guessed(int num_letters)
 *
 * Arg 1: the number of letters the current word has
 *
 * This function checks whether there are any underscores (_) within the
 * current word. If there are, it returns 0, because the word hasn't been
 * guessed. If there aren't, the word has been guessed, so it returns 1
 */
int word_guessed(int num_letters);

/**
 * int get_num_letters(void)
 *
 * This function gets the number of letters for the currently generated
 * word and returns it
 */
int get_num_letters(void);

/**
 * void generate_word(void)
 *
 * This function initializes the random numbers generator, and randomly
 * picks a word from the dictionary, setting cur_word to the value of the
 * remainder of a random number and the number of characters in the array
 */
void generate_word(void);

/**
 * void print_letters(int alphabet[26])
 *
 * This function checks the alphabet[] array and prints all the letters
 * tried, marked as 1 inside the array
 */
void print_letters(int alphabet[26]);

/**
 * void print_word(int num_letters)
 *
 * Arg 1: the number of letters the current word has
 *
 * This function prints the current state of the word[] array
 */
void print_word(int num_letters);

/**************************************************************************
 * main: this function generates a random number representing the word,   *
 *       initializes the arrays, gets and evaluates input from the        *
 *       user, prints the remaining letters, and also prints the current  *
 *       state of the word                                                *
 **************************************************************************/
int main(void) {

    /**
     * char   ch: the current character under cursor
     *  int fail: the number of bad attempts at guessing the number
     *  int num_letters: the number of letters the current word has, as
     *                   returned by the function get_num_letters
     *  int alphabet[26]: an array of integers, representing the letters of
     *                    the alphabet. If a letter has been tried, mark it
     *                    with 1. Otherwise, let it equal to 0, as initiated
     */

    char ch;
    int fail = 0, num_letters; 
    int alphabet[26];

    generate_word(); /* Generate a random word */

    /* Get the number of letters of the word */
    num_letters = get_num_letters();

    /* Initialize the words[] and alphabet[] arrays */
    init_arrays(num_letters, alphabet);

    /**
     * As long as the number of tries is less than the maximum number of
     * bad guesses defined as a constant above and the word has not been
     * guessed, keep asking the user for input
     */
    do {

        printf("\n\n"); /* Eye-candy, two blank lines between guesses */

        print_word(num_letters); /* Print the current state of the word */

        print_letters(alphabet); /* Print the letters already tried */

        /* Get input from user */
        printf("Enter a letter: ");
        scanf(" %c", &ch); /* Ignore white-spaces */
       
        /**
         * If a non-alphabetic character has been entered, print an error 
         * message and skip the other parts of the loop
         */
        if(!isalpha(ch)) {

            printf("Invalid character"); /* Print an error message */
            continue; /* Skip the next part of the loop */
        }
        
        ch = tolower(ch); /* Set the letter to all lower-case */

        /**
         * If the letter has already been entered, issue an error message 
         * and skip the rest of the loop
         */
        if(alphabet[ch - 'a']) {

            /* Print an error message */
            printf("Letter already entered. Please try another one\n"); 
            continue; /* Skip the next part of the loop */
        } 

            alphabet[ch - 'a'] = 1;
            /**
             * If the letter exists within the current word, put the letter
             * inside the word[] array. Otherwise, increase the number of
             * bad tries
             */
            if(!in_dictionary(ch, num_letters)) {

                fail++; /* Increase the number of bad attempts */
            }
    } while(fail < MAX_BAD && !word_guessed(num_letters));

    /**
     * If there have been 6 failed attempts at guessing the number, print
     * a message a(nd the correct word. Otherwise, print "You won!" and exit
     */
    if(fail == 6) {

        int i;

        printf("You lost! The word is "); /* Print "You lost!" */
        /* Print the word */
        for(i = 0; i < num_letters; i++) {

            printf("%c", toupper(dictionary[cur_word][i]));
        }
    } else {

        printf("You won!\nThe word is: "); /* Print "You won!" */
        print_word(num_letters);
    }

    printf("\n"); /* Print a new line */

    return 0; /* Return 0 upon successful program execution */
}

/**************************************************************************
 *                          FUNCTION DEFINITIONS                          *
 **************************************************************************/

void generate_word(void) {

    srand((unsigned) time(NULL)); /* Seed the random numbers generator */

    cur_word = rand() % NUM_WORDS; /* Get the word */
}

int get_num_letters(void) {

    /**
     * int num_letters: the number of letters to be returned by the
     *                  function, initially 0
     */
    int num_letters = 0;


    /**
     * As long as the array elements are characters, increment the counter
     * representing the number of letters
     */
    while(isalpha(dictionary[cur_word][num_letters])) {

        num_letters++; /* Increment the number of letters */
    }

    return num_letters; /* Return the number of letters */
}

void init_arrays(int num_letters, int alphabet[26]) {

    /**
     * int last: the last letter of the word, written as the number of
     * letters in the word - 1
     */
    int last = num_letters - 1, i;

    /* Initialize the alphabet[] array by setting all the elements to 0 */
    for(i = 0; i < 26; i++) {

        alphabet[i] = 0;
    }

    /**
     * Initialize the word[] array by first setting all the elements to
     * underscores (_), and then putting the first, last and recurring
     * letters in place
     */
    for(i = 0; i < num_letters; i++) {

        word[i] = '_'; /* Set the element to underscore (_) */
    }

    word[0] = dictionary[cur_word][0]; /* Set the first letter */
    alphabet[word[0] - 'a'] = 1; /* Mark the letter as 'tried' */
    word[last] = dictionary[cur_word][last]; /* Set the last letter */
    alphabet[word[last] - 'a'] = 1; /* Mark the letter as 'tried' */

    /* Check for repeated occurrences */
    in_dictionary(word[0], num_letters);
    in_dictionary(word[last], num_letters);
}

int in_dictionary(char letter, int num_letters) {

    /**
     * int i: a loop counter, initially 0
     * int flag: a boolean flag, determining whether the letter exists or
     *           not. Initially, 0
     */
    int i = 0, flag = 0;

    /**
     * Go through the word in the dictionary, and, if the letter passed as
     * argument is found, replace the underscore in the word[] array.
     */
    while(i < num_letters) {

        if(dictionary[cur_word][i] == letter) {

            word[i] = dictionary[cur_word][i]; /* Replace the underscores */
            flag = 1; /* Set the boolean flag to 1 */
        }

        i++; /* Increment the loop counter */
    }

    /**
     * If the boolean flag has been altered (set to 1) while looping, the
     * letter has been found and replaced, so return 1 (true). Otherwise,
     * the flag has its original value of 0, causing the function to return
     * 0
     */
    if(flag) {

        return 1; /* Return TRUE */
    } else {

        return 0; /* Return FALSE */
    }
}

int word_guessed(int num_letters) {

    /**
     * int i: loop counter
     */
    int i;

    /**
     * Check the current state of the current word. If there are any
     * underscores in it, the word has not been guessed, so the function
     * returns 0. If the 'return 0;' statement has not been encountered by
     * the end of the loop, the function returns the default value of 1
     */
    for(i = 0; i < num_letters; i++) {

        if(word[i] == '_') { /* Checks for underscores */

            return 0; /* Return FALSE if there are underscores */
        } 
    }

    /**
     * Return TRUE if the function's execution 
     * has not been stopped by now
     */
    return 1; 
}

void print_word(int num_letters) {

    /**
     * int i: loop counter 
     */
    int i;

    /**
     * Go through the current state of the word, printing each letter or
     * underscore. If the current character is a letter, print it in
     * upper-case. Also, leave a blank space to the right, for aesthetic
     * purposes
     */ 
    for(i = 0; i < num_letters; i++) {

        /* Print the letter in upper-case, with a space to the right */
        printf("%c ", toupper(word[i]));
    }

    /* Print a new line */
    printf("\n");
}

void print_letters(int alphabet[26]) {

    /**
     * int i: loop counter
     */
    int i;

    printf("Letters tried: "); /* Informational message */
   
    /**
     * Go through the alphabet[] array. If the current element is 1, print
     * the letter associated with it, in upper-case
     */
    for(i = 0; i < 26; i++) {

        if(alphabet[i] == 1) {

            printf(" %c", 'A' + i); /* Print the letter */
        }
    }

    printf("\n"); /* Print a new line */
}

Recommended Answers

All 4 Replies

Member Avatar for engg_fahd

It works fine.What you can do to improve it is:
1.use first a menu to ask the user which type of game he wants to play(easy,hard,expert)
2.Make 3 dictinaries(1 for easy 1 for hard and 1 for expert)
3.Your game quits just after a word,It should continue asking for more words untill the user approaches the maximum number of mistakes he can make,Change the mistakes count.
4.You can also make password for hard and expert and provide the password only if the user completes easy

It works fine.What you can do to improve it is:
1.use first a menu to ask the user which type of game he wants to play(easy,hard,expert)
2.Make 3 dictinaries(1 for easy 1 for hard and 1 for expert)
3.Your game quits just after a word,It should continue asking for more words untill the user approaches the maximum number of mistakes he can make,Change the mistakes count.
4.You can also make password for hard and expert and provide the password only if the user completes easy

For 3, that's why I've used init_arrays() , I wanted to have the player enter Y/N after every game. As to the Easy/Expert/Advanced, if I ever make a distribution version, I will certainly add those features. Thanks!

Holy excessive comments Batman! Just FYI, if you find yourself commenting nearly every line of code with multiple lines of comments, either your code is far too opaque and needs some serious attention toward readability, or you're including too many unnecessary comments. In reality well written code needs fewer comments than you'd think. My own production code tends to be rather sparse, comment-wise, with no loss of clarity. I honestly almost laughed when I saw a six line comment for every header you included. That's almost as good as the traditional "bad" comment:

i = i + 1; /* Increment i */

I'm on vacation at the moment with only access to an iPad. Making posts is especially tedious, otherwise I would make detailed comments on your code. If you remind me in about a week, I can do a full review for you.

Thanks, I'll create a copy and remove the unnecessary comments. And yes, I do have several 'Increment <whatever>' lines :-) I will surely remind you about it, I'm really looking for feedback on my first shot at a program :-)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.