2.8 KiB
Wordle Solver
This is a toy project to implement a Wordle solver. This project provides no packaging but has no dependencies on libraries that aren't builtin. The main function selects a random word, then simulates the game with the solver and prints the output.
I didn't look at any other solutions before writing this, since that would ruin the fun.
Solver
The solver is mostly brute-force. It takes every possible "guess" word from the dictionary (assumed to be in /etc/share/dict/words), and plays it to every possible "answer" in the dictionary, and determines the top moves. The ranking is based on a heuristic using the average number of remaining playable words, where fewer is better.
Performance is reasonably optimized but the runtime of the overall algorithm is O(n)^3, where n is the size of the dictionary. Using 16 threads I was able to compute the first guess (the slowest) in about 6 hours using a dictionary of 3,000 5-letter words. The subsequent guesses are typically subsecond, and the first guess could be trivially cached since the initial board state is always the same.
Example
The below was run with a random dictionary of size 100.
$ python3 ./src/wordle.py
2022-02-04 10:31:50,370 Round: 1
2022-02-04 10:31:52,158 Best Moves: [('cakes', -50.44), ('foxes', -51.78), ('coils', -51.9), ('corns', -52.65), ('waxes', -53.63)]
2022-02-04 10:31:52,158 Num words remaining: 100
2022-02-04 10:31:52,158 Guess: cakes
2022-02-04 10:31:52,158 Board: [None, None, None, None, None]
2022-02-04 10:31:52,158 Round: 2
2022-02-04 10:31:52,173 Best Moves: [('goofy', -4.222222222222222), ('minty', -5.444444444444445), ('dingy', -5.666666666666667), ('dizzy', -5.777777777777778), ('gruff', -5.777777777777778)]
2022-02-04 10:31:52,173 Num words remaining: 9
2022-02-04 10:31:52,174 Guess: goofy
2022-02-04 10:31:52,174 Board: [None, None, None, None, 'y']
2022-02-04 10:31:52,174 Round: 3
2022-02-04 10:31:52,184 Best Moves: [('minty', -1.6666666666666667), ('dizzy', -2.0), ('butty', -2.0)]
2022-02-04 10:31:52,185 Num words remaining: 3
2022-02-04 10:31:52,185 Guess: minty
2022-02-04 10:31:52,185 Board: [None, None, None, 't', 'y']
2022-02-04 10:31:52,185 Round: 4
2022-02-04 10:31:52,194 Best Moves: [('butty', -1.0)]
2022-02-04 10:31:52,195 Num words remaining: 1
2022-02-04 10:31:52,195 Guess: butty
2022-02-04 10:31:52,195 Board: ['b', 'u', 't', 't', 'y']
Answer: butty
Iterations: 4
Guesses: cakes, goofy, minty, butty
Performance Notes
I learned from this project that deepcopy.copy is outrageously slow, even for simple data classes. Initially, it dominated runtime when it was used to copy the board state for each evaluation. I replaced it with a hand-written copy, assigning immutable values and shallow copying dictionaries, and this allowed meeting sufficient performance for a toy.