init
This commit is contained in:
368
wordle.c
Normal file
368
wordle.c
Normal file
@@ -0,0 +1,368 @@
|
||||
#include "wordle.h"
|
||||
|
||||
// TODO: Use enums for error codes
|
||||
|
||||
char* get_flag_from_argument(char* argument) {
|
||||
char* flag = (char*)malloc(sizeof(char) * (strlen(argument) + 1));
|
||||
if (!strcpy(flag, argument)) {
|
||||
fprintf(stderr, "Could not parse argument \"%s\", exiting...\n",
|
||||
argument);
|
||||
exit(E_ARGS);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
int add_to_used_flags(
|
||||
char* flag, char usedFlags[MAX_FLAGS][5], int* usedFlagIndex) {
|
||||
// Make sure command has not already been used
|
||||
for (int i = 0; i < (*usedFlagIndex) + 1; i++) {
|
||||
if (strcmp(usedFlags[i], flag) == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Add command to used commands to make sure there are no
|
||||
// duplicates
|
||||
strcpy(usedFlags[*usedFlagIndex], flag);
|
||||
(*usedFlagIndex)++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int check_valid_value(char* value) {
|
||||
for (int i = 0; i < strlen(value); i++) {
|
||||
if (!isdigit(value[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void update_file_path_from_argument(char** filePath, char* argument) {
|
||||
*filePath =
|
||||
(char*)realloc(*filePath, sizeof(char) * (strlen(argument) + 1));
|
||||
if (!strcpy(*filePath, argument)) {
|
||||
fprintf(stderr, "Could not parse file path, exiting...\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int get_valid_command_arguments(int argc, char* argv[], int* wordLength,
|
||||
int* maxGuesses, char** filePath) {
|
||||
char usedFlags[MAX_FLAGS][5];
|
||||
int usedFlagIndex = 0;
|
||||
char* flag = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
flag = get_flag_from_argument(argv[i]);
|
||||
if (flag[0] == '-') {
|
||||
int value = 0;
|
||||
|
||||
// Check conditions and move index to argument index
|
||||
if (!add_to_used_flags(flag, usedFlags, &usedFlagIndex) ||
|
||||
(++i) >= argc) {
|
||||
free(flag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if the argument is a valid integer
|
||||
value = atoi(argv[i]);
|
||||
if (!check_valid_value(argv[i]) || !value || value < MIN_RANGE ||
|
||||
value > MAX_RANGE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the option matches, update its corresponding value
|
||||
if (strcmp(flag, "-len") == 0) {
|
||||
*wordLength = value;
|
||||
} else if (strcmp(flag, "-max") == 0) {
|
||||
*maxGuesses = value;
|
||||
} else {
|
||||
free(flag);
|
||||
return 0;
|
||||
}
|
||||
} else if (i == argc - 1) {
|
||||
update_file_path_from_argument(filePath, argv[i]);
|
||||
} else {
|
||||
free(flag);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
free(flag);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int check_invalid_word(char* word, int wordLength) {
|
||||
if (strlen(word) != wordLength) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < strlen(word); i++) {
|
||||
if (!isalpha(word[i])) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_dictionary(Dictionary* dictionary) {
|
||||
for (int i = 0; i < dictionary->size; i++) {
|
||||
free(dictionary->contents[i]);
|
||||
}
|
||||
free(dictionary->contents);
|
||||
}
|
||||
|
||||
void insert_word_into_dictionary(Dictionary* dictionary, char* word) {
|
||||
// Increase dictionary size
|
||||
dictionary->size++;
|
||||
dictionary->contents = (char**)realloc(
|
||||
dictionary->contents, sizeof(char*) * dictionary->size);
|
||||
|
||||
// Allocate space and insert new word
|
||||
dictionary->contents[dictionary->size - 1] =
|
||||
(char*)malloc(sizeof(char) * (strlen(word) + 1));
|
||||
strcpy(dictionary->contents[dictionary->size - 1], word);
|
||||
}
|
||||
|
||||
Dictionary get_new_dictionary() {
|
||||
Dictionary dictionary;
|
||||
dictionary.size = 0;
|
||||
dictionary.contents = (char**)malloc(sizeof(char*));
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
Dictionary get_dictionary_from_buffer(FILE* buffer) {
|
||||
Dictionary dictionary = get_new_dictionary();
|
||||
|
||||
char line[MAX_GUESS_LENGTH] = {'\0'};
|
||||
while (fgets(line, MAX_GUESS_LENGTH, buffer)) {
|
||||
// Remove newline
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
|
||||
if (check_invalid_word(line, strlen(line))) {
|
||||
continue;
|
||||
}
|
||||
string_lowercase(line);
|
||||
|
||||
// Won't check if word in already as lookup times would be huge
|
||||
insert_word_into_dictionary(&dictionary, line);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
Dictionary get_dictionary_words_of_length(
|
||||
Dictionary dictionary, int wordLength) {
|
||||
Dictionary sizedDictionary = get_new_dictionary();
|
||||
for (int i = 0; i < dictionary.size; i++) {
|
||||
if (strlen(dictionary.contents[i]) == wordLength) {
|
||||
insert_word_into_dictionary(
|
||||
&sizedDictionary, dictionary.contents[i]);
|
||||
}
|
||||
}
|
||||
return sizedDictionary;
|
||||
}
|
||||
|
||||
int check_word_in_dictionary(Dictionary dictionary, char* word) {
|
||||
if (dictionary.size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dictionary.size; i++) {
|
||||
if (strcmp(dictionary.contents[i], word) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* get_blank_wordle(int wordLength) {
|
||||
char* wordleWord = (char*)malloc(sizeof(char) * (wordLength + 1));
|
||||
memset(wordleWord, '-', wordLength);
|
||||
wordleWord[wordLength] = '\0';
|
||||
return wordleWord;
|
||||
}
|
||||
|
||||
char* get_wordle_format(char* guessedWord, char* answer) {
|
||||
char* modifiedAnswer = (char*)malloc(sizeof(char) * (strlen(answer) + 1));
|
||||
char* wordleWord = get_blank_wordle(strlen(answer));
|
||||
|
||||
strcpy(modifiedAnswer, answer);
|
||||
|
||||
// First parse, match all exact matches
|
||||
for (int i = 0; i < strlen(answer); i++) {
|
||||
if (guessedWord[i] == modifiedAnswer[i]) {
|
||||
wordleWord[i] = toupper(modifiedAnswer[i]);
|
||||
modifiedAnswer[i] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
// Second parse, match everything else
|
||||
for (int i = 0; i < strlen(answer); i++) {
|
||||
// If the character has already been matched
|
||||
if (wordleWord[i] != '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sourced with inspiration from:
|
||||
// https://stackoverflow.com/questions/1479386/
|
||||
const char* ptr;
|
||||
if ((ptr = strchr(modifiedAnswer, guessedWord[i]))) {
|
||||
int characterIndex = ptr - modifiedAnswer;
|
||||
|
||||
wordleWord[i] = tolower(guessedWord[i]);
|
||||
modifiedAnswer[characterIndex] = '-';
|
||||
}
|
||||
}
|
||||
free(modifiedAnswer);
|
||||
return wordleWord;
|
||||
}
|
||||
|
||||
void string_lowercase(char* string) {
|
||||
for (int i = 0; i < strlen(string); i++) {
|
||||
string[i] = tolower(string[i]);
|
||||
}
|
||||
}
|
||||
|
||||
char* get_guessed_word_from_input(void) {
|
||||
char* guessedWord = (char*)malloc(sizeof(char) * MAX_GUESS_LENGTH);
|
||||
|
||||
char buffer[MAX_GUESS_LENGTH];
|
||||
if (fgets(buffer, MAX_GUESS_LENGTH, stdin)) {
|
||||
// Remove newline character
|
||||
buffer[strcspn(buffer, "\n")] = '\0';
|
||||
if (strlen(buffer) == 0) {
|
||||
// Words cannot be smaller than 3 so this is a quick and
|
||||
// dirty hack
|
||||
strcpy(buffer, "a");
|
||||
}
|
||||
guessedWord = (char*)realloc(
|
||||
guessedWord, sizeof(char) * (strlen(buffer) + 1));
|
||||
strcpy(guessedWord, buffer);
|
||||
} else {
|
||||
free(guessedWord);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return guessedWord;
|
||||
}
|
||||
|
||||
void prompt_invalid_guess(int invalidType, int wordLength) {
|
||||
if (invalidType == 1) {
|
||||
printf("Words must be %d letters long - try again.\n", wordLength);
|
||||
} else if (invalidType == 2) {
|
||||
printf("Words must contain only letters - try again.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void print_attempt_prompt(int guessesRemaining, int wordLength) {
|
||||
if (guessesRemaining > 1) {
|
||||
printf("Enter a %d letter word (%d attempts remaining):\n", wordLength,
|
||||
guessesRemaining);
|
||||
} else if (guessesRemaining == 1) {
|
||||
printf("Enter a %d letter word (last attempt):\n", wordLength);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: split this into smaller functions
|
||||
int game_loop(
|
||||
int wordLength, int maxGuesses, char* answer, Dictionary dictionary) {
|
||||
int gameWon = 0;
|
||||
int guessesRemaining = maxGuesses;
|
||||
printf(WELCOME_MESSAGE);
|
||||
while (!gameWon && guessesRemaining >= 1) {
|
||||
int invalidGuess = 1;
|
||||
|
||||
// Print prompt
|
||||
print_attempt_prompt(guessesRemaining, wordLength);
|
||||
|
||||
// Get user input, break on EOF
|
||||
char* guessedWord = get_guessed_word_from_input();
|
||||
if (!guessedWord) {
|
||||
free(guessedWord);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if word is valid
|
||||
invalidGuess = check_invalid_word(guessedWord, wordLength);
|
||||
if (invalidGuess) {
|
||||
prompt_invalid_guess(invalidGuess, wordLength);
|
||||
free(guessedWord);
|
||||
continue;
|
||||
}
|
||||
|
||||
string_lowercase(guessedWord);
|
||||
|
||||
// If the guessed word is correct
|
||||
if (strcmp(guessedWord, answer) == 0) {
|
||||
printf("Correct!\n");
|
||||
gameWon = 1;
|
||||
free(guessedWord);
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
if (!check_word_in_dictionary(dictionary, guessedWord)) {
|
||||
printf("Word not found in the dictionary - try again.\n");
|
||||
free(guessedWord);
|
||||
continue;
|
||||
}
|
||||
|
||||
char* wordleWord = get_wordle_format(guessedWord, answer);
|
||||
printf("%s\n", wordleWord);
|
||||
|
||||
free(wordleWord);
|
||||
free(guessedWord);
|
||||
guessesRemaining--;
|
||||
}
|
||||
fprintf(stderr, "Bad luck - the word is \"%s\".\n", answer);
|
||||
return E_LOST;
|
||||
}
|
||||
|
||||
void get_answer(char** answer, int wordLength) {
|
||||
char* tmpAnswer = get_random_word(wordLength);
|
||||
*answer = (char*)realloc(*answer, sizeof(char) * (strlen(tmpAnswer) + 1));
|
||||
strcpy(*answer, tmpAnswer);
|
||||
free(tmpAnswer);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
char* answer = NULL;
|
||||
int wordLength = DEFAULT_WORD_LENGTH;
|
||||
int maxGuesses = DEFAULT_MAX_GUESSES;
|
||||
char* filePath = (char*)malloc(
|
||||
sizeof(char) * (strlen(DEFAULT_DICTIONARY_PATH) + 1));
|
||||
strcpy(filePath, DEFAULT_DICTIONARY_PATH);
|
||||
|
||||
// clang-format off
|
||||
if (argc > 6 || !get_valid_command_arguments(
|
||||
argc, argv, &wordLength, &maxGuesses, &filePath)) {
|
||||
fprintf(stderr, USAGE_MESSAGE);
|
||||
return E_ARGS;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
FILE* dictionaryBuffer = fopen(filePath, "r");
|
||||
if (!dictionaryBuffer) {
|
||||
fprintf(stderr, FILE_ERROR, filePath);
|
||||
return E_FILE;
|
||||
}
|
||||
free(filePath);
|
||||
|
||||
get_answer(&answer, wordLength);
|
||||
|
||||
// Load dictionary and get words of the required word length
|
||||
Dictionary dictionary = get_dictionary_from_buffer(dictionaryBuffer);
|
||||
Dictionary sizedDictionary =
|
||||
get_dictionary_words_of_length(dictionary, wordLength);
|
||||
|
||||
fclose(dictionaryBuffer);
|
||||
|
||||
int gameResult =
|
||||
game_loop(wordLength, maxGuesses, answer, sizedDictionary);
|
||||
|
||||
free(answer);
|
||||
free_dictionary(&dictionary);
|
||||
free_dictionary(&sizedDictionary);
|
||||
|
||||
return gameResult;
|
||||
}
|
||||
Reference in New Issue
Block a user