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