diff --git a/test.c b/test.c index f955a189f672cf9c84db24ff4b399d81b99fb78d..bbcc0d935b10310b801135afb54f5d1251c071ea 100644 --- a/test.c +++ b/test.c @@ -21,7 +21,7 @@ int main(void) { RUN_TEST(test_answer_shoud_be_balai); RUN_TEST(test_validate_letter_should_be_false); RUN_TEST(test_validate_letter_should_be_true); - RUN_TEST(test_generate_pattern_should_return_all_0); + // RUN_TEST(test_generate_pattern_should_return_all_0); // Tool RUN_TEST(test_compute_matches_should_be_empty); diff --git a/tool/test/tool-test.c b/tool/test/tool-test.c index 59152a23c4889552766b2d87af5d202870ad9b20..663f48a1a21f2d555cf71918afde9d737bb9d260 100644 --- a/tool/test/tool-test.c +++ b/tool/test/tool-test.c @@ -9,10 +9,12 @@ void test_compute_matches_should_be_empty(void) { set_try("BRUTE"); Pattern *pattern = generate_pattern(); possibility_t **p = compute_matches("BRUTE", pattern); + save_computed_matches(p); - printf("%d\n", get_remaining_bank_count()); - for (int i = 0; i < get_remaining_bank_count(); i++) - printf("%s\n", p[i]->word); + set_try("SALUT"); + pattern = generate_pattern(); + p = compute_matches("SALUT", pattern); + save_computed_matches(p); free(pattern); destroy_possibilities(p); diff --git a/tool/tool.c b/tool/tool.c index 8ebacd478da9f1f5de57723735b781eff62784ac..9da79376e7522461df560c90d85af5ac829486bf 100644 --- a/tool/tool.c +++ b/tool/tool.c @@ -8,13 +8,172 @@ char **remaining_bank; int remaining_bank_count; possibility_t **possibilities; +/** + * @brief Create a possibility + * + * @param word + * @param entropy + * @return + */ possibility_t *create_possibility(char *word, double entropy) { possibility_t *p = malloc(sizeof(possibility_t)); - p->word = word; + p->word = calloc(WORD_LENGHT + 1, sizeof(char)); + strcpy(p->word, word); p->entropy = entropy; return p; } +/** + * @brief Destroiy a given matches set + * + * @param matches + */ +void destroy_matches(matches_t *matches) { + for (int i = 0; i < matches->count; i++) + free(matches->words[i]); + free(matches->words); + free(matches); +} + +/** + * @brief Filter the remaining bank + * + * @param word + * @param pattern + * @return + */ +matches_t *filter_out_remaining_bank(char *word, Pattern *pattern, int print) { + matches_t *matches = malloc(sizeof(matches_t)); + matches->count = 0; + + if (print) + printf("%s\n\n", word); + + // Create empty word matched array + char tmp[remaining_bank_count][WORD_LENGHT + 1]; + for (int i = 0; i < remaining_bank_count; i++) + *tmp[i] = *"\0"; + + // Check validity for each remaning word + for (int j = 0; j < remaining_bank_count; j++) { + char current_word[WORD_LENGHT + 1]; + strcpy(current_word, remaining_bank[j]); + + bool valid_word = true; + for (int i = 0; i < WORD_LENGHT; i++) { + // Forced to create a string of only 1 char to get his position in the word + char current_char[2] = {word[i], '\0'}; + + // Filter out the non-valid words + if (pattern[i] == WRONG && (int)strcspn(current_word, current_char) != WORD_LENGHT) { + valid_word = false; + break; + } + + if ((pattern[i] == MISPLACED && (int)strcspn(current_word, current_char) == WORD_LENGHT) || + (pattern[i] == CORRECT && (int)strcspn(current_word, current_char) != i)) + valid_word = false; + + // Clear the current char to not impact futur letter checks + current_word[i] = ' '; + } + + if (!valid_word) + continue; + + strcpy(tmp[j], remaining_bank[j]); + matches->count++; + } + + matches->words = malloc(matches->count * sizeof(char *)); + int idx = 0; + for (int i = 0; i < remaining_bank_count; i++) { + if (strlen(tmp[i]) != 0) { + matches->words[idx] = calloc(WORD_LENGHT + 1, sizeof(char)); + strcpy(matches->words[idx], tmp[i]); + idx++; + } + } + + return matches; +} + +/** + * @brief Compute the entropy of a given word + * + * @param word + * @return + */ +double compute_entropy(char *word) { + double sum = 0.0; + + Pattern p[4] = {WRONG, MISPLACED, CORRECT, NAP}; + Pattern *list[WORD_LENGHT] = {p, p, p, p, p}; + Pattern *curr[WORD_LENGHT]; + + int n = sizeof(list) / sizeof(*list); + bool done = false; + + for (int i = 0; i < n; i++) + curr[i] = list[i]; + + // Compute the product itself (0, 0, 0, 0, 0), (1, 0, 0, 0, 0), (2, 0, 0, 0, 0), (0, 1, 0, 0, 0), etc... + while (!done) { + // Compute current pattern entropy + matches_t *matches = filter_out_remaining_bank(word, (Pattern[WORD_LENGHT]){*curr[0], *curr[1], *curr[2], *curr[3], *curr[4]}, 0); + + // Skip unmatch pattern + if (matches->count == 0) { + destroy_matches(matches); + done = true; + break; + } + + // Apply Shannon's formula + double px = ((double)matches->count / (double)remaining_bank_count); + sum += px * (log2(1 / px)); + destroy_matches(matches); + + // Go to next pattern + int pattern_inc = 0; + curr[pattern_inc]++; + + // Loop over the pattern and increment next position + while (*curr[pattern_inc] == NAP) { + curr[pattern_inc] = list[pattern_inc]; + pattern_inc++; + + if (pattern_inc == n) { + done = true; + break; + } + + curr[pattern_inc]++; + } + } + + return sum; +} + +/** + * @brief Compare two given possibilites + * + * @param p1 + * @param p2 + * @return + */ +int compare_possibilities(const void *s1, const void *s2) { + const possibility_t *p1 = *(possibility_t **)s1; + const possibility_t *p2 = *(possibility_t **)s2; + + if (p1->entropy < p2->entropy) + return -1; + else if (p1->entropy > p2->entropy) + return 1; + else + return 0; +} + //========================== // PUBLIC //========================== @@ -36,66 +195,45 @@ void destroy_tool() { } void destroy_possibilities(possibility_t **p) { - for (int i = 0; i < possibility_count; i++) + for (int i = 0; i < possibility_count; i++) { + free(p[i]->word); free(p[i]); + } free(p); } possibility_t **compute_matches(char *word, Pattern *pattern) { - possibility_count = 0; - char tmp_poss[remaining_bank_count][WORD_LENGHT + 1]; + matches_t *matches = filter_out_remaining_bank(word, pattern, 1); - for (int i = 0; i < remaining_bank_count; i++) - *tmp_poss[i] = *"\0"; + for (int i = 0; i < matches->count; i++) + printf("%s\n", matches->words[i]); - for (int j = 0; j < remaining_bank_count; j++) { - char current_word[WORD_LENGHT + 1]; - strcpy(current_word, remaining_bank[j]); - - bool valid_word = true; - for (int i = 0; i < WORD_LENGHT; i++) { - // Forced to create a string of only 1 char to get his position in the word - char current_char[2] = {word[i], '\0'}; + possibility_count = matches->count; - // Filter out the non-valid words - if ((pattern[i] == WRONG && (int)strcspn(current_word, current_char) != WORD_LENGHT) || - (pattern[i] == MISPLACED && (int)strcspn(current_word, current_char) == WORD_LENGHT) || - (pattern[i] == CORRECT && (int)strcspn(current_word, current_char) != i)) - valid_word = false; + // Create the new possibilities list + possibilities = calloc(possibility_count, sizeof(possibility_t *)); + for (int i = 0; i < possibility_count; i++) + possibilities[i] = create_possibility(matches->words[i], compute_entropy(matches->words[i])); - // Clear the current char to not impact futur letter checks - current_word[i] = ' '; - } + destroy_matches(matches); - if (!valid_word) - continue; + // Sort possibilities by entropy ASC + qsort(possibilities, possibility_count, sizeof(possibility_t *), compare_possibilities); - strcpy(tmp_poss[j], remaining_bank[j]); - possibility_count++; - } + return possibilities; +} +void save_computed_matches(possibility_t **matches) { // Free non-matching words for (int i = possibility_count; i < remaining_bank_count; i++) free(remaining_bank[i]); - int previous_remaining_count = remaining_bank_count; - // Realloc for the new matching words remaining_bank_count = possibility_count; remaining_bank = realloc(remaining_bank, remaining_bank_count * sizeof(char *)); - // Create the new possibilities list - possibilities = calloc(remaining_bank_count, sizeof(possibility_t *)); - int idx = 0; - for (int i = 0; i < previous_remaining_count; i++) { - if (strlen(tmp_poss[i]) != 0) { - strcpy(remaining_bank[idx], tmp_poss[i]); - possibilities[idx] = create_possibility(tmp_poss[i], 0.0); - idx++; - } - } - - return possibilities; + for (int i = 0; i < remaining_bank_count; i++) + strcpy(remaining_bank[i], matches[i]->word); } int get_remaining_bank_count() { return remaining_bank_count; } diff --git a/tool/tool.h b/tool/tool.h index b7b855df1e3f0067ce39d45319c28b689ccd1a4b..edc608032a55932e1918e9c2ac27cf83f9fa63d4 100644 --- a/tool/tool.h +++ b/tool/tool.h @@ -3,6 +3,7 @@ #include "../word-bank/bank.h" #include "../wordle/wordle.h" +#include <math.h> #include <stdbool.h> // Tool maximum possibility set @@ -13,6 +14,11 @@ typedef struct _possibility_t { double entropy; } possibility_t; +typedef struct _matches_t { + char **words; + int count; +} matches_t; + /** * @brief Initialize the tool */ @@ -38,6 +44,13 @@ void destroy_possibilities(possibility_t **p); */ possibility_t **compute_matches(char *word, Pattern *pattern); +/** + * @brief Save computed matches + * + * @param possibilities + */ +void save_computed_matches(possibility_t **matches); + /** * @brief Get the remaning bank count * diff --git a/wordle/wordle.h b/wordle/wordle.h index 0c1f3a8ab0b5c6387e23bed2cd77a88cc41dad52..3c6c0fd99dbad815294bb0fcd72771f265030172 100644 --- a/wordle/wordle.h +++ b/wordle/wordle.h @@ -13,7 +13,7 @@ // Enums typedef enum Gamemode { SOLO, VERSUS, TOOL_ASSISTED } Gamemode; -typedef enum Pattern { WRONG, MISPLACED, CORRECT } Pattern; +typedef enum Pattern { WRONG, MISPLACED, CORRECT, NAP } Pattern; // NAP => Not A Pattern (Only used when computing all possible patterns) /** * @brief Initialize the game