Hangman written in C
$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);
c hangman c99
$endgroup$
add a comment |
$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);
c hangman c99
$endgroup$
1
$begingroup$
char **word_to_guess = malloc(word_size * sizeof(word));
looks wrong. Suggestchar **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
add a comment |
$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);
c hangman c99
$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
c hangman c99
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. Suggestchar **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
add a comment |
1
$begingroup$
char **word_to_guess = malloc(word_size * sizeof(word));
looks wrong. Suggestchar **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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
1
$begingroup$
char **word_to_guess = malloc(word_size * sizeof(word));
looks wrong. Suggestchar **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