Files
csse2310-a1/wordle.c
2025-03-21 10:51:17 +10:00

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