Instructions
Objective
Write a program to create a hangman game in C.
Requirements and Specifications
= Hangman
== Task Specification
Implement a simple link:https://en.wikipedia.org/wiki/Hangman_(game)[_Hangman_] game, where one player has to guess a word.
. Main Task (30 points)
Supply the word to guess on the command line.
Let the user guess character by character and inform him about the state of the game (cf. <<exec,below>>).
Do not distinguish between lower- and uppercase characters, i.e. use an alphabet of only 26 symbols.
Write the progress of the game into a log file including the time of the user inputs (cf. time.h). Clearly mark the start and end of each game and do not overwrite an existing log file.
2Task (15 points)* +
Allow the user to directly guess the whole word instead of a single character and end the game immediately afterwards (the player looses if he guesses wrong).
. 3Task (10 points) +
Support multiple game rounds without restarting the game.
Supply a new word for each round in a sensible way.
. 4Task (15 points) +
Instead of supplying the word directly the user has to specify an input file that contains possible words to guess.
Select any word from that list at random.
.5 Task (15 points) +
Mark used words persistently in the word input file.
Exclude previously used words when selecting a random word.
Allow the user to reset the markers in the file.
.6 Task (25 points) + Instead of the simpler version above implement the following user interface: +
Draw the used interface including a depiction of the gallows and body parts using the ncurses library (cf. link:https://en.wikipedia.org/wiki/Ncurses[Wikipedia], link:http://invisible-island.net/ncurses/ncurses-intro.html[Howto by one of the ncurses authors], link:http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/[TLDP Howto]).
Draw the gallows and the parts of the hanging stick figure in simple link:https://en.wikipedia.org/wiki/ASCII_art[ASCII graphics].
Do not require the user to hit enter after each character.
. Task (15 points) +
The program arguments are handled by getopt(3).
== Example Execution[[exec]]
Source Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
// number of tries to guess the word
#define TRIES_LIMIT 8
// input line limit
#define MAX_LINE 1024
// max number of words in file
#define MAX_WORDS 100
// default words file name
#define WORDS_FILENAME "words.txt"
// default log file name
#define LOG_FILENAME "log.txt"
FILE *logFile;
// method for logging a message
void logInfo(char* message) {
// creating timestamp string
char buff[20];
struct tm *sTm;
time_t now = time (0);
sTm = gmtime (&now);
strftime (buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", sTm);
// outputting timestamp and message to log file
fprintf (logFile, "%s %s\n", buff, message);
}
// method for reading word list and used array from word file
int readWords(char* wordsFileName, char words[MAX_WORDS][MAX_LINE], int* used) {
char line[MAX_LINE];
int count = 0, available = 0, i;
// opening file
FILE *f = fopen(wordsFileName, "r");
// reading line, while there are lines to read
while(fgets(line, MAX_LINE, f) != NULL) {
// reading word
sscanf(line, "%s", words[count]);
for (i = 0; i<strlen(words[count]); i++) {
words[count][i] = toupper(words[count][i]);
}
// reading used flag
sscanf(line, "%d", &used[count]);
// counting available words
if (used[count] == 1) {
available++;
}
// counting all words
count++;
}
fclose(f);
// returning number of words
return count;
}
// method for choosing randomly a single words for game from word list
int chooseWord(int n, char words[MAX_WORDS][MAX_LINE], int* used) {
srand(time(NULL));
int i, available = 0, r, count = 0;
// checking if there are available words and reset if needed
while (available == 0) {
available = 0;
for (i = 0; i<n; i++) {
if (used[i] == 1) {
available++;
}
}
// there are available words - no need to reset
if (available > 0) {
break;
}
// no words available - we need to reset used array
for (i = 0; i<n; i++) {
used[i] = 1;
}
}
// choosing random word from available list
r = rand() % available;
for (i = 0; i<n; i++) {
if (used[i] == 1) {
if (count == r) {
used[i] = 0;
return i;
}
count++;
}
}
return -1;
}
// method for updating word list with new used array state
void saveWords(int n, char words[MAX_WORDS][MAX_LINE], int* used) {
int i;
// opening file for writing
FILE *f = fopen(WORDS_FILENAME, "w");
// printing data for each word
for(i = 0; i < n; i++) {
fprintf(f, "%s %d\n", words[i], used[i]);
}
fclose(f);
}
// method for reading user integer choice
int readUsersIntChoice(char* prompt, int max) {
// keep repeating until valid input
while(1) {
char line[MAX_LINE];
int choice;
// prompting user
printf("%s: ", prompt);
// reading line and converting it to int
gets(line);
choice = atoi(line);
// choice must be in [1,max]
if (choice <= 0 || choice > max) {
// otherwise it is invalid
printf("Invalid input.\n");
}
else {
printf("\n");
// if input is valid, returning it
return choice;
}
}
}
// method for reading user char choice
char readUsersCharChoice(char* prompt) {
while(1) {
char line[MAX_LINE];
// keep repeating until valid input
printf("%s: ", prompt);
// reading line
gets(line);
// everything is valid besides empty line
if (strlen(line) > 0) {
// if line is not empty, return first character
printf("\n");
return line[0];
}
printf("Invalid input\n");
}
}
// method for opening a single character in current game state
int open(char* word, char* state, char c) {
// uppercasing letter
char cUp = toupper(c);
int i, opened = 0, len = strlen(word);
// going through the word array and trying to open it
for (i = 0; i<len; i++) {
if (toupper(word[i]) == cUp && state[i] == '_') {
state[i] = cUp;
// counting open letters
opened++;
}
}
return opened;
}
// method which makes single game rounf
void playGame(char* word) {
// logging
logInfo("New game started");
int i, len = strlen(word);
char state[len+1], buf[MAX_LINE + 40];
// creating new empty state word of secret word length
for(i = 0; i<len; i++) {
state[i] = '_';
}
state[len] = 0;
int tries = TRIES_LIMIT;
int left = len;
// keep iterating until game is over
while(tries > 0 && left > 0) {
printf("-----------------------\n");
printf("Current state: %s\n", state);
// logging
sprintf(buf, "Current state: %s", state);
logInfo(buf);
printf("Tries left: %d\n", tries);
// logging
sprintf(buf, "Tries left: %d", tries);
logInfo(buf);
// reading user game option
int choice = readUsersIntChoice("Options:\n1. Open a letter\n2. Guess the word\nPlease, enter your choice", 2);
if (choice == 1) {
// reading single character
char c = readUsersCharChoice("Please, enter character");
// trying to open entered letter
int opened = open(word, state, c);
// logging
sprintf(buf, "User tries to open letter: %c", c);
logInfo(buf);
// if there were more, than 0 open letters - the move was successful
if (opened > 0) {
left -= opened;
printf("Success! You opened %d letter(s)\n", opened);
sprintf(buf, "User opened %d letter(s)", opened);
logInfo(buf);
}
else {
// there were no open letters - move was bad
tries--;
printf("Fail! Can not open such letter\n");
logInfo("User fails. No such letter");
}
}
else {
// user tries to guess the whole word
printf("Please, enter your guess: ");
char guess[MAX_LINE];
// reading user's guess
gets(guess);
// logging
sprintf(buf, "User tries to guess the word letter: %s", guess);
logInfo(buf);
for (i = 0; i<strlen(guess); i++) {
guess[i] = toupper(guess[i]);
}
// comparing guess with secret word
if (strcmp(guess, word) == 0) {
left = 0;
}
else {
tries = 0;
}
break;
}
}
// analyzing game end and output messages
printf("=======================\n");
if (left > 0) {
printf("You lost... The correct word was '%s'\n", word);
sprintf(buf, "User LOST. The correct word was '%s'", word);
logInfo(buf);
}
else {
printf("You WON!\n");
logInfo("User WON");
}
logInfo("Game finished");
}
int main(int argc, char** argv) {
char words[MAX_WORDS][MAX_LINE], singleWord[MAX_LINE];
int used[MAX_WORDS];
char* logFileName = LOG_FILENAME, *wordsFileName = WORDS_FILENAME;
int totalWords, a, useSingleWord = 0;
// parsing command line arguments
while ((a = getopt(argc, argv, "l:w:")) != -1) {
switch (a)
{
// custom log file name
case 'l':
logFileName = optarg;
break;
// custom word file name
case 'w':
wordsFileName = optarg;
break;
default:
printf("Unknown command line argument. Terminating\n");
abort ();
}
}
for (a = optind; a < argc; a++) {
int i;
strcpy(singleWord, argv[a]);
for (i = 0; i<strlen(singleWord); i++) {
singleWord[i] = toupper(singleWord[i]);
}
useSingleWord = 1;
}
// opening log file
logFile = fopen(logFileName, "w");
logInfo("Application started");
// reading words
totalWords = readWords(wordsFileName, words, used);
// keep repeating new rounds of game, while needed
while(1) {
if (useSingleWord == 0) {
// choosing word
int w = chooseWord(totalWords, words, used);
// updating word usage array
saveWords(totalWords, words, used);
playGame(words[w]);
}
else {
playGame(singleWord);
}
printf("==================================\n");
// asking user, if he wants play one more time
if (readUsersIntChoice("Do you want to play one more time?\n1. Yes\n2. No\nPlease, enter your choice", 2) == 2) {
break;
}
}
// finishing
logInfo("Application finished");
fclose(logFile);
return 0;