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 */
}