5
\$\begingroup\$

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;
}
\$\endgroup\$
5
\$\begingroup\$

There's a lot of code here (the poor indentation in making it harder to read), but I'll skim through and recommend several things I've found.

  • Some of your indentation is off. Remember to have all code within curly braces indented, preferably by four spaces. More importantly, keep this consistent throughout the program.

  • You're mixing lowercase and uppercase naming with your functions. Choose only one and keep it consistent. Lowercase is common for functions in C, but it's mostly personal preference.

  • You can eliminate all of those function prototypes by defining main() below the others. This works because main() will already be aware of their definitions when calling them.

  • Your large hard-coded displays clutter up your code and are not necessary for a console program. Just a simple menu for the user will work.

  • printHangMan() is really long and should be simplified if you still want such an output.

  • Prefer to call srand() at the top of main(). This will make it easy to maintain and will ensure that it's called only once. If called multiple times, it reset the seed each time, resulting in the "same" random values when calling rand().

  • I don't think your space-printing functions are necessary, or at least printOneSpace() (you could just output this inline). You shouldn't output a lot of spaces if it'll clutter up your code and output.

  • For printing an unformatted line that should also end with a newline, use puts() instead of printf(). For similar lines without a newline, use fputs(). More info about that here.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.