ChiaraStellata

Wordle Solver (hard mode tree walk C++)

Jan 22nd, 2022
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 60.24 KB | None | 0 0
  1. #include <iostream>
  2. #include <fstream>
  3. #include <string>
  4. #include <vector>
  5. #include <map>
  6. #include <chrono>
  7. #include <cstdint>
  8. #include <algorithm>
  9.  
  10. using namespace std;
  11. using namespace std::chrono;
  12.  
  13. // From https://www-cs-faculty.stanford.edu/~knuth/sgb-words.txt
  14. // const char* ANSWER_WORD_FILE = "sgb-words.txt";
  15. // const char* GUESS_WORD_FILE = "sgb-words.txt";
  16. // From https://boardgames.stackexchange.com/a/38386/3054
  17. // const char* ANSWER_WORD_FILE = "Collins Scrabble Words (2019).txt";
  18. // const char* GUESS_WORD_FILE = "Collins Scrabble Words (2019).txt";
  19. // From http://www.mieliestronk.com/corncob_caps.txt
  20. // const char* ANSWER_WORD_FILE = "corncob_caps.txt";
  21. // const char* GUESS_WORD_FILE = "corncob_caps.txt";
  22. // From https://github.com/first20hours/google-10000-english/blob/master/google-10000-english.txt
  23. // const char* ANSWER_WORD_FILE = "google-10000-english.txt";
  24. // const char* GUESS_WORD_FILE = "google-10000-english.txt";
  25. // From https://github.com/dwyl/english-words
  26. // const char* ANSWER_WORD_FILE = "words.txt";
  27. // const char* GUESS_WORD_FILE = "words.txt";
  28. // From https://github.com/Kinkelin/WordleCompetition/blob/main/data/official/combined_wordlist.txt
  29. // const char* ANSWER_WORD_FILE = "combined_wordlist.txt";
  30. // const char* GUESS_WORD_FILE = "combined_wordlist.txt";
  31. // const char* GUESS_WORD_FILE = "combined_wordlist_rearranged.txt";
  32. // const char* GUESS_WORD_FILE = "combined_wordlist_real_wordles_first.txt";
  33. // From https://github.com/Kinkelin/WordleCompetition/blob/main/data/official/shuffled_real_wordles.txt
  34. const char* ANSWER_WORD_FILE = "shuffled_real_wordles.txt";
  35. const char* GUESS_WORD_FILE = "shuffled_real_wordles.txt";
  36.  
  37. int word_length;
  38. vector<char*> guess_words;
  39. vector<char*> answer_words;
  40. char* best_first_guess = NULL;
  41.  
  42. bool all_alpha(string s) {
  43.     for (unsigned int i = 0; i < s.length(); i++) {
  44.         if (!isalpha(s[i])) {
  45.             return false;
  46.         }
  47.     }
  48.     return true;
  49. }
  50.  
  51. void toupper_cstr(char* s) {
  52.     while (*s != '\0') {
  53.         *s = toupper(*s);
  54.         s++;
  55.     }
  56. }
  57.  
  58. vector<char*> read_word_file(const char* filename) {
  59.     vector<char*> result;
  60.     string line;
  61.     ifstream infile(filename);
  62.     int max_length = 0;
  63.     while (getline(infile, line)) {
  64.         if (all_alpha(line)) {
  65.             if (line.length() == word_length) {
  66.                 char* word = new char[word_length + 1];
  67.                 strcpy(word, line.c_str());
  68.                 toupper_cstr(word);
  69.                 result.push_back(word);
  70.             }
  71.         }
  72.     }
  73.     return result;
  74. }
  75.  
  76. // Hints are packed into unsigned integers, one ternary digit per bit, least-significant digit is last character
  77. // This is more space-efficient than using two bits per hint, which is important for cache efficiency.
  78. typedef uint64_t hint; // Use for number of words >= 21
  79. // typedef uint32_t hint; // Use for number of words <= 20
  80. hint num_hint_values; // max hint value plus one
  81.  
  82. hint get_wordle_hints(const char* guess, const char* answer) {
  83.     hint result = 0;
  84.     char buf[80];
  85.     strcpy(buf, answer);
  86.  
  87.     for (int i = 0; i < word_length; i++) {
  88.         if (guess[i] == answer[i]) {
  89.             buf[i] = ' ';
  90.         }
  91.     }
  92.     for (int i = 0; i < word_length; i++) {
  93.         result *= 3;
  94.         if (guess[i] == answer[i]) {
  95.             result += 2; // green
  96.         } else {
  97.             char* char_pos = strchr(buf, guess[i]);
  98.             if (char_pos != NULL) {
  99.                 result += 1; // yellow
  100.                 *char_pos = ' ';
  101.             }
  102.             // Otherwise black which is zero, do nothing
  103.         }
  104.     }
  105.     return result;
  106. }
  107.  
  108. hint string_to_hint(string hint_str) {
  109.     hint result = 0;
  110.     for (int i = 0; i < hint_str.length(); i++) {
  111.         result *= 3;
  112.         if (hint_str[i] == 'g') {
  113.             result += 2;
  114.         } else if (hint_str[i] == 'y') {
  115.             result += 1;
  116.         }
  117.     }
  118.     return result;
  119. }
  120.  
  121. string hint_to_string(hint hint) {
  122.     string result;
  123.     for (int i = 0; i < word_length; i++) {
  124.         char c;
  125.         if ((hint % 3) == 2) {
  126.             c = 'g';
  127.         } else if ((hint % 3) == 1) {
  128.             c = 'y';
  129.         } else {
  130.             c = 'k';
  131.         }
  132.         result = c + result;
  133.         hint /= 3;
  134.     }
  135.     return result;
  136. }
  137.  
  138. struct guess {
  139.     guess(char* word, hint hint) {
  140.         this->word = word;
  141.         this->hint = hint;
  142.     }
  143.  
  144.     char* word;
  145.     hint hint;
  146. };
  147.  
  148. bool matches_hints(const char* answer, const vector<guess>& guesses_so_far) {
  149.     for (int i = 0; i < guesses_so_far.size(); i++) {
  150.         if (get_wordle_hints(guesses_so_far[i].word, answer) != guesses_so_far[i].hint) {
  151.             return false;
  152.         }
  153.     }
  154.     return true;
  155. }
  156.  
  157. // In Wordle hard mode, black/grey hints do *not* have to be used, only yellow and green
  158. bool matches_hints_guess(const char* guess_word, const vector<guess>& guesses_so_far) {
  159.     for (int i = 0; i < guesses_so_far.size(); i++) {
  160.         hint h1 = guesses_so_far[i].hint;
  161.         hint h2 = get_wordle_hints(guesses_so_far[i].word, guess_word);
  162.  
  163.         for (int i = 0; i < word_length; i++) {
  164.             int color1 = h1 % 3;
  165.             int color2 = h2 % 3;
  166.             if (color1 != 0 && color1 != color2) {
  167.                 return false;
  168.             }
  169.             h1 /= 3;
  170.             h2 /= 3;
  171.         }
  172.     }
  173.     return true;
  174. }
  175.  
  176. int count_valid_words(const vector<char*>& answer_words, const vector<guess>& guesses_so_far) {
  177.     int count = 0;
  178.     for (int i = 0; i < answer_words.size(); i++) {
  179.         if (matches_hints(answer_words[i], guesses_so_far)) {
  180.             count++;
  181.         }
  182.     }
  183.     return count;
  184. }
  185.  
  186. vector<char*> get_valid_words(const vector<char*>& answer_words, const vector<guess>& guesses_so_far) {
  187.     vector<char*> result;
  188.     for (int i = 0; i < answer_words.size(); i++) {
  189.         if (matches_hints(answer_words[i], guesses_so_far)) {
  190.             result.push_back(answer_words[i]);
  191.         }
  192.     }
  193.     return result;
  194. }
  195.  
  196. vector<char*> get_valid_guesses(const vector<char*>& guess_words, const vector<guess>& guesses_so_far) {
  197.     vector<char*> result;
  198.     for (int i = 0; i < guess_words.size(); i++) {
  199.         if (matches_hints_guess(guess_words[i], guesses_so_far)) {
  200.             result.push_back(guess_words[i]);
  201.         }
  202.     }
  203.     return result;
  204. }
  205.  
  206. inline bool should_prefer_dense(int num_words) {
  207.      return (num_hint_values / num_words) < 250;  // Experimentally determined constant
  208. }
  209.  
  210. bool is_among_guesses(char* word, const vector<guess>& guesses_so_far) {
  211.     for (int i = 0; i < guesses_so_far.size(); i++) {
  212.         if (strcmp(word, guesses_so_far[i].word) == 0) {
  213.             return true;
  214.         }
  215.     }
  216.     return false;
  217. }
  218.  
  219. map<char*, bool> forbidden_first_guesses;
  220.  
  221. void forbid_first_guess(char* word) {
  222.     forbidden_first_guesses[word] = true;
  223.     best_first_guess = NULL;
  224. }
  225.  
  226. int* hint_class_counts = NULL;
  227. char* get_best_guess(const vector<char*>& valid_words, bool& result_is_correct_word, const vector<guess>& guesses_so_far, int& rating) {
  228.     if (valid_words.size() == 0) {
  229.         rating = 0;
  230.         return NULL;
  231.     } else if (valid_words.size() == 1) {
  232.         result_is_correct_word = true;
  233.         rating = 0;
  234.         return valid_words[0];
  235.     }
  236.  
  237.     map<string, bool> valid_words_map;
  238.     for (int i = 0; i < valid_words.size(); i++) {
  239.         valid_words_map[valid_words[i]] = true;
  240.     }
  241.  
  242.     char* best_guess = NULL;
  243.     int best_guess_rating = INT_MAX;
  244.     bool best_matches_hints = false;
  245.  
  246.     if (should_prefer_dense(valid_words.size())) {
  247.         // Dense case - use direct array indexing
  248.         for (int i = 0; i < guess_words.size(); i++) {
  249.             if (guess_words[i][0] == '\0') {
  250.                 continue; // Word was removed from dictionary during guessing
  251.             }
  252.             memset(hint_class_counts, 0, sizeof(int) * num_hint_values);
  253.             bool skip_to_next_word = false;
  254.             for (int j = 0; j < valid_words.size(); j++) {
  255.                 int idx = get_wordle_hints(guess_words[i], valid_words[j]);
  256.                 hint_class_counts[idx]++;
  257.                 if (hint_class_counts[idx] > best_guess_rating) {
  258.                     skip_to_next_word = true;
  259.                     break;
  260.                 }
  261.             }
  262.             if (skip_to_next_word) {
  263.                 continue;
  264.             }
  265.  
  266.             int guess_rating = 0;
  267.             // Rating for this guess is max of all hint class counts
  268.             for (int j = 0; j < num_hint_values; j++) {
  269.                 if (hint_class_counts[j] >= guess_rating) {
  270.                     guess_rating = hint_class_counts[j];
  271.                 }
  272.             }
  273.             if (guess_rating < best_guess_rating && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  274.                 best_guess = guess_words[i];
  275.                 best_guess_rating = guess_rating;
  276.                 best_matches_hints = matches_hints(guess_words[i], guesses_so_far);
  277.             } else if (!best_matches_hints && guess_rating == best_guess_rating && valid_words_map.find(guess_words[i]) != valid_words_map.end() && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  278.                 // Among the best candidates, prioritize those that are valid words satisfying all the hints
  279.                 // so far, so that we have a chance to guess the word.
  280.                 best_guess = guess_words[i];
  281.                 best_guess_rating = guess_rating;
  282.                 best_matches_hints = true;
  283.             }
  284.         }
  285.     } else {
  286.         // Sparse case - use map
  287.         map<hint, int> hint_class_counts;
  288.         for (int i = 0; i < guess_words.size(); i++) {
  289.             hint_class_counts.clear();
  290.             bool skip_to_next_word = false;
  291.             for (int j = 0; j < valid_words.size(); j++) {
  292.                 int idx = get_wordle_hints(guess_words[i], valid_words[j]);
  293.                 hint_class_counts[idx]++;
  294.                 if (hint_class_counts[idx] > best_guess_rating) {
  295.                     skip_to_next_word = true;
  296.                     break;
  297.                 }
  298.             }
  299.             if (skip_to_next_word) {
  300.                 continue;
  301.             }
  302.  
  303.             int guess_rating = 0;
  304.             // Rating for this guess is max of all hint class counts
  305.             for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  306.                 if (iter->second >= guess_rating) {
  307.                     guess_rating = iter->second;
  308.                 }
  309.             }
  310.  
  311.             if (guess_rating < best_guess_rating && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  312.                 best_guess = guess_words[i];
  313.                 best_guess_rating = guess_rating;
  314.                 best_matches_hints = matches_hints(guess_words[i], guesses_so_far);
  315.             } else if (!best_matches_hints && guess_rating == best_guess_rating && valid_words_map.find(guess_words[i]) != valid_words_map.end() && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  316.                 // Among the best candidates, prioritize those that are valid words satisfying all the hints
  317.                 // so far, so that we have a chance to guess the word.
  318.                 best_guess = guess_words[i];
  319.                 best_guess_rating = guess_rating;
  320.                 best_matches_hints = true;
  321.             }
  322.         }
  323.     }
  324.  
  325.     result_is_correct_word = false;
  326.     rating = best_guess_rating;
  327.     return best_guess;
  328. }
  329.  
  330. char* get_worst_guess(const vector<char*>& valid_words, bool& result_is_correct_word, const vector<guess>& guesses_so_far, int& rating) {
  331.     if (valid_words.size() == 0) {
  332.         rating = 0;
  333.         return NULL;
  334.     } else if (valid_words.size() == 1) {
  335.         result_is_correct_word = true;
  336.         rating = 0;
  337.         return valid_words[0];
  338.     }
  339.  
  340.     map<string, bool> valid_words_map;
  341.     for (int i = 0; i < valid_words.size(); i++) {
  342.         valid_words_map[valid_words[i]] = true;
  343.     }
  344.  
  345.     char* best_guess = NULL;
  346.     int best_guess_rating = 0;
  347.     bool best_matches_hints = false;
  348.  
  349.     if (should_prefer_dense(valid_words.size())) {
  350.         // Dense case - use direct array indexing
  351.         for (int i = 0; i < guess_words.size(); i++) {
  352.             if (guess_words[i][0] == '\0') {
  353.                 continue; // Word was removed from dictionary during guessing
  354.             }
  355.             memset(hint_class_counts, 0, sizeof(int) * num_hint_values);
  356.             bool skip_to_next_word = false;
  357.             for (int j = 0; j < valid_words.size(); j++) {
  358.                 int idx = get_wordle_hints(guess_words[i], valid_words[j]);
  359.                 hint_class_counts[idx]++;
  360.                 // if (hint_class_counts[idx] > best_guess_rating) {
  361.                 //     skip_to_next_word = true;
  362.                 //     break;
  363.                 // }
  364.             }
  365.             if (skip_to_next_word) {
  366.                 continue;
  367.             }
  368.  
  369.             int guess_rating = 0;
  370.             // Rating for this guess is max of all hint class counts
  371.             for (int j = 0; j < num_hint_values; j++) {
  372.                 if (hint_class_counts[j] >= guess_rating) {
  373.                     guess_rating = hint_class_counts[j];
  374.                 }
  375.             }
  376.             if (guess_rating > best_guess_rating && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  377.                 best_guess = guess_words[i];
  378.                 best_guess_rating = guess_rating;
  379.                 best_matches_hints = matches_hints(guess_words[i], guesses_so_far);
  380.             } else if (best_matches_hints && guess_rating == best_guess_rating && valid_words_map.find(guess_words[i]) == valid_words_map.end() && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  381.                 // Among the best candidates, prioritize those that are valid words satisfying all the hints
  382.                 // so far, so that we have a chance to guess the word.
  383.                 best_guess = guess_words[i];
  384.                 best_guess_rating = guess_rating;
  385.                 best_matches_hints = true;
  386.             }
  387.         }
  388.     } else {
  389.         // Sparse case - use map
  390.         map<hint, int> hint_class_counts;
  391.         for (int i = 0; i < guess_words.size(); i++) {
  392.             hint_class_counts.clear();
  393.             bool skip_to_next_word = false;
  394.             for (int j = 0; j < valid_words.size(); j++) {
  395.                 int idx = get_wordle_hints(guess_words[i], valid_words[j]);
  396.                 hint_class_counts[idx]++;
  397.                 // if (hint_class_counts[idx] > best_guess_rating) {
  398.                 //     skip_to_next_word = true;
  399.                 //     break;
  400.                 // }
  401.             }
  402.             if (skip_to_next_word) {
  403.                 continue;
  404.             }
  405.  
  406.             int guess_rating = 0;
  407.             // Rating for this guess is max of all hint class counts
  408.             for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  409.                 if (iter->second >= guess_rating) {
  410.                     guess_rating = iter->second;
  411.                 }
  412.             }
  413.  
  414.             if (guess_rating > best_guess_rating && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  415.                 best_guess = guess_words[i];
  416.                 best_guess_rating = guess_rating;
  417.                 best_matches_hints = matches_hints(guess_words[i], guesses_so_far);
  418.             } else if (best_matches_hints && guess_rating == best_guess_rating && valid_words_map.find(guess_words[i]) == valid_words_map.end() && !is_among_guesses(guess_words[i], guesses_so_far) && !(guesses_so_far.size() == 0 && forbidden_first_guesses.find(guess_words[i]) != forbidden_first_guesses.end())) {
  419.                 // Among the best candidates, prioritize those that are valid words satisfying all the hints
  420.                 // so far, so that we have a chance to guess the word.
  421.                 best_guess = guess_words[i];
  422.                 best_guess_rating = guess_rating;
  423.                 best_matches_hints = true;
  424.             }
  425.         }
  426.     }
  427.  
  428.     result_is_correct_word = false;
  429.     rating = best_guess_rating;
  430.     return best_guess;
  431. }
  432.  
  433. bool can_guess_word_max_depth(const vector<char*>& valid_words, char* word, const vector<guess>& guesses_so_far, int max_depth) {
  434.     if (valid_words.size() <= 1) {
  435.         return true;
  436.     }
  437.     if (max_depth == 0) {
  438.         return false;
  439.     }
  440.  
  441.     for (int i = 0; i < guess_words.size(); i++) {
  442.         if (is_among_guesses(guess_words[i], guesses_so_far)) {
  443.             continue;
  444.         }
  445.         vector<guess> guesses_so_far2 = guesses_so_far;
  446.         guesses_so_far2.push_back(guess(guess_words[i], get_wordle_hints(guess_words[i], word)));
  447.         vector<char*> valid_words2 = get_valid_words(valid_words, guesses_so_far2);
  448.         if (valid_words2.size() == valid_words.size()) {
  449.             continue; // No new information
  450.         }
  451.  
  452.         if (!can_guess_word_max_depth(valid_words2, word, guesses_so_far2, max_depth - 1)) {
  453.             return false;
  454.         }
  455.     }
  456.  
  457.     return true;
  458. }
  459.  
  460. void cache_best_first_guess() {
  461.     if (best_first_guess == NULL) {
  462.         vector<guess> guesses_so_far;
  463.         bool result_is_correct_word;
  464.         int rating;
  465.         cout << "Computing and caching best first guess..." << endl;
  466.         auto start = high_resolution_clock::now();
  467.         best_first_guess = get_best_guess(answer_words, result_is_correct_word, guesses_so_far, rating);
  468.         auto stop = high_resolution_clock::now();
  469.         cout << "Done, time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 << " s" << endl;
  470.         best_first_guess = _strdup("SNARE");
  471.     }
  472. }
  473.  
  474. struct pairi {
  475.     int i;
  476.     int j;
  477.  
  478.     bool operator<(const pairi p) const {
  479.         if (i < p.i)
  480.             return true;
  481.         else if (i > p.i)
  482.             return false;
  483.         else
  484.             return j < p.j;
  485.     }
  486. };
  487.  
  488. int thread_num;
  489. int num_threads;
  490.  
  491. void best_first_k() {
  492.     map<hint, int> hint_class_counts;
  493.     int best_worst_count_so_far = 7;
  494.     // int best_worst_count_so_far = 6; // NOLES CRAFT WIMPY
  495.     int best_pair_i = -1, best_pair_j = -1, best_pair_k = -1;
  496.  
  497.     char buf[300];
  498.     sprintf(buf, "results.small.txt", thread_num);
  499.     ofstream of(buf);
  500.     cout << "thread_num: " << thread_num << endl;
  501.     for (int i=thread_num; i < guess_words.size(); i+= num_threads) {
  502.         cout << "i: " << i << " " << guess_words[i] << endl;
  503.         for (int j=i+1; j < guess_words.size(); j++) {
  504.             if ((j % 1000) == 0) {
  505.                 cout << "i: " << i << " " << guess_words[i] << " j: " << j << " " << guess_words[j] << endl;
  506.             }
  507.  
  508.             hint_class_counts.clear();
  509.             for (int m = 0; m < answer_words.size(); m++) {
  510.                 int combined_hint = get_wordle_hints(guess_words[i], answer_words[m]) * num_hint_values +
  511.                     get_wordle_hints(guess_words[j], answer_words[m]);
  512.                 int count = hint_class_counts[combined_hint];
  513.                 hint_class_counts[combined_hint] = count + 1;
  514.             }
  515.  
  516.             int guess_rating = 0;
  517.             vector<guess> guesses_so_far;
  518.             for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  519.                 if (iter->second >= guess_rating) {
  520.                     guess_rating = iter->second;
  521.                     guesses_so_far.clear();
  522.                     guesses_so_far.push_back(guess(guess_words[i], iter->first / num_hint_values));
  523.                     guesses_so_far.push_back(guess(guess_words[j], iter->first % num_hint_values));
  524.                 }
  525.             }
  526.             vector<char*> valid_words_first_2 = get_valid_words(answer_words, guesses_so_far);
  527.  
  528.             for (int k = j + 1; k < guess_words.size(); k++) {
  529.                 hint_class_counts.clear();
  530.  
  531.                 bool exceeded_count = false;
  532.                 for (int m = 0; m < valid_words_first_2.size(); m++) {
  533.                     int combined_hint = get_wordle_hints(guess_words[i], valid_words_first_2[m]) * num_hint_values * num_hint_values +
  534.                         get_wordle_hints(guess_words[j], valid_words_first_2[m]) * num_hint_values +
  535.                         get_wordle_hints(guess_words[k], valid_words_first_2[m]);
  536.                     int count = hint_class_counts[combined_hint];
  537.                     hint_class_counts[combined_hint] = count + 1;
  538.                     if (count + 1 > best_worst_count_so_far) {
  539.                         exceeded_count = true;
  540.                         break;
  541.                     }
  542.                 }
  543.                 if (exceeded_count) {
  544.                     continue;
  545.                 }
  546.  
  547.                 for (int m = 0; m < answer_words.size(); m++) {
  548.                     int combined_hint = get_wordle_hints(guess_words[i], answer_words[m]) * num_hint_values * num_hint_values +
  549.                         get_wordle_hints(guess_words[j], answer_words[m]) * num_hint_values +
  550.                         get_wordle_hints(guess_words[k], answer_words[m]);
  551.                     int count = hint_class_counts[combined_hint];
  552.                     hint_class_counts[combined_hint] = count + 1;
  553.                     if (count + 1 > best_worst_count_so_far) {
  554.                         break;
  555.                     }
  556.                 }
  557.  
  558.                 int guess_rating = 0;
  559.                 for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  560.                     if (iter->second >= guess_rating) {
  561.                         guess_rating = iter->second;
  562.                     }
  563.                 }
  564.                 if (guess_rating < best_worst_count_so_far) {
  565.                     best_worst_count_so_far = guess_rating;
  566.                     best_pair_i = i;
  567.                     best_pair_j = j;
  568.                     best_pair_k = k;
  569.                     cout << guess_words[i] << " " << guess_words[j] << " " << guess_words[k] << " " << best_worst_count_so_far << endl;
  570.                     of << guess_words[i] << " " << guess_words[j] << " " << guess_words[k] << " " << best_worst_count_so_far << endl;
  571.                     of.flush();
  572.                 } else if (guess_rating == best_worst_count_so_far) {
  573.                     cout << guess_words[i] << " " << guess_words[j] << " " << guess_words[k] << " " << best_worst_count_so_far << endl;
  574.                     of << guess_words[i] << " " << guess_words[j] << " " << guess_words[k] << " " << best_worst_count_so_far << endl;
  575.                     of.flush();
  576.                 }
  577.             }
  578.         }
  579.     }
  580.  
  581.     cout << guess_words[best_pair_i] << " " << guess_words[best_pair_j] << " " << guess_words[best_pair_k] << " " << best_worst_count_so_far << endl;
  582.     of << guess_words[best_pair_i] << " " << guess_words[best_pair_j] << " " << guess_words[best_pair_k] << " " << best_worst_count_so_far << endl;
  583.     of.flush();
  584. }
  585.  
  586. void print_words(const vector<char*>& valid_words);
  587.  
  588. void best_4th() {
  589.     ifstream in("solutions6_minus.txt");
  590.     ofstream of("results.4th.txt");
  591.  
  592.     while (true) {
  593.         map<hint, int> hint_class_counts;
  594.         // int best_worst_count_so_far = 2;
  595.         // int best_worst_count_so_far = 6; // NOLES CRAFT WIMPY
  596.         int best_pair_i, best_pair_j = -1;
  597.  
  598.         cout << "Precomputing best first guess..." << endl;
  599.         cache_best_first_guess();
  600.         char* first_guess = best_first_guess;
  601. #if 1
  602.         string line;
  603.         getline(in, line);
  604.         if (in.eof()) break;
  605.         if (line.length() < word_length * 3 + 2) {
  606.             continue;
  607.         }
  608.         char* first_word = _strdup(line.c_str());
  609.         first_word[word_length] = '\0';
  610.         char* second_word = _strdup(line.c_str() + word_length + 1);
  611.         second_word[word_length] = '\0';
  612.         char* third_word = _strdup(line.c_str() + word_length + 1 + word_length + 1);
  613.         third_word[word_length] = '\0';
  614.         // cout << "First guess: " << first_guess << endl;
  615.         // first_guess = _strdup("TAILS");
  616. #else
  617.         char* first_word = _strdup("CHANT");
  618.         char* second_word = _strdup("SORED");
  619.         char* third_word = _strdup("BLIMP");
  620.         // char* forth_word = _strdup("FUGLY");
  621.         // EVOKE
  622. #endif
  623.         cout << "First three guesses: " << first_word << " " << second_word << " " << third_word << endl;
  624.  
  625.         hint_class_counts.clear();
  626.         for (int m = 0; m < answer_words.size(); m++) {
  627.             hint combined_hint = get_wordle_hints(first_word, answer_words[m]) * num_hint_values * num_hint_values +
  628.                 get_wordle_hints(second_word, answer_words[m]) * num_hint_values +
  629.                 get_wordle_hints(third_word, answer_words[m]);
  630.             int count = hint_class_counts[combined_hint];
  631.             hint_class_counts[combined_hint] = count + 1;
  632.         }
  633.  
  634.         // We want the word list sorted with largest classes first, this will give us the fastest break out
  635.         std::vector<std::pair<hint, int>> pairs;
  636.         for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); ++iter) {
  637.             pairs.push_back(*iter);
  638.         }
  639.         sort(pairs.begin(), pairs.end(), [=](std::pair<hint, int>& a, std::pair<hint, int>& b) {
  640.             return a.second > b.second;
  641.         });
  642.  
  643.         int guess_rating = 0;
  644.         vector<char*> valid_words_worst_class;
  645.         vector<char*> answer_words_minus_singletons;
  646.         vector<guess> guesses_so_far;
  647.         vector<guess> guesses_so_far_worst;
  648.         for (auto iter = pairs.begin(); iter != pairs.end(); iter++) {
  649.             cout << iter->second << " ";
  650.             if (iter->second <= 1) {
  651.                 break;
  652.             }
  653.             guesses_so_far.clear();
  654.             guesses_so_far.push_back(guess(first_word, iter->first / num_hint_values / num_hint_values));
  655.             guesses_so_far.push_back(guess(second_word, (iter->first / num_hint_values) % num_hint_values));
  656.             guesses_so_far.push_back(guess(third_word, iter->first % num_hint_values));
  657.             vector<char*> valid_words_this_class = get_valid_words(answer_words, guesses_so_far);
  658.             for (int m = 0; m < valid_words_this_class.size(); m++) {
  659.                 answer_words_minus_singletons.push_back(valid_words_this_class[m]);
  660.             }
  661.         }
  662.         cout << endl;
  663.         cout << "Singletons removed: " << (answer_words.size() - answer_words_minus_singletons.size()) << endl;
  664.  
  665.         for (int i = 0; i < guess_words.size(); i++) {
  666.             for (int j = i + 1; j < guess_words.size(); j++) {
  667.                 hint_class_counts.clear();
  668.                 bool skip = false;
  669.                 for (int m = 0; m < answer_words_minus_singletons.size(); m++) {
  670.                     hint combined_hint = get_wordle_hints(guess_words[i], answer_words_minus_singletons[m]) * num_hint_values * num_hint_values * num_hint_values * num_hint_values +
  671.                         get_wordle_hints(guess_words[j], answer_words_minus_singletons[m]) * num_hint_values * num_hint_values * num_hint_values +
  672.                         get_wordle_hints(first_word, answer_words_minus_singletons[m]) * num_hint_values * num_hint_values +
  673.                         get_wordle_hints(second_word, answer_words_minus_singletons[m]) * num_hint_values +
  674.                         get_wordle_hints(third_word, answer_words_minus_singletons[m]);
  675.                     int count = hint_class_counts[combined_hint];
  676.                     hint_class_counts[combined_hint] = count + 1;
  677.                     if (count + 1 > 2) {
  678.                         skip = true;
  679.                         break;
  680.                     }
  681.                 }
  682.                 if (skip) {
  683.                     continue;
  684.                 }
  685.  
  686.                 int guess_rating = 0;
  687.                 for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  688.                     if (iter->second >= guess_rating) {
  689.                         guess_rating = iter->second;
  690.                     }
  691.                 }
  692.                 if (guess_rating <= 2) {
  693.                     cout << first_word << " " << second_word << " " << third_word << " " << guess_words[i] << " " << guess_words[j] << " " << guess_rating << endl;
  694.                     of << first_word << " " << second_word << " " << third_word << " " << guess_words[i] << " " << guess_words[j] << " " << guess_rating << endl;
  695.  
  696.                     std::vector<std::pair<hint, int>> pairs;
  697.                     for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); ++iter) {
  698.                         pairs.push_back(*iter);
  699.                     }
  700.                     sort(pairs.begin(), pairs.end(), [=](std::pair<hint, int>& a, std::pair<hint, int>& b) {
  701.                         return a.second > b.second;
  702.                     });
  703.  
  704.                     for (auto iter = pairs.begin(); iter != pairs.end(); iter++) {
  705.                         cout << iter->second << " ";
  706.                         if (iter->second <= 1) {
  707.                             break;
  708.                         }
  709.                         guesses_so_far.clear();
  710.                         hint combined_hint = iter->first;
  711.                         guesses_so_far.push_back(guess(third_word, combined_hint % num_hint_values));
  712.                         combined_hint /= num_hint_values;
  713.                         guesses_so_far.push_back(guess(second_word, combined_hint % num_hint_values));
  714.                         combined_hint /= num_hint_values;
  715.                         guesses_so_far.push_back(guess(first_word, combined_hint % num_hint_values));
  716.                         combined_hint /= num_hint_values;
  717.                         guesses_so_far.push_back(guess(guess_words[j], combined_hint % num_hint_values));
  718.                         combined_hint /= num_hint_values;
  719.                         guesses_so_far.push_back(guess(guess_words[i], combined_hint % num_hint_values));
  720.                         vector<char*> valid_words_this_class = get_valid_words(answer_words, guesses_so_far);
  721.                         cout << "[";
  722.                         for (int m = 0; m < valid_words_this_class.size(); m++) {
  723.                             cout << valid_words_this_class[m];
  724.                             if (m != valid_words_this_class.size() - 1) {
  725.                                 cout << ",";
  726.                             }
  727.                         }
  728.                         cout << "] ";
  729.                     }
  730.                     cout << endl;
  731.                 }
  732.             }
  733.         }
  734.     }
  735. }
  736.  
  737. void find_best_first_guess() {
  738.     cache_best_first_guess();
  739.     cout << "Best first guess: " << best_first_guess << endl;
  740. }
  741.  
  742. void print_words(const vector<char*>& valid_words) {
  743.     cout << "[";
  744.     if (valid_words.size() <= 50) {
  745.         int limit = valid_words.size() <= 50 ? valid_words.size() : 20;
  746.         for (int i = 0; i < limit; i++) {
  747.             cout << valid_words[i] << (i < valid_words.size() - 1 ? ", " : "");
  748.         }
  749.         if (limit < valid_words.size()) {
  750.             cout << "(" << (valid_words.size() - limit) << " other words" << ")";
  751.         }
  752.     } else {
  753.         cout << "(" << valid_words.size() << " words)" << endl;
  754.     }
  755.     cout << "]";
  756. }
  757.  
  758. void guess_unknown_word() {
  759.     vector<guess> guesses_so_far;
  760.     bool result_is_correct_word;
  761.     int rating;
  762.  
  763.     string hint_string;
  764.     char* current_guess;
  765.     do {
  766.         cache_best_first_guess();
  767.         current_guess = best_first_guess;
  768.         cout << "Next guess: " << current_guess << endl;
  769.         cout << "Enter result (k for black, y for yellow, g for green, or ENTER if word is not accepted): ";
  770.         getline(cin, hint_string);
  771.         if (hint_string.length() == 0) {
  772.             strcpy(best_first_guess, ""); // Remove from dictionary
  773.             best_first_guess = NULL;
  774.         }
  775.     } while (hint_string.length() == 0);
  776.     guesses_so_far.push_back(guess(current_guess, string_to_hint(hint_string)));
  777.     vector<char*> valid_words = get_valid_words(answer_words, guesses_so_far);
  778.     print_words(valid_words);
  779.     cout << endl << endl;
  780.  
  781.     while (true) {
  782.         char* current_guess = get_best_guess(valid_words, result_is_correct_word, guesses_so_far, rating);
  783.         if (current_guess == NULL) {
  784.             cout << "Word is not in dictionary." << endl;
  785.             break;
  786.         } else if (result_is_correct_word) {
  787.             cout << "The word is: " << current_guess << endl;
  788.             break;
  789.         }
  790.         cout << "Next guess: " << current_guess << endl;
  791.         cout << "Enter result (k for black, y for yellow, g for green, or ENTER if word is not accepted): ";
  792.         getline(cin, hint_string);
  793.         if (hint_string.length() == 0) {
  794.             strcpy(current_guess, ""); // Remove from dictionary
  795.             continue;
  796.         }
  797.         guesses_so_far.push_back(guess(current_guess, string_to_hint(hint_string)));
  798.         valid_words = get_valid_words(valid_words, guesses_so_far);
  799.         print_words(valid_words);
  800.         cout << endl << endl;
  801.     }
  802. }
  803.  
  804. void solve_single_word_helper(char* answer) {
  805.     vector<guess> guesses_so_far;
  806.     bool result_is_correct_word;
  807.     int rating;
  808.  
  809.     auto start = high_resolution_clock::now();
  810.     cache_best_first_guess();
  811.     char* current_guess = best_first_guess;
  812.     hint hint = get_wordle_hints(current_guess, answer);
  813.     guesses_so_far.push_back(guess(current_guess, hint));
  814.     vector<char*> valid_words = get_valid_words(answer_words, guesses_so_far);
  815.     cout << current_guess << " " << hint_to_string(hint) << " ";
  816.     print_words(valid_words);
  817.     cout << endl << endl;
  818.  
  819.     while (strcmp(current_guess, answer) != 0) {
  820.         current_guess = get_best_guess(valid_words, result_is_correct_word, guesses_so_far, rating);
  821.         if (current_guess == NULL) {
  822.             cout << "Word is not in dictionary. You may have typed it incorrectly." << endl;
  823.             break;
  824.         }
  825.         hint = get_wordle_hints(current_guess, answer);
  826.         guesses_so_far.push_back(guess(current_guess, hint));
  827.         valid_words = get_valid_words(valid_words, guesses_so_far);
  828.         cout << current_guess << " " << hint_to_string(hint) << " ";
  829.         print_words(valid_words);
  830.         cout << endl << endl;
  831.     }
  832.     auto stop = high_resolution_clock::now();
  833.     cout << "Done, time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 << " s" << endl;
  834.     cout << "Number of guesses: " << guesses_so_far.size() << endl;
  835. }
  836.  
  837. void solve_single_word() {
  838.     char* answer = NULL;
  839.     do {
  840.         cout << "Enter solution word: ";
  841.         string answer_str;
  842.         getline(cin, answer_str);
  843.         answer = _strdup(answer_str.c_str());
  844.         toupper_cstr(answer);
  845.         if (strlen(answer) != word_length) {
  846.             cout << "Word is wrong length, must be " << word_length << " letters." << endl;
  847.             continue;
  848.         }
  849.         break;
  850.     } while (true);
  851.  
  852.     solve_single_word_helper(answer);
  853. }
  854.  
  855. void solve_all_words_in_dictionary() {
  856.     vector<guess> guesses_so_far;
  857.     bool result_is_correct_word;
  858.     int rating;
  859.     double best_average = 100000.0f;
  860.  
  861.     auto overall_start = high_resolution_clock::now();
  862.  
  863.     char* best_first_word_solve_all = NULL;
  864.     char* best_second_word_solve_all = NULL;
  865.     char* best_third_word_solve_all = NULL;
  866.  
  867.     // ifstream in("solutions6.txt");
  868.  
  869.     while (true) {
  870.         cout << "Precomputing best first guess..." << endl;
  871.         cache_best_first_guess();
  872.         char* first_guess = best_first_guess;
  873. #if false
  874.         string line;
  875.         getline(in, line);
  876.         if (in.eof()) break;
  877.         if (line.length() < word_length * 3 + 2) {
  878.             continue;
  879.         }
  880.         char* first_guess = _strdup(line.c_str());
  881.         first_guess[word_length] = '\0';
  882.         char* second_guess = _strdup(line.c_str() + word_length + 1);
  883.         second_guess[word_length] = '\0';
  884.         char* third_guess = _strdup(line.c_str() + word_length + 1 + word_length + 1);
  885.         third_guess[word_length] = '\0';
  886.         // cout << "First guess: " << first_guess << endl;
  887.         first_guess = _strdup("CHANT");
  888.         second_guess = _strdup("SORED");
  889.         third_guess = _strdup("BLIMP");
  890.         cout << "First three guesses: " << first_guess << " " << second_guess << " " << third_guess << endl;
  891.         // first_guess = _strdup("TAILS");
  892. #endif
  893.  
  894. #if 0
  895.         {
  896.             vector<guess> guesses_so_far_first;
  897.             char* foo;
  898.             foo = _strdup("-----");
  899.             for (int i = 0; i < word_length; i++) {
  900.                 for (int j = i + 1; j < word_length; j++) {
  901.                     foo[0] = first_guess[i];
  902.                     foo[1] = first_guess[j];
  903.                     guesses_so_far_first.clear();
  904.                     guesses_so_far_first.push_back(guess(foo, string_to_hint("yykkk")));
  905.                     cout << " " << first_guess[i] << " " << first_guess[j] << " " << get_valid_words(answer_words, guesses_so_far_first).size() << endl;
  906.                     guesses_so_far_first.clear();
  907.                     guesses_so_far_first.push_back(guess(foo, string_to_hint("ykkkk")));
  908.                     cout << " " << first_guess[i] << "!" << first_guess[j] << " " << get_valid_words(answer_words, guesses_so_far_first).size() << endl;
  909.                     guesses_so_far_first.clear();
  910.                     guesses_so_far_first.push_back(guess(foo, string_to_hint("kykkk")));
  911.                     cout << "!" << first_guess[i] << " " << first_guess[j] << " " << get_valid_words(answer_words, guesses_so_far_first).size() << endl;
  912.                     guesses_so_far_first.clear();
  913.                     guesses_so_far_first.push_back(guess(foo, string_to_hint("kkkkk")));
  914.                     cout << "!" << first_guess[i] << "!" << first_guess[j] << " " << get_valid_words(answer_words, guesses_so_far_first).size() << endl;
  915.                     cout << endl;
  916.                 }
  917.             }
  918.         }
  919.  
  920.         {
  921.             vector<guess> guesses_so_far_first;
  922.             char* foo;
  923.             foo = _strdup("-----");
  924.             char* bar;
  925.             bar = _strdup("kkkkk");
  926.             for (int i = 0; i < word_length; i++) {
  927.                 for (int j = 0; j < word_length; j++) {
  928.                     guesses_so_far_first.clear();
  929.                     foo[j] = first_guess[i];
  930.                     bar[j] = 'g';
  931.                     guesses_so_far_first.push_back(guess(foo, string_to_hint(bar)));
  932.                     cout << first_guess[i] << " " << j << " " << get_valid_words(answer_words, guesses_so_far_first).size() << endl;
  933.                     bar[j] = 'k';
  934.                     foo[j] = '-';
  935.                 }
  936.             }
  937.         }
  938. #endif
  939.  
  940.         char** second_guesses = NULL;
  941.         int empty_classes = 0;
  942. #if 1
  943.         if (answer_words.size() * 1000 > num_hint_values) {
  944.             cout << "Precomputing best second guesses..." << endl;
  945.             auto start = high_resolution_clock::now();
  946.             second_guesses = new char* [num_hint_values];
  947.             for (int i = 0; i < num_hint_values; i++) {
  948.                 vector<guess> guesses_so_far;
  949.                 guesses_so_far.push_back(guess(first_guess, i));
  950.                 vector<char*> valid_words = get_valid_words(answer_words, guesses_so_far);
  951.                 second_guesses[i] = get_best_guess(valid_words, result_is_correct_word, guesses_so_far, rating);
  952.                 // second_guesses[i] = _strdup("DECOR");
  953. #if 0
  954.                 cout << hint_to_string(i) << " " << valid_words.size() << " " << (second_guesses[i] == NULL ? "" : second_guesses[i]) << endl;
  955. #endif
  956.                 if (valid_words.size() == 0) {
  957.                     empty_classes++;
  958.                 }
  959.                 // print_words(valid_words);
  960.                 // cout << endl;
  961.  
  962.                 // if ((i % 1000) == 0) {
  963.                 //     auto stop = high_resolution_clock::now();
  964.                 //     cout << "Projected time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 / i * num_hint_values << " s" << endl;
  965.                 // }
  966.             }
  967.             auto stop = high_resolution_clock::now();
  968.             cout << "Done, time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 << " s" << endl;
  969.             cout << "Empty hint classes: " << empty_classes << endl;
  970.         }
  971. #endif
  972.  
  973.         char*** third_guesses = NULL;
  974. #if 1
  975.         if (sqrt(answer_words.size() * 1000) > num_hint_values) {
  976.             cout << "Precomputing third guesses..." << endl;
  977.             auto start = high_resolution_clock::now();
  978.             third_guesses = new char** [num_hint_values * num_hint_values];
  979.             for (int i = 0; i < num_hint_values; i++) {
  980.                 vector<guess> guesses_so_far_first;
  981.                 guesses_so_far_first.push_back(guess(first_guess, i));
  982.                 vector<char*> valid_words_first = get_valid_words(answer_words, guesses_so_far_first);
  983.  
  984.                 third_guesses[i] = new char* [num_hint_values];
  985.                 for (int j = 0; j < num_hint_values; j++) {
  986.                     vector<guess> guesses_so_far;
  987.                     guesses_so_far.push_back(guess(first_guess, i));
  988.                     guesses_so_far.push_back(guess(second_guesses[i], j));
  989.                     // guesses_so_far.push_back(guess(second_guess, i));
  990.                     vector<char*> valid_words = get_valid_words(valid_words_first, guesses_so_far);
  991.                     third_guesses[i][j] = get_best_guess(valid_words, result_is_correct_word, guesses_so_far, rating);
  992.  
  993. #if 0
  994.                     if (valid_words.size() > 10) {
  995.                         cout << hint_to_string(i) << " " << (second_guesses[i] == NULL ? "null" : second_guesses[i]) << " " << hint_to_string(j) << " " << valid_words.size() << endl;
  996.                     }
  997. #endif
  998.                 }
  999.                 // if ((i % 1000) == 0) {
  1000.                 //     auto stop = high_resolution_clock::now();
  1001.                 //     cout << "Projected time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 / i * num_hint_values << " s" << endl;
  1002.                 // }
  1003.             }
  1004.             auto stop = high_resolution_clock::now();
  1005.             cout << "Done, time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 << " s" << endl;
  1006.         }
  1007. #endif
  1008.  
  1009.         int max_guesses = 0;
  1010.         char* worst_word = NULL;
  1011.  
  1012.         cout << "Solving all words in dictionary..." << endl;
  1013.         auto start = high_resolution_clock::now();
  1014.         int histogram[50] = { 0 };
  1015.         for (int i = 0; i < answer_words.size(); i++) {
  1016.             if ((i % 1000) == 0) {
  1017.                 cout << i << endl;
  1018.                 // auto stop = high_resolution_clock::now();
  1019.                 // cout << "Projected time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 / i * answer_words.size() << " s" << endl;
  1020.             }
  1021.             vector<guess> guesses_so_far;
  1022.             char* current_guess = first_guess;
  1023.  
  1024.             guesses_so_far.push_back(guess(current_guess, get_wordle_hints(current_guess, answer_words[i])));
  1025.  
  1026.             int number_of_guesses = 1;
  1027.             hint first_hint;
  1028.             vector<char*> valid_words;
  1029.  
  1030.             if (strcmp(current_guess, answer_words[i]) != 0) {
  1031.                 first_hint = get_wordle_hints(current_guess, answer_words[i]);
  1032.                 if (second_guesses != NULL) {
  1033.                     current_guess = second_guesses[first_hint];
  1034.                 } else {
  1035.                     current_guess = get_best_guess(answer_words, result_is_correct_word, guesses_so_far, rating);
  1036.                 }
  1037.                 // current_guess = second_guess;
  1038.                 number_of_guesses++;
  1039.                 guesses_so_far.push_back(guess(current_guess, get_wordle_hints(current_guess, answer_words[i])));
  1040.                 valid_words = get_valid_words(answer_words, guesses_so_far);
  1041.             }
  1042.  
  1043.             if (/*third_guesses != NULL &&*/ strcmp(current_guess, answer_words[i]) != 0) {
  1044.                 current_guess = third_guesses[first_hint][get_wordle_hints(current_guess, answer_words[i])];
  1045.                 // current_guess = third_guess;
  1046.                 number_of_guesses++;
  1047.                 guesses_so_far.push_back(guess(current_guess, get_wordle_hints(current_guess, answer_words[i])));
  1048.                 valid_words = get_valid_words(valid_words, guesses_so_far);
  1049.             }
  1050.  
  1051.             while (strcmp(current_guess, answer_words[i]) != 0) {
  1052.                 // if (guesses_so_far.size() == 3) {
  1053.                 //     current_guess = forth_word;
  1054.                 // } else {
  1055.                     current_guess = get_best_guess(valid_words, result_is_correct_word, guesses_so_far, rating);
  1056.                 // }
  1057.                 number_of_guesses++;
  1058.                 guesses_so_far.push_back(guess(current_guess, get_wordle_hints(current_guess, answer_words[i])));
  1059.                 valid_words = get_valid_words(valid_words, guesses_so_far);
  1060.             }
  1061.  
  1062.             histogram[number_of_guesses]++;
  1063.             if (number_of_guesses > max_guesses) {
  1064.                 worst_word = answer_words[i];
  1065.                 max_guesses = number_of_guesses;
  1066.             }
  1067.  
  1068. #if 0
  1069.             if (number_of_guesses == 5) {
  1070.                 cout << "    " << answer_words[i] << ": ";
  1071.                 for (int i = 0; i < guesses_so_far.size(); i++) {
  1072.                     cout << guesses_so_far[i].word << " " << hint_to_string(guesses_so_far[i].hint) << "  ";
  1073.                 }
  1074.                 cout << endl;
  1075.             }
  1076. #endif
  1077.         }
  1078.         auto stop = high_resolution_clock::now();
  1079.         cout << "Done, time: " << duration_cast<microseconds>(stop - start).count() / 1000000.0 << " s" << endl;
  1080.  
  1081.         auto overall_stop = high_resolution_clock::now();
  1082.         cout << "Total time: " << duration_cast<microseconds>(overall_stop - overall_start).count() / 1000000.0 << " s" << endl;
  1083.         cout << "Time per word: " << duration_cast<microseconds>(overall_stop - overall_start).count() / 1000.0 / answer_words.size() << " ms" << endl;
  1084.  
  1085.         int total_count = 0, total_guesses = 0;
  1086.         for (int i = 0; i < sizeof(histogram) / sizeof(histogram[0]); i++) {
  1087.             if (histogram[i] != 0) {
  1088.                 cout << i << ": " << histogram[i] << endl;
  1089.                 total_count += histogram[i];
  1090.                 total_guesses += i * histogram[i];
  1091.             }
  1092.         }
  1093.         double average = double(total_guesses) / total_count;
  1094.         cout << "Average: " << average << " guesses" << endl;
  1095.         cout << "Worst word was " << worst_word << ":" << endl;
  1096.         // solve_single_word_helper(worst_word);
  1097.  
  1098.         if (average < best_average) {
  1099.             best_first_word_solve_all = first_guess;
  1100.             // best_second_word_solve_all = second_guess;
  1101.             // best_third_word_solve_all = third_guess;
  1102.             // best_forth_word = forth_word;
  1103.             best_average = average;
  1104.         }
  1105.         // cout << "Best triple so far: " << best_first_word_solve_all << " " << best_second_word_solve_all << " " << best_third_word_solve_all << " with average of " << best_average << endl;
  1106.         // cout << "Best fourth word so far: " << best_forth_word << " with average of " << best_average << endl;
  1107.  
  1108.         // forbid_first_guess(first_guess);
  1109.         break;
  1110.     }
  1111. }
  1112.  
  1113. void show_largest_hint_class() {
  1114.     cout << "Enter three words:" << endl;
  1115.     vector<char*> words;
  1116.     for (int i = 0; i < 3; i++) {
  1117.         string word;
  1118.         cout << "> ";
  1119.         getline(cin, word);
  1120.         for (int i = 0; i < guess_words.size(); i++) {
  1121.             if (strcmp(guess_words[i], word.c_str()) == 0) {
  1122.                 words.push_back(guess_words[i]);
  1123.             }
  1124.         }
  1125.     }
  1126.  
  1127.     map<hint, int> hint_class_counts;
  1128.     // int best_worst_count_so_far = 8;
  1129.     int best_worst_count_so_far = 6; // NOLES CRAFT WIMPY
  1130.     int best_pair_i = -1, best_pair_j = -1, best_pair_k = -1;
  1131.  
  1132.     for (int m = 0; m < answer_words.size(); m++) {
  1133.         int combined_hint = 0;
  1134.         for (int i = words.size() - 1; i >= 0; i--) {
  1135.             combined_hint *= num_hint_values;
  1136.             combined_hint += get_wordle_hints(words[i], answer_words[m]);
  1137.         }
  1138.         hint_class_counts[combined_hint]++;
  1139.     }
  1140.  
  1141.     int guess_rating = 0;
  1142.     vector<guess> guesses_so_far;
  1143.     for (auto iter = hint_class_counts.begin(); iter != hint_class_counts.end(); iter++) {
  1144.         if (iter->second >= guess_rating) {
  1145.             guess_rating = iter->second;
  1146.             guesses_so_far.clear();
  1147.            
  1148.             hint combined_hint = iter->first;
  1149.             for (int i = 0; i < words.size(); i++) {
  1150.                 guesses_so_far.push_back(guess(words[i], combined_hint % num_hint_values));
  1151.                 combined_hint /= num_hint_values;
  1152.             }
  1153.         }
  1154.     }
  1155.     vector<char*> valid_words = get_valid_words(answer_words, guesses_so_far);
  1156.  
  1157.     for (int i = 0; i < guesses_so_far.size(); i++) {
  1158.         cout << guesses_so_far[i].word << " " << hint_to_string(guesses_so_far[i].hint) << endl;
  1159.     }
  1160.     cout << endl;
  1161.     print_words(valid_words);
  1162.     cout << endl;
  1163. }
  1164.  
  1165. int hint_count_greens(hint h) {
  1166.     int num_greens = 0;
  1167.     for (int i = 0; i < word_length; i++) {
  1168.         if ((h % 3) == 2) {
  1169.             num_greens++;
  1170.         }
  1171.         h /= 3;
  1172.     }
  1173.     return num_greens;
  1174. }
  1175.  
  1176. // If depth is >= depth_limit_max, just return depth_limit_max
  1177. int hard_mode_get_tree_height(ofstream& of, vector<char*> valid_guesses, vector<char*> valid_answers, vector<guess>& guesses_so_far, int depth_limit_min, int depth_limit_max) {
  1178.     if (depth_limit_max <= 1) {
  1179.         return depth_limit_max;
  1180.     }
  1181.     if (valid_answers.size() <= 2) {
  1182.         return valid_answers.size();
  1183.     }
  1184.     depth_limit_min = max(2, depth_limit_min);
  1185.  
  1186.     int min_depth = depth_limit_max;
  1187.     int best_guess = -1;
  1188.     vector<int> hint_class_counts;
  1189.     hint_class_counts.resize(num_hint_values);
  1190.  
  1191.     char letters_in_answers[(int)'Z' + 1] = { 0 };
  1192.     for (int i = 0; i < valid_answers.size(); i++) {
  1193.         for (int cidx = 0; cidx < word_length; cidx++) {
  1194.             letters_in_answers[valid_answers[i][cidx]] = 1;
  1195.         }
  1196.     }
  1197.  
  1198.     map<string, bool> guess_classes;
  1199.     for (int i = 0; i < valid_guesses.size(); i++) {
  1200.         bool found_non_answer_letter = false;
  1201.         string guess_class_str(valid_guesses[i]);
  1202.         for (int cidx = 0; cidx < word_length; cidx++) {
  1203.             if (!letters_in_answers[guess_class_str[cidx]]) {
  1204.                 guess_class_str[cidx] = '-';
  1205.                 found_non_answer_letter = true;
  1206.             }
  1207.         }
  1208.         if (found_non_answer_letter) {
  1209.             if (guess_classes.find(guess_class_str) != guess_classes.end()) {
  1210.                 continue; // Duplicate on all letters occuring in answers, can skip
  1211.             }
  1212.             guess_classes[guess_class_str] = true;
  1213.         }
  1214.  
  1215.         for (int j = 0; j < num_hint_values; j++) {
  1216.             hint_class_counts[j] = 0;
  1217.         }
  1218.        
  1219.         int depth_i = 0;
  1220.         int max_count = 0;
  1221.         for (int m = 0; m < valid_answers.size(); m++) {
  1222.             hint hint = get_wordle_hints(valid_guesses[i], valid_answers[m]);
  1223.             if (++hint_class_counts[hint] > max_count) {
  1224.                 max_count = hint_class_counts[hint];
  1225.             }
  1226.         }
  1227.  
  1228.         if (max_count <= 2) {
  1229.             depth_i = 1 + max_count;
  1230.         } else {
  1231.             int non_empty_classes = 0;
  1232.             for (hint h = 0; h < num_hint_values; h++) {
  1233.                 if (hint_class_counts[h] > 0) {
  1234.                     non_empty_classes++;
  1235.                 }
  1236.             }
  1237.             if (non_empty_classes == 1) {
  1238.                 // This hint gives no new information, don't bother guessing this word
  1239.                 continue;
  1240.             }
  1241.  
  1242.             if (guesses_so_far.size() <= 0) {
  1243.                 cout << "Starting L" << guesses_so_far.size() << ": ";
  1244.                 for (int g = 0; g < guesses_so_far.size(); g++) {
  1245.                     cout << guesses_so_far[g].word << " " << hint_to_string(guesses_so_far[g].hint) << " ";
  1246.                 }
  1247.                 cout << valid_guesses[i] << endl;
  1248.             }
  1249.  
  1250.             // Count backwards to encounter most constraining classes first, watch out for unsigned overflow
  1251.             for (hint h = num_hint_values - 1; h >= 0 && h < num_hint_values; h--) {
  1252.                 if (hint_class_counts[h] <= 2) {
  1253.                     continue;
  1254.                 }
  1255.  
  1256.                 int depth_h;
  1257.                 if (hint_count_greens(h) == word_length - 1) {
  1258.                     // Trivial case, all but one are green, we must guess all the remaining words one-by-one
  1259.                     depth_h = 1 + hint_class_counts[h];
  1260.                 } else {
  1261.                     guesses_so_far.push_back(guess(valid_guesses[i], h));
  1262.                     vector<guess> guesses_so_far_small;
  1263.                     guesses_so_far_small.push_back(guess(valid_guesses[i], h));
  1264.                     vector<char*> valid_guesses_this_class = get_valid_guesses(valid_guesses, guesses_so_far_small);
  1265.                     vector<char*> valid_answers_this_class = get_valid_words(valid_answers, guesses_so_far_small);
  1266.                     depth_h = 1 + hard_mode_get_tree_height(of, valid_guesses_this_class, valid_answers_this_class, guesses_so_far,
  1267.                         guesses_so_far.size() <= 1 ? 2 : depth_i - 1, depth_limit_max - 1);
  1268.                     guesses_so_far.pop_back();
  1269.                 }
  1270.  
  1271.                 if (depth_h > depth_i) {
  1272.                     depth_i = depth_h;
  1273.                     if (depth_i >= min_depth) {
  1274.                         break;
  1275.                     }
  1276.                 }
  1277.             }
  1278.         }
  1279.  
  1280.         if (depth_i < min_depth) {
  1281.             best_guess = i;
  1282.             if (guesses_so_far.size() > 0) {
  1283.                 min_depth = depth_i;
  1284.             }
  1285.             if (guesses_so_far.size() <= 0) {
  1286.                 cout << "Best so far: ";
  1287.                 of << "Best so far: ";
  1288.                 cout << "L" << guesses_so_far.size() << ": ";
  1289.                 of << "L" << guesses_so_far.size() << ": ";
  1290.                 for (int g = 0; g < guesses_so_far.size(); g++) {
  1291.                     cout << guesses_so_far[g].word << " " << hint_to_string(guesses_so_far[g].hint) << " ";
  1292.                     of << guesses_so_far[g].word << " " << hint_to_string(guesses_so_far[g].hint) << " ";
  1293.                 }
  1294.                 cout << valid_guesses[i] << "  " << depth_i << endl;
  1295.                 of << valid_guesses[i] << "  " << depth_i << endl;
  1296.                 of.flush();
  1297.             }
  1298.         }
  1299.         if (min_depth <= depth_limit_min) {
  1300.             break;
  1301.         }
  1302.     }
  1303.     if (guesses_so_far.size() <= 1) {
  1304.         cout << "L" << guesses_so_far.size() << ": ";
  1305.         of << "L" << guesses_so_far.size() << ": ";
  1306.         for (int g = 0; g < guesses_so_far.size(); g++) {
  1307.             cout << guesses_so_far[g].word << " " << hint_to_string(guesses_so_far[g].hint) << " ";
  1308.             of << guesses_so_far[g].word << " " << hint_to_string(guesses_so_far[g].hint) << " ";
  1309.         }
  1310.         cout << (best_guess == -1 ? "(XXX)" : valid_guesses[best_guess]) << "  " << min_depth << endl;
  1311.         of << (best_guess == -1 ? "(XXX)" : valid_guesses[best_guess]) << "  " << min_depth << endl;
  1312.         of.flush();
  1313.     }
  1314.     return min_depth;
  1315. }
  1316.  
  1317. void hard_mode_analysis() {
  1318.     // I just realized that the correct strategy to optimize hard mode in Wordle is blindingly simple.
  1319.     // Because the guess pool size is reduced so much with each move, that means the move tree is very small.
  1320.     // That means it's totally computationally possible to branch-and-bound to exhaustively try every possible move tree.
  1321.     // And if I can heuristically find the longest paths first, I can do the bounding part even faster.
  1322.     // at kind of longest path search is more or less exactly what Absurdle is already doing
  1323.     vector<guess> guesses_so_far;
  1324.     ofstream of("hard_mode_log_small.txt");
  1325.     // 5 is SCOWL
  1326.     hard_mode_get_tree_height(of, guess_words, answer_words, guesses_so_far, 2, 6);
  1327. }
  1328.  
  1329. int main(int argc, char* argv[]) {
  1330. #if 0
  1331.     for (word_length = 1; word_length <= 500; word_length++) {
  1332.         num_hint_values = 1;
  1333.         for (int i = 0; i < word_length; i++) {
  1334.             num_hint_values *= 3;
  1335.         }
  1336.  
  1337.         guess_words = read_word_file(GUESS_WORD_FILE);
  1338.         if (guess_words.size() > 0) {
  1339.             cout << "Valid guesses : " << guess_words.size() << " words of length " << word_length << " in " << GUESS_WORD_FILE << endl;
  1340.         }
  1341.         if (strcmp(GUESS_WORD_FILE, ANSWER_WORD_FILE) == 0) {
  1342.             answer_words = guess_words;
  1343.         } else {
  1344.             answer_words = read_word_file(ANSWER_WORD_FILE);
  1345.         }
  1346.         if (answer_words.size() > 0) {
  1347.             cout << "Valid answers : " << answer_words.size() << " words of length " << word_length << " in " << ANSWER_WORD_FILE << endl;
  1348.         }
  1349.         if (answer_words.size() == 0 || guess_words.size() == 0) {
  1350.             continue;
  1351.         }
  1352.  
  1353.         if (should_prefer_dense(answer_words.size())) {
  1354.             hint_class_counts = new int[num_hint_values];
  1355.         }
  1356.  
  1357.         best_first_guess = NULL;
  1358.         find_best_first_guess();
  1359.  
  1360.         delete[] hint_class_counts;
  1361.         hint_class_counts = NULL;
  1362.     }
  1363.     return 0;
  1364. #endif
  1365.  
  1366.     string newline;
  1367.     cout << "Enter word length: ";
  1368.     cin >> word_length;
  1369.     getline(cin, newline);
  1370.  
  1371.     num_hint_values = 1;
  1372.     for (int i = 0; i < word_length; i++) {
  1373.         num_hint_values *= 3;
  1374.     }
  1375.  
  1376.     guess_words = read_word_file(GUESS_WORD_FILE);
  1377.     cout << "Valid guesses : " << guess_words.size() << " words of length " << word_length << " in " << GUESS_WORD_FILE << endl;
  1378.     if (strcmp(GUESS_WORD_FILE, ANSWER_WORD_FILE) == 0) {
  1379.         answer_words = guess_words;
  1380.     } else {
  1381.         answer_words = read_word_file(ANSWER_WORD_FILE);
  1382.     }
  1383.     cout << "Valid answers : " << answer_words.size() << " words of length " << word_length << " in " << ANSWER_WORD_FILE << endl;
  1384.  
  1385.     if (answer_words.size() == 0 || guess_words.size() == 0) {
  1386.         cout << "No words of length " << word_length << " in wordlists, exiting." << endl;
  1387.         return 1;
  1388.     }
  1389.  
  1390.     if (should_prefer_dense(answer_words.size())) {
  1391.         hint_class_counts = new int[num_hint_values];
  1392.     }
  1393.  
  1394.  
  1395.     while (true) {
  1396.         cout << "Select one:" << endl;
  1397.         cout << " 1. Find best first guess" << endl;
  1398.         cout << " 2. Guess unknown word (solve puzzle)" << endl;
  1399.         cout << " 3. Show solution for a single given word" << endl;
  1400.         cout << " 4. Solve all words in dictionary and show statistics" << endl;
  1401.         cout << " 5. Find best set of 3 words to start out with" << endl;
  1402.         cout << " 6. Show largest hint class of a set of words" << endl;
  1403.         cout << " 7. Get best 4th and 5th words" << endl;
  1404.         cout << " 8. Hard mode analysis" << endl;
  1405.         cout << " 9. Quit" << endl;
  1406.         cout << "Enter your choice: ";
  1407.         int choice;
  1408.         cin >> choice;
  1409.         getline(cin, newline);
  1410.  
  1411.         switch (choice) {
  1412.         case 1:
  1413.             find_best_first_guess();
  1414.             break;
  1415.         case 2:
  1416.             guess_unknown_word();
  1417.             break;
  1418.         case 3:
  1419.             solve_single_word();
  1420.             break;
  1421.         case 4:
  1422.             solve_all_words_in_dictionary();
  1423.             break;
  1424.         case 5:
  1425.             if (argc >= 3) {
  1426.                 thread_num = atoi(argv[1]);
  1427.                 num_threads = atoi(argv[2]);
  1428.             } else {
  1429.                 thread_num = 0;
  1430.                 num_threads = 1;
  1431.             }
  1432.             best_first_k();
  1433.             break;
  1434.         case 6:
  1435.             show_largest_hint_class();
  1436.             break;
  1437.         case 7:
  1438.             best_4th();
  1439.             break;
  1440.         case 8:
  1441.             hard_mode_analysis();
  1442.             break;
  1443.         case 9:
  1444.             return 0;
  1445.         default:
  1446.             cout << "Invalid option selected." << endl;
  1447.             break;
  1448.         }
  1449.     }
  1450. }
  1451.  
Advertisement
Add Comment
Please, Sign In to add comment