-
Notifications
You must be signed in to change notification settings - Fork 0
/
wordle-solver.cpp
134 lines (119 loc) · 4.82 KB
/
wordle-solver.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "wordle-solver.h"
// Prints usage instructions.
void usage(void) {
std::cout << "Usage: ./wordle-solver.exe <filename of word list>" << std::endl;
}
// Returns the evaluation of 'guess' against 'actual' as if it were an actual Wordle game:
// 'b' for a black box, 'y' for a yellow box, and 'g' for a green box.
// \param guess std::string of length 5 containing the guess.
// \param actual std::string of length 5 containing the actual word.
// \param char_count std::vector of int of length 26 for the character counts.
// \return std::string of length 5 containing the evaluation.
std::string evaluate(const std::string& guess, const std::string& actual, std::vector<int>& char_count) {
std::string result = "aaaaa";
std::fill(char_count.begin(), char_count.end(), 0);
for (auto c : actual) {
char_count[int(c)%26] += 1;
}
for (int i = 0; i < 5; i++) {
if (guess[i] == actual[i]) {
result[i] = 'g';
char_count[guess[i]%26] -= 1;
}
}
for (int i = 0; i < 5; i++) {
if (result[i] == 'g') {
continue;
}
if (guess[i] != actual[i] && char_count[guess[i]%26] > 0) {
result[i] = 'y';
char_count[guess[i]%26] -= 1;
} else {
result[i] = 'b';
}
}
return result;
}
// Removes elements from 'words' that do not fit 'result'.
// \param guess std::string of length 5 containing the guess.
// \param result std::string of length 5 containing the result. 'b' for a black box, 'y' for a yellow box, and 'g' for a green box.
// \param words std::vector of std::string containing all possible words remaining.
void prune_word_list(const std::string& guess, const std::string& result, std::vector<std::string>& words) {
std::vector<std::string> temp;
std::vector<int> char_count(26,0);
for (auto word : words) {
if (evaluate(guess, word, char_count) == result) {
temp.push_back(word);
}
}
words = temp;
}
// Converts result string to index in partition.
// \param std::string of length 5 containing the result.
// \return int of index
int convert_to_index(const std::string& result) { // TODO: update to accomodate arbitrary length games.
int index = 0;
for (int i = 0; i < 5; i++) {
char c = result[i];
// index += (c == 'b') * (2 * std::pow(3, 4 - i)) + (c == 'y') * std::pow(3, 4 - i);
if (c == 'b') {
index += 2 * std::pow(3, 4 - i);
} else if (c == 'y') {
index += std::pow(3, 4 - i);
}
}
return index;
}
// Returns the entropy of the result distribution for a given word.
// \param word std::string of length 5 containing the guess.
// \param words std::vector of std::string containing all possible words remaining.
// \param partitions std::vector of int containing enough elements for every possible combination of game results. (243 for 5-word Wordle)
// \return float of entropy value.
float get_entropy(const std::string& word, const std::vector<std::string>& words, std::vector<int>& partitions) {
// TODO: implement a way to choose differentiate between similar words within a single partition
std::fill(partitions.begin(), partitions.end(), 0);
std::vector<int> char_count(26,0);
int total = words.size();
for (auto& other : words) {
if (word == other) {
continue;
}
int index = convert_to_index(evaluate(other, word, char_count));
partitions[index] += 1;
}
float entropy = 0.0;
for (int i = 0; i < 243; i++) {
int members = partitions[i];
if (members == 0) {
continue;
}
float p = float(members) / total; //probability
entropy -= p * std::log2(p);
}
return entropy;
}
static std::vector<std::string> bad_guesses {"weird", "price", "antic", "corny", "baste"};
// Finds and returns the word with the maximum entropy among 'words'.
// \param words std::vector of std::string containing all possible words remaining.
// \return std::string of word.
std::string find_max_entropy(const std::vector<std::string>& words) {
std::string max_entropy_word;
std::vector<int> partitions(243,0);
float max_entropy = 0.0;
float entropy = 0.0;
for (auto& word : words) {
entropy = get_entropy(word, words, partitions);
if (entropy > max_entropy &&
!(std::any_of(bad_guesses.begin(), bad_guesses.end(),
[&](const std::string& bad_guess){ return bad_guess == word; }) &&
words.size() > 10)) // exclude word if it is part of bad_guesses and words.size() > 10
{
max_entropy = entropy;
max_entropy_word = word;
}
}
return max_entropy_word;
}
std::string randomly_choose_word(const std::vector<std::string>& words) {
return words[std::rand() % words.size()];
}