369 lines
10 KiB
C
369 lines
10 KiB
C
#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;
|
|
}
|