I have been trying to learn C by making a Hangman game. It's not perfect, but with C, I worry that my coding practices will not be very good. I'm also new to allocating memory myself, so I feel I may have done something wrong there. I would very much appreciate reviews of my code.
The game takes in a text file with the first line of the file being a category and the rest of the file being words that can be used in the game: a random line is selected. The file address is in the arguments for the program.
/* File: hangman.c
*
*
* This program implements the game, Hangman.
*
* Comments
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <dirent.h>
#include <sys/types.h>
int contains(char[], char, int);
char *hideWord(char []);
void changeWord(char [], char *, char);
void InitializeArray(char [], int);
void printHangMan(int, int);
void Engine(char [], char *);
void Clear();
void printSpace(int);
char *stringTrimmer(char *);
void printOneSpace(int);
char *Randomline(FILE *, char *);
char *Catergory(FILE *, char *);
void printTop();
void printWin(int, int);
void printLost(int, int);
void printRest(int, int, char [], char [], char []);
char **fileArray(char []);
int stringCont(char *, char *);
int menu();
void files();
#define Size 60
#define wordLength 30
int main(int argc, char *argv[])
{
FILE *ptrFile;
char input[5] = " ";
char *randomCat;
char *randomLine;
while (strcmp(input, "quit") != 0)
{
char address[Size]="0";
int menuChoice = menu();
if(menuChoice== 1)
{
char *i=argv[1];
randomLine = Randomline(ptrFile,i);
randomCat = Catergory(ptrFile,i);
}
else if(menuChoice == 2)
{
files(address);
randomLine = Randomline(ptrFile, address);
randomCat = Catergory(ptrFile, address);
system("clear");
}
Engine(p, r);
printf("\ntype menu to return to Menu type quit to quit\n");
scanf("%s", input);
system("clear");
}
return 0;
}
int menu(char *address)
{
char input[9];
printf("*******************************************\n");
printf("* *\n");
printf("* * * * * * * *\n");
printf("* * Play * *\n");
printf("* * * * * * * *\n");
printf("* *\n");
printf("* * * * * * * *\n");
printf("* * Files * *\n");
printf("* * * * * * * *\n");
printf("* *\n");
printf("* * * * * * * *\n");
printf("* * Rules * *\n");
printf("* * * * * * * *\n");
printf("* *\n");
printf("*******************************************\n");
scanf("%s",input);
if(strcmp(input,"play")==0||strcmp(input,"Play")==0)
{
system("clear");
return 1;
}
else if(strcmp(input,"Files")==0||strcmp(input,"files")==0)
{
return 2;
}
else
{
system("clear");
return 0;
}
return 0;
}
void files(char address[])
{
system("clear");
printf("*******************************************\n");
printf("* *\n");
printf("* *\n");
printf("* * * * * * * * * * * * * * * *\n");
printf("* * Type the path of a file.* *\n");
printf("* * Or path of a folder for * *\n");
printf("* * many files. * *\n");
printf("* * * * * * * * * * * * * * * *\n");
printf("* *\n");
printf("* *\n");
printf("* *\n");
printf("* *\n");
printf("*******************************************\n");
scanf("%s",address);
}
void Engine(char mysteryWord[], char *cat)
{
int Length = strlen(mysteryWord); //Length is made strlen() so its only called once
int catLength = strlen(cat);
int spaces = Length+3; // spaces incremented for better formatting
if (Length*2<catLength) // if mystery word is small the screen will be to small
{
spaces = catLength/2+3;
}
else if(Length<7)
{
spaces =10;
}
int lifeLost = 0;
char input = '\n'; // made to \n so it will be ignored later on
char *HiddenWords = hideWord(mysteryWord);
char wrongChars[11] = {' '}; //InitializeArray(wrongChars,11);
//While loop will stop if the user looses 10 lives or guesses all the letters
while (lifeLost < 10 && contains(HiddenWords, '_', Length * 2) > 0)
{
printTop(spaces);
printRest(spaces, lifeLost, cat, HiddenWords, wrongChars);
scanf("\n%c", &input); // /n*c added so that new line char is ignored when enter button is pressed
if (lifeLost < 10 && input != '\n') {
int occurances = contains(mysteryWord, input, Length);
if (occurances < 1)
{
wrongChars[lifeLost] = input;
lifeLost = lifeLost + 1;
}
else
{
changeWord(mysteryWord, HiddenWords, input);
}
}
system("clear");
}
if (lifeLost == 10) {
system("clear");
printLost(spaces, lifeLost);
} else
{
system("clear");
printWin(spaces, lifeLost);
}
}
/*Clear method maybe used as System("clear") is not portable
* and maybe be easily hacked by changing the clear program in memory
* but for this purpose System("clear") should hopefully suffice
*/
void Clear() {
for (int i = 0; i < 20; i++) {
printf("\n");
}
}
/*
* hideWord takes in the mysteryWord array and returns a new array pointer
* based on the hideWord array but with all the letter replaced to underscores
*
*
* every other element in the returned array is also made a space so the array can
* be printed correctly
*
* the new array has to be twice the length of the original because of the extra
* spaces.
* If a letter is found at the ith element in the mysteryWord at must be made an
* underscore in the hiddenWord at the i*2 element as even elements are spaces.
*
* spaces in the mysteryWord remain spaces in the hiddenWord they do not got made
* into underscores.
*/
char *hideWord(char mysteryWord[]) {
int length = strlen(mysteryWord);
char *word = malloc((length * 2));
if (!word) {
return NULL;
}
for (int i = 0; i < length * 2; i++) {
word[i] = ' ';
}
for (int i = 0; i <= length - 1; i++) {
if (mysteryWord[i] == ' ') {
word[i * 2] = ' ';
}
else if (isalpha(mysteryWord[i]) != 0) {
word[i * 2] = '_';
} else {
word[i * 2] = mysteryWord[i];
}
}
return word;
}
/*
* contains takes in the mysteryWord array,its length and the user input
* it returns how many times the input occurs in the array
*
*/
int contains(char mysteryWord[], char input, int arrayLength) {
int a = 0;
if(isalpha(input)==0 && input != '_')
{
return 2;
}
char flipInput;
;
if (isupper(input) != 0) {
flipInput = tolower(input);
} else {
flipInput = toupper(input);
}
for (int i = 0; i < arrayLength; i++) {
if (mysteryWord[i] == input || mysteryWord[i] == flipInput) {
a = a + 1;
}
}
return a;
}
/*changeWord takes in the user input
* The Mystery word p[]
* and the hidden word *a
// *
* for loop loops round the mystery word comparing every item to the input
* and if it is found it then displays the input in the hidden word array
*
* The first element in the Mystery word corresponds to the 1st in the hiddenArray
* But the 2nd element corresponds to the 3rd in the hiddenArray due to there being spaces
* in every other element;
* 0 1 2 3
*[B] [O] [B] [S] (Mystery word)p[]
*[_] [ ] [_] [ ] (Hidden word)*a
*
* if the user input O it would be found in the 1st element of the MysteryArray
* but would be added to the 2nd in the hiddenArray it is alway i*2
*
* If the user enter a lower case letter it is converted to an uppercase and
* then compared to the array if it finds a match it is added to the hiddenWord array
*
*If the user enters a uppercase letter is converted to a lowercase
*
*
*/
void changeWord(char mysteryWord[], char *hiddenWord, char input) {
char flipInput;
int mult;
int length = strlen(mysteryWord);
if (isupper(input) != 0) {
flipInput = tolower(input);
} else {
flipInput = toupper(input);
}
for (int i = 0; i < length; i++) {
if (mysteryWord[i] == input) // compares every element to user input
{
mult = i * 2;
hiddenWord[mult] = input;
} else if (mysteryWord[i] == flipInput) // compares element to lowered Input
{
mult = i * 2;
hiddenWord[mult] = flipInput;
}
}
}
/*Possible the worst looking function ever but it's fairly simple.
*
* due to the varying length of the mystery word different amounts of spaces need
* to be printed the function takes in the width of the game screen prints the
* corresponding amount of spaces.
*
* printHangMan takes in how many lives have been lost
* and then prints out the corresponding hangman drawing
*/
void printHangMan(int livesUsed, int length) {
switch (livesUsed) {
case 0:
printf(" * ");
printSpace(length - 2); // -2 because there is a * at either end
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
break;
case 1:
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printSpace(length - 2);
printf("\n");
printf(" * ");
printf(" ___________");
printSpace(length - 8); // less spaces need printing because of the underscores
printf("\n");
break;
case 2:
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 3:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ ");
printSpace(length - 5);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 4:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 5:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 6:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | | ");
printSpace(length - 7);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 7:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | |\\ ");
printSpace(length - 7);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 8:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | /|\\ ");
printSpace(length - 7);
printf("\n");
printf(" * | ");
printSpace(length - 4);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 9:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | /|\\ ");
printSpace(length - 7);
printf("\n");
printf(" * | \\ ");
printSpace(length - 7);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
case 10:
printf(" * _________ ");
printSpace(length - 8);
printf("\n");
printf(" * |/ | ");
printSpace(length - 7);
printf("\n");
printf(" * | 0 ");
printSpace(length - 7);
printf("\n");
printf(" * | /|\\ ");
printSpace(length - 7);
printf("\n");
printf(" * | / \\ ");
printSpace(length - 7);
printf("\n");
printf(" * ");
printf(" _|__________ ");
printSpace(length - 9);
printf("\n");
break;
}
}
/*
* countLetter takes in the mysteryWord its length
* and returns how many letters it contains
*/
int countLetter(char mysteryWord[], int length) {
int ret = 0;
for (int i = 0; i < length; i++) {
if (mysteryWord[i] != ' ') {
ret = ret + 1;
}
}
return ret;
}
/**
* Randomline function
* first finds the length of the file ptrFile.File length stored in 'count'
* Second creates a random number with a max value of 'count'.Random number called 'random'
* Third uses fseek function to go to postion of value 'random'.
* fseek may not have been at the begining of a line so it is the next line that is read.
*
* As I do not know how large the line is 60 is allocated to printLine.
* stringTrimmer() is called and returns a string without new line chars and of a correct size
* printLines memory is then freed as it is now needed anymore.
*
* tempLine reads the fraction of a line 'random' points to it is then freed.
*
* 'finalLine' is returned as it is the random line from the file which has been trimmer
*
*
* @param ptrFile
* @param address
* @return
*/
char *Randomline(FILE *ptrFile, char *address)
{
ptrFile = fopen(address, "r");
int count = 0;
char *append;
int random = 0;
char word[wordLength];
int c = 0;
int a = 0;
char *printLine = malloc(Size);
char *tempLine = malloc(Size);
char *finalLine;
fseek(ptrFile, 0, SEEK_END);
count = ftell(ptrFile);
srand(time(NULL));
random = rand() % count;
fseek(ptrFile, random, SEEK_SET);
fgets(tempLine, 60, ptrFile); // skip a line incase random place is in middle of line
fgets(printLine, 60, ptrFile);
fclose(ptrFile);
finalLine = stringTrimmer(printLine); // memory allocated in function is free in main method
free(printLine);
return finalLine;
}
/**
* This function basically reads in a text file and returns the first line
* of that file.
* @param ptrFile-This is a pointer to where the file is.
* @param address - This is the address of the file needed to open ptrFile.
* @return - The function returns the first line of the file.
*/
char *Catergory(FILE *ptrFile, char *address) {
ptrFile = fopen(address, "r");
char *ret = malloc(Size);
char *finalLine;
fgets(ret, 60, ptrFile);
fclose(ptrFile);
for (int i = 0; i < 60; i++) {
if (ret[i] == '\n') {
ret[i] = '\0';
}
}
finalLine = stringTrimmer(ret);
return finalLine;
}
/**
* due to the varying length of the mystery word different amount of spaces
* have to printed on each line this function stops the Engine() from become to big
* @param number
*/
void printSpace(int number) {
for (int i = 0; i < number; i++)
{
printf(" ");
}
printf("*");
}
/**
* due to the varying length of the mystery word different amount of spaces
* have to printed on each line this function stops the Engine() from become to big
* @param number
*/
void printOneSpace(int number) {
for (int i = 0; i < number; i++)
{
printf(" ");
}
printf("*");
}
/**
*
* @param input
* @return
*
* stringTrimmer takes in a string that has been read from a file
* and creates a new string without the newline that has been read from the file
* also the amount of space allocated to the line read in is 60
* this creates a string with correct memory allocation.
*
*/
char *stringTrimmer(char *input) {
int counter = 0;
int size = strlen(input);
int a = 0;
while (isprint(input[counter]))
{
counter++;
}
char *output = malloc(counter + 1);
for (int i = 0; i <= size; i++) {
if (isprint(input[i]) && a < counter) {
output[a] = input[i];
a++;
}
}
output[counter + 1] = '\0';
return output;
}
/**
*
* @param spaces
* printTop prints everything on the screen up until
* the hangman figure
*/
void printTop(int spaces) {
printf(" ");
for (int i = 0; i < spaces; i++)
{
printf("* ");
}
printf("\n");
printf(" * HangMan ");
for (int i = 0; i < spaces - 8; i++) {
printf(" ");
}
printf("*\n");
printf(" * ");
printSpace(spaces - 2);
printf("\n");
}
/**
*
* @param spaces
* @param lifeLost
* Screen is to show when the user has won
*/
void printWin(int spaces, int lifeLost) {
printTop(spaces);
printHangMan(lifeLost, spaces);
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" * You Win! ");
printSpace(spaces - 8);
printf("\n");
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" ");
for (int i = 0; i < spaces; i++) {
printf("* ");
}
}
/*The printLost method takes in the amount of spaces and the lifeLost
*Its function is to print out the screen when user has lost
*/
void printLost(int spaces, int lifeLost) {
printTop(spaces);
printHangMan(lifeLost, spaces);
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" * You Lose! ");
printSpace(spaces - 8);
printf("\n");
printf(" * ");
printSpace(spaces - 2);
printf("\n");
printf(" ");
for (int i = 0; i < spaces; i++) {
printf("* ");
}
}
void printRest(int spaces, int lifeLost, char cat[], char HiddenWords[], char wrongChars[]) {
printHangMan(lifeLost, spaces);
printf(" * %s ", wrongChars);
if (lifeLost > 0) printf(" ");
printOneSpace(((spaces - 3)*2) - lifeLost);
printf("\n * ");
printSpace(spaces - 2);
printf("\n * %s ", cat);
printOneSpace((spaces - strlen(cat))*2+1);
printf("\n * %s",HiddenWords);
printOneSpace((spaces*2-strlen(HiddenWords)-4));
printf("\n");
printf(" ");
for (int i = 0; i < spaces; i++)
{
printf("* ");
}
}
int isFile(FILE *fptr, char *address) {
fptr = fopen(address, "r");
if (fptr == NULL) {
return 1;
} else {
fclose(fptr);
return 0;
}
}
char **fileArray(char address[])
{
DIR *dir;
struct dirent *entry;
static char *a[10];
if ((dir = opendir(address)) == NULL) {
perror("opendir() error");
} else {
puts("contents of root:");
for (int i = 0; ((entry = readdir(dir)) != NULL) && i <= 10; i++) {
if (stringCont(".txt", entry->d_name) == 1)
{
a[i] = entry->d_name;
printf("%s \n", a[i]);
}
}
closedir(dir);
}
return a;
}
/*/
*
* Check if str2 contains str1
* return 1 if true
*/
int stringCont(char *str1, char *str2) {
int str2Len = strlen(str2);
int counter = 0;
int str1Len = strlen(str1);
for (int i = 0; i < str2Len; i++) {
counter = i;
for (int a = 0; str1[a] == str2[counter]; a++) {
counter++;
}
if ((counter - i) > str1Len) {
return 1;
}
}
return 0;
}