Hangman written in C












0












$begingroup$


I would like your feedback on any improvements that can be made to this Hangman game I have written in C. Specifically, improvements in terms of runtime and code organization. This game was a nice way for me to learn more about the features of the C language, specifically pointers, and thus I have heavily commented the code I have written for the purposes of learning.



Below are the source files and the CMakeLists file (which includes many runtime Clang sanitizers enabled). Alternatively, the code can be easily compiled with the following: cc *.c -o hangman && ./hangman



main.c



/**
* * Hangman in C *
* O(1) lookup using pointers to 26 letters which each have a state
* A letter is either _ (not picked, default), or the letter itself
* I was inspired by seeing many other Hangman implementations which
* relied on a multiple layers of iteration, this aims to be 'simple'
* and 'idiomatic', by using a different approach.
*
* @date 1/15/19
* @author Faraz Fazli
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "rng.h"
#include "utils.h"

/**
* Use enum to replace "magic numbers" instead of #define or const
* Ref: Practice of Programming, p.21
*/
enum {
ALPHABET_SIZE = 26,
TOTAL_TRIES = 10,
};

// Words the program chooses from
static char *words = {"racing", "magic", "bow", "racecar"};

int main() {
char letters[ALPHABET_SIZE];

// Let's set 'letters' to be composed of just _
// This will later be changed as the user guesses
memset(letters, '_', ALPHABET_SIZE);

init_rng();

// Total number of elements in our array
size_t total_elems = len(words);

char *word = words[rand_to(total_elems)];
size_t word_size = strlen(word) + 1; // includes NUL character

// Here I used 'malloc' instead of VLA
char **word_to_guess = malloc(word_size * sizeof(word));
size_t word_len = strlen(word); // excludes NUL

// point each element to the appropriate letter in our array
for (size_t i = 0; i < word_len; i++) {
word_to_guess[i] = &letters[from_a(word[i])];
}

int tries = 0;
size_t num_previous_underscores = word_len;

print_count_underscores(word_to_guess, word_len);
fputs("nPick a letter: ", stdout);

// Could replace getchar() with fgets and parse each letter
// which may serve better in the future
int current_letter;
while ((current_letter = getchar()) != EOF) {
if (!isalpha(current_letter)) {
// Simply continue - printing here causes bugs
continue;
}

// convert to lower case
current_letter = tolower(current_letter);

// distance from 'a'
size_t letter_pos = from_a(current_letter);

// Letter has already been picked if it is in array
if (letters[letter_pos] == current_letter) {
puts("Please pick a different letter.");
continue;
} else {
// Change underscore to the letter
letters[letter_pos] = (char) current_letter;
}

// Finds if word still has underscores, and print word state
size_t num_underscores = print_count_underscores(word_to_guess, word_len);

// If letter has no correct guesses from this turn, increment tries
if (num_underscores == num_previous_underscores) {
tries++;
}
num_previous_underscores = num_underscores;

// Win if no underscores left
if (num_underscores == 0) {
puts("-> YOU WIN!");
break;
}

if (tries < TOTAL_TRIES) {
printf("nTries Remaining: %dn", TOTAL_TRIES - tries);
fputs("Pick a letter: ", stdout);
} else {
puts("No tries left! Game Over!");
break;
}
}
free(word_to_guess);
}


rng.c



#include "rng.h"
#include <time.h>

void init_rng(void) {
srand((unsigned int) time(NULL));
}

size_t rand_to(size_t max) {
return (unsigned long) rand() / (RAND_MAX / max + 1);
}


rng.h



#include <stdlib.h>

#pragma once

// Initializes random number generator
void init_rng(void);

/**
* Helper method for Random Number Generation
* @param max - max number
* @return between 0 to max
*/
size_t rand_to(size_t max);


utils.c



#include <stdio.h>
#include "utils.h"

size_t len(char **arr) {
return sizeof(arr) / sizeof(arr[0]);
}

size_t print_count_underscores(char **word_to_guess, size_t word_len) {
size_t num_underscores = 0;
for (size_t i = 0; i < word_len; i++) {
printf("%c ", *word_to_guess[i]);
if (*word_to_guess[i] == '_') {
num_underscores++;
}
}
return num_underscores;
}

size_t from_a(int letter) {
return (size_t) abs(letter - 'a');
}


utils.h



#pragma once

#include <stdlib.h>

/**
* Returns length of array
* @param arr - words array
* @return length
*/
size_t len(char **arr);

/**
* Prints the state of each letter and counts the number of underscores
* @param word_to_guess - word being guessed (array of pointers)
* @param word_len - length of word (excluding NUL char)
* @return underscore count
*/
size_t print_count_underscores(char **word_to_guess, size_t word_len);

/**
* Returns the distance from 'a'
* @param letter 'a' to 'z'
* @return 0 through 25
*/
size_t from_a(int letter);








share









$endgroup$

















    0












    $begingroup$


    I would like your feedback on any improvements that can be made to this Hangman game I have written in C. Specifically, improvements in terms of runtime and code organization. This game was a nice way for me to learn more about the features of the C language, specifically pointers, and thus I have heavily commented the code I have written for the purposes of learning.



    Below are the source files and the CMakeLists file (which includes many runtime Clang sanitizers enabled). Alternatively, the code can be easily compiled with the following: cc *.c -o hangman && ./hangman



    main.c



    /**
    * * Hangman in C *
    * O(1) lookup using pointers to 26 letters which each have a state
    * A letter is either _ (not picked, default), or the letter itself
    * I was inspired by seeing many other Hangman implementations which
    * relied on a multiple layers of iteration, this aims to be 'simple'
    * and 'idiomatic', by using a different approach.
    *
    * @date 1/15/19
    * @author Faraz Fazli
    */

    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <stdlib.h>
    #include "rng.h"
    #include "utils.h"

    /**
    * Use enum to replace "magic numbers" instead of #define or const
    * Ref: Practice of Programming, p.21
    */
    enum {
    ALPHABET_SIZE = 26,
    TOTAL_TRIES = 10,
    };

    // Words the program chooses from
    static char *words = {"racing", "magic", "bow", "racecar"};

    int main() {
    char letters[ALPHABET_SIZE];

    // Let's set 'letters' to be composed of just _
    // This will later be changed as the user guesses
    memset(letters, '_', ALPHABET_SIZE);

    init_rng();

    // Total number of elements in our array
    size_t total_elems = len(words);

    char *word = words[rand_to(total_elems)];
    size_t word_size = strlen(word) + 1; // includes NUL character

    // Here I used 'malloc' instead of VLA
    char **word_to_guess = malloc(word_size * sizeof(word));
    size_t word_len = strlen(word); // excludes NUL

    // point each element to the appropriate letter in our array
    for (size_t i = 0; i < word_len; i++) {
    word_to_guess[i] = &letters[from_a(word[i])];
    }

    int tries = 0;
    size_t num_previous_underscores = word_len;

    print_count_underscores(word_to_guess, word_len);
    fputs("nPick a letter: ", stdout);

    // Could replace getchar() with fgets and parse each letter
    // which may serve better in the future
    int current_letter;
    while ((current_letter = getchar()) != EOF) {
    if (!isalpha(current_letter)) {
    // Simply continue - printing here causes bugs
    continue;
    }

    // convert to lower case
    current_letter = tolower(current_letter);

    // distance from 'a'
    size_t letter_pos = from_a(current_letter);

    // Letter has already been picked if it is in array
    if (letters[letter_pos] == current_letter) {
    puts("Please pick a different letter.");
    continue;
    } else {
    // Change underscore to the letter
    letters[letter_pos] = (char) current_letter;
    }

    // Finds if word still has underscores, and print word state
    size_t num_underscores = print_count_underscores(word_to_guess, word_len);

    // If letter has no correct guesses from this turn, increment tries
    if (num_underscores == num_previous_underscores) {
    tries++;
    }
    num_previous_underscores = num_underscores;

    // Win if no underscores left
    if (num_underscores == 0) {
    puts("-> YOU WIN!");
    break;
    }

    if (tries < TOTAL_TRIES) {
    printf("nTries Remaining: %dn", TOTAL_TRIES - tries);
    fputs("Pick a letter: ", stdout);
    } else {
    puts("No tries left! Game Over!");
    break;
    }
    }
    free(word_to_guess);
    }


    rng.c



    #include "rng.h"
    #include <time.h>

    void init_rng(void) {
    srand((unsigned int) time(NULL));
    }

    size_t rand_to(size_t max) {
    return (unsigned long) rand() / (RAND_MAX / max + 1);
    }


    rng.h



    #include <stdlib.h>

    #pragma once

    // Initializes random number generator
    void init_rng(void);

    /**
    * Helper method for Random Number Generation
    * @param max - max number
    * @return between 0 to max
    */
    size_t rand_to(size_t max);


    utils.c



    #include <stdio.h>
    #include "utils.h"

    size_t len(char **arr) {
    return sizeof(arr) / sizeof(arr[0]);
    }

    size_t print_count_underscores(char **word_to_guess, size_t word_len) {
    size_t num_underscores = 0;
    for (size_t i = 0; i < word_len; i++) {
    printf("%c ", *word_to_guess[i]);
    if (*word_to_guess[i] == '_') {
    num_underscores++;
    }
    }
    return num_underscores;
    }

    size_t from_a(int letter) {
    return (size_t) abs(letter - 'a');
    }


    utils.h



    #pragma once

    #include <stdlib.h>

    /**
    * Returns length of array
    * @param arr - words array
    * @return length
    */
    size_t len(char **arr);

    /**
    * Prints the state of each letter and counts the number of underscores
    * @param word_to_guess - word being guessed (array of pointers)
    * @param word_len - length of word (excluding NUL char)
    * @return underscore count
    */
    size_t print_count_underscores(char **word_to_guess, size_t word_len);

    /**
    * Returns the distance from 'a'
    * @param letter 'a' to 'z'
    * @return 0 through 25
    */
    size_t from_a(int letter);








    share









    $endgroup$















      0












      0








      0





      $begingroup$


      I would like your feedback on any improvements that can be made to this Hangman game I have written in C. Specifically, improvements in terms of runtime and code organization. This game was a nice way for me to learn more about the features of the C language, specifically pointers, and thus I have heavily commented the code I have written for the purposes of learning.



      Below are the source files and the CMakeLists file (which includes many runtime Clang sanitizers enabled). Alternatively, the code can be easily compiled with the following: cc *.c -o hangman && ./hangman



      main.c



      /**
      * * Hangman in C *
      * O(1) lookup using pointers to 26 letters which each have a state
      * A letter is either _ (not picked, default), or the letter itself
      * I was inspired by seeing many other Hangman implementations which
      * relied on a multiple layers of iteration, this aims to be 'simple'
      * and 'idiomatic', by using a different approach.
      *
      * @date 1/15/19
      * @author Faraz Fazli
      */

      #include <stdio.h>
      #include <string.h>
      #include <ctype.h>
      #include <stdlib.h>
      #include "rng.h"
      #include "utils.h"

      /**
      * Use enum to replace "magic numbers" instead of #define or const
      * Ref: Practice of Programming, p.21
      */
      enum {
      ALPHABET_SIZE = 26,
      TOTAL_TRIES = 10,
      };

      // Words the program chooses from
      static char *words = {"racing", "magic", "bow", "racecar"};

      int main() {
      char letters[ALPHABET_SIZE];

      // Let's set 'letters' to be composed of just _
      // This will later be changed as the user guesses
      memset(letters, '_', ALPHABET_SIZE);

      init_rng();

      // Total number of elements in our array
      size_t total_elems = len(words);

      char *word = words[rand_to(total_elems)];
      size_t word_size = strlen(word) + 1; // includes NUL character

      // Here I used 'malloc' instead of VLA
      char **word_to_guess = malloc(word_size * sizeof(word));
      size_t word_len = strlen(word); // excludes NUL

      // point each element to the appropriate letter in our array
      for (size_t i = 0; i < word_len; i++) {
      word_to_guess[i] = &letters[from_a(word[i])];
      }

      int tries = 0;
      size_t num_previous_underscores = word_len;

      print_count_underscores(word_to_guess, word_len);
      fputs("nPick a letter: ", stdout);

      // Could replace getchar() with fgets and parse each letter
      // which may serve better in the future
      int current_letter;
      while ((current_letter = getchar()) != EOF) {
      if (!isalpha(current_letter)) {
      // Simply continue - printing here causes bugs
      continue;
      }

      // convert to lower case
      current_letter = tolower(current_letter);

      // distance from 'a'
      size_t letter_pos = from_a(current_letter);

      // Letter has already been picked if it is in array
      if (letters[letter_pos] == current_letter) {
      puts("Please pick a different letter.");
      continue;
      } else {
      // Change underscore to the letter
      letters[letter_pos] = (char) current_letter;
      }

      // Finds if word still has underscores, and print word state
      size_t num_underscores = print_count_underscores(word_to_guess, word_len);

      // If letter has no correct guesses from this turn, increment tries
      if (num_underscores == num_previous_underscores) {
      tries++;
      }
      num_previous_underscores = num_underscores;

      // Win if no underscores left
      if (num_underscores == 0) {
      puts("-> YOU WIN!");
      break;
      }

      if (tries < TOTAL_TRIES) {
      printf("nTries Remaining: %dn", TOTAL_TRIES - tries);
      fputs("Pick a letter: ", stdout);
      } else {
      puts("No tries left! Game Over!");
      break;
      }
      }
      free(word_to_guess);
      }


      rng.c



      #include "rng.h"
      #include <time.h>

      void init_rng(void) {
      srand((unsigned int) time(NULL));
      }

      size_t rand_to(size_t max) {
      return (unsigned long) rand() / (RAND_MAX / max + 1);
      }


      rng.h



      #include <stdlib.h>

      #pragma once

      // Initializes random number generator
      void init_rng(void);

      /**
      * Helper method for Random Number Generation
      * @param max - max number
      * @return between 0 to max
      */
      size_t rand_to(size_t max);


      utils.c



      #include <stdio.h>
      #include "utils.h"

      size_t len(char **arr) {
      return sizeof(arr) / sizeof(arr[0]);
      }

      size_t print_count_underscores(char **word_to_guess, size_t word_len) {
      size_t num_underscores = 0;
      for (size_t i = 0; i < word_len; i++) {
      printf("%c ", *word_to_guess[i]);
      if (*word_to_guess[i] == '_') {
      num_underscores++;
      }
      }
      return num_underscores;
      }

      size_t from_a(int letter) {
      return (size_t) abs(letter - 'a');
      }


      utils.h



      #pragma once

      #include <stdlib.h>

      /**
      * Returns length of array
      * @param arr - words array
      * @return length
      */
      size_t len(char **arr);

      /**
      * Prints the state of each letter and counts the number of underscores
      * @param word_to_guess - word being guessed (array of pointers)
      * @param word_len - length of word (excluding NUL char)
      * @return underscore count
      */
      size_t print_count_underscores(char **word_to_guess, size_t word_len);

      /**
      * Returns the distance from 'a'
      * @param letter 'a' to 'z'
      * @return 0 through 25
      */
      size_t from_a(int letter);








      share









      $endgroup$




      I would like your feedback on any improvements that can be made to this Hangman game I have written in C. Specifically, improvements in terms of runtime and code organization. This game was a nice way for me to learn more about the features of the C language, specifically pointers, and thus I have heavily commented the code I have written for the purposes of learning.



      Below are the source files and the CMakeLists file (which includes many runtime Clang sanitizers enabled). Alternatively, the code can be easily compiled with the following: cc *.c -o hangman && ./hangman



      main.c



      /**
      * * Hangman in C *
      * O(1) lookup using pointers to 26 letters which each have a state
      * A letter is either _ (not picked, default), or the letter itself
      * I was inspired by seeing many other Hangman implementations which
      * relied on a multiple layers of iteration, this aims to be 'simple'
      * and 'idiomatic', by using a different approach.
      *
      * @date 1/15/19
      * @author Faraz Fazli
      */

      #include <stdio.h>
      #include <string.h>
      #include <ctype.h>
      #include <stdlib.h>
      #include "rng.h"
      #include "utils.h"

      /**
      * Use enum to replace "magic numbers" instead of #define or const
      * Ref: Practice of Programming, p.21
      */
      enum {
      ALPHABET_SIZE = 26,
      TOTAL_TRIES = 10,
      };

      // Words the program chooses from
      static char *words = {"racing", "magic", "bow", "racecar"};

      int main() {
      char letters[ALPHABET_SIZE];

      // Let's set 'letters' to be composed of just _
      // This will later be changed as the user guesses
      memset(letters, '_', ALPHABET_SIZE);

      init_rng();

      // Total number of elements in our array
      size_t total_elems = len(words);

      char *word = words[rand_to(total_elems)];
      size_t word_size = strlen(word) + 1; // includes NUL character

      // Here I used 'malloc' instead of VLA
      char **word_to_guess = malloc(word_size * sizeof(word));
      size_t word_len = strlen(word); // excludes NUL

      // point each element to the appropriate letter in our array
      for (size_t i = 0; i < word_len; i++) {
      word_to_guess[i] = &letters[from_a(word[i])];
      }

      int tries = 0;
      size_t num_previous_underscores = word_len;

      print_count_underscores(word_to_guess, word_len);
      fputs("nPick a letter: ", stdout);

      // Could replace getchar() with fgets and parse each letter
      // which may serve better in the future
      int current_letter;
      while ((current_letter = getchar()) != EOF) {
      if (!isalpha(current_letter)) {
      // Simply continue - printing here causes bugs
      continue;
      }

      // convert to lower case
      current_letter = tolower(current_letter);

      // distance from 'a'
      size_t letter_pos = from_a(current_letter);

      // Letter has already been picked if it is in array
      if (letters[letter_pos] == current_letter) {
      puts("Please pick a different letter.");
      continue;
      } else {
      // Change underscore to the letter
      letters[letter_pos] = (char) current_letter;
      }

      // Finds if word still has underscores, and print word state
      size_t num_underscores = print_count_underscores(word_to_guess, word_len);

      // If letter has no correct guesses from this turn, increment tries
      if (num_underscores == num_previous_underscores) {
      tries++;
      }
      num_previous_underscores = num_underscores;

      // Win if no underscores left
      if (num_underscores == 0) {
      puts("-> YOU WIN!");
      break;
      }

      if (tries < TOTAL_TRIES) {
      printf("nTries Remaining: %dn", TOTAL_TRIES - tries);
      fputs("Pick a letter: ", stdout);
      } else {
      puts("No tries left! Game Over!");
      break;
      }
      }
      free(word_to_guess);
      }


      rng.c



      #include "rng.h"
      #include <time.h>

      void init_rng(void) {
      srand((unsigned int) time(NULL));
      }

      size_t rand_to(size_t max) {
      return (unsigned long) rand() / (RAND_MAX / max + 1);
      }


      rng.h



      #include <stdlib.h>

      #pragma once

      // Initializes random number generator
      void init_rng(void);

      /**
      * Helper method for Random Number Generation
      * @param max - max number
      * @return between 0 to max
      */
      size_t rand_to(size_t max);


      utils.c



      #include <stdio.h>
      #include "utils.h"

      size_t len(char **arr) {
      return sizeof(arr) / sizeof(arr[0]);
      }

      size_t print_count_underscores(char **word_to_guess, size_t word_len) {
      size_t num_underscores = 0;
      for (size_t i = 0; i < word_len; i++) {
      printf("%c ", *word_to_guess[i]);
      if (*word_to_guess[i] == '_') {
      num_underscores++;
      }
      }
      return num_underscores;
      }

      size_t from_a(int letter) {
      return (size_t) abs(letter - 'a');
      }


      utils.h



      #pragma once

      #include <stdlib.h>

      /**
      * Returns length of array
      * @param arr - words array
      * @return length
      */
      size_t len(char **arr);

      /**
      * Prints the state of each letter and counts the number of underscores
      * @param word_to_guess - word being guessed (array of pointers)
      * @param word_len - length of word (excluding NUL char)
      * @return underscore count
      */
      size_t print_count_underscores(char **word_to_guess, size_t word_len);

      /**
      * Returns the distance from 'a'
      * @param letter 'a' to 'z'
      * @return 0 through 25
      */
      size_t from_a(int letter);






      c game c99





      share












      share










      share



      share










      asked 7 mins ago









      FarazFaraz

      302110




      302110






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211593%2fhangman-written-in-c%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211593%2fhangman-written-in-c%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Costa Masnaga

          Fotorealismo

          Sidney Franklin