Hangman written in C












1












$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 = sizeof(words)/sizeof(words[0]);

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);
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);

// 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
#pragma once
#include



// 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 print_count_underscores(const char **word_to_guess) {
size_t num_underscores = 0;
while (*word_to_guess) {
printf("%c ", **word_to_guess);
if (**word_to_guess++ == '_') {
num_underscores++;
}
}
return num_underscores;
}

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


utils.h



#pragma once

#include <stdlib.h>

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

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









share|improve this question











$endgroup$








  • 1




    $begingroup$
    char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
    $endgroup$
    – chux
    25 mins ago










  • $begingroup$
    @chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
    $endgroup$
    – 200_success
    1 min ago
















1












$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 = sizeof(words)/sizeof(words[0]);

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);
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);

// 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
#pragma once
#include



// 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 print_count_underscores(const char **word_to_guess) {
size_t num_underscores = 0;
while (*word_to_guess) {
printf("%c ", **word_to_guess);
if (**word_to_guess++ == '_') {
num_underscores++;
}
}
return num_underscores;
}

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


utils.h



#pragma once

#include <stdlib.h>

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

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









share|improve this question











$endgroup$








  • 1




    $begingroup$
    char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
    $endgroup$
    – chux
    25 mins ago










  • $begingroup$
    @chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
    $endgroup$
    – 200_success
    1 min ago














1












1








1





$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 = sizeof(words)/sizeof(words[0]);

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);
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);

// 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
#pragma once
#include



// 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 print_count_underscores(const char **word_to_guess) {
size_t num_underscores = 0;
while (*word_to_guess) {
printf("%c ", **word_to_guess);
if (**word_to_guess++ == '_') {
num_underscores++;
}
}
return num_underscores;
}

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


utils.h



#pragma once

#include <stdlib.h>

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

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









share|improve this question











$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 = sizeof(words)/sizeof(words[0]);

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);
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);

// 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
#pragma once
#include



// 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 print_count_underscores(const char **word_to_guess) {
size_t num_underscores = 0;
while (*word_to_guess) {
printf("%c ", **word_to_guess);
if (**word_to_guess++ == '_') {
num_underscores++;
}
}
return num_underscores;
}

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


utils.h



#pragma once

#include <stdlib.h>

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

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






c hangman c99






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 2 mins ago









200_success

129k15152414




129k15152414










asked 1 hour ago









FarazFaraz

307110




307110








  • 1




    $begingroup$
    char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
    $endgroup$
    – chux
    25 mins ago










  • $begingroup$
    @chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
    $endgroup$
    – 200_success
    1 min ago














  • 1




    $begingroup$
    char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
    $endgroup$
    – chux
    25 mins ago










  • $begingroup$
    @chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
    $endgroup$
    – 200_success
    1 min ago








1




1




$begingroup$
char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
$endgroup$
– chux
25 mins ago




$begingroup$
char **word_to_guess = malloc(word_size * sizeof(word)); looks wrong. Suggest char **word_to_guess = malloc(word_size * sizeof *word_to_guess);
$endgroup$
– chux
25 mins ago












$begingroup$
@chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
$endgroup$
– 200_success
1 min ago




$begingroup$
@chux Please put all suggestions for improvements in answers, even if the suggestion is trivial.
$endgroup$
– 200_success
1 min ago










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

How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

is 'sed' thread safe

How to make a Squid Proxy server?