Hey all, I’ve been enjoying wordle, the puzzle game recently but also wondered how a program might do at it.

My first step was to extract the lists of words from the wordle site. Interestingly, there is a “target” list of 2315 words - the words that may be the answer, but additionally a list of 10657 possible additional guesses - words that users can guess that are valid but will never be the answer. If you want these lists there’s a Python-formatted couple of sets representing them in the repo below.

My immediate first thought was I should use the frequency of letters in the english language to inform my guess strategy. However, I realized there was a better way: Use the frequency of letters in the target list! That’s what really matters, yeah? No etaoin shrdlu for me!

I also had the thought, that I could measure the frequency of letters in position. For the first position of a 5 letter word, perhaps the frequency of consonants is higher than vowels? Or perhaps, I am wrong, there.

I then thought, why don’t I measure this frequency for the possible targets based on previous guesses, by pruning the wordlist for each new guess?

I eventually wrote this all in a program with the following steps:

  1. Figure out, for the target list of words, what the frequency of letters per positions 1-5 is.
  2. Pick the word that is most likely to get new green letters based on the frequency of letters in each slot.
  3. Use the results from the new guess to prune the target list to only words that are possible given all previous guesses.
  4. Repeat.

Along the way I also had the thought that the expanded list, representing all targets and all possible guesses, could be used as a list of potential guesses. This would expand the possibility of getting a green letter in position. However, I ran this experiment, as you can see in the code - and the answer is that this approach is actually worse! I thought also, maybe the idea is to use the expanded list for the first guess or two, then use only the targets. This approach was also worse than just using the targets only.

Additionally, at one point I hadn’t implemented the logic that says when a yellow letter is in the same position in a word, that word is not the answer. This logic alone took me from an 4.5 average to the 3.6 it currently gets.

The program also, loses the game, 14 times out of 2315! Those words and the guesses are interesting:

wound: [slate: 00000, crony: 00120, hound: 02222, bound: 02222, pound: 02222, mound: 02222]
shave: [slate: 20202, share: 22202, shame: 22202, shape: 22202, shake: 22202, shade: 22202]
vaunt: [slate: 00110, taunt: 02222, jaunt: 02222, haunt: 02222, daunt: 02222, gaunt: 02222]
found: [slate: 00000, crony: 00120, hound: 02222, bound: 02222, pound: 02222, mound: 02222]
boxer: [slate: 00001, fever: 00022, rider: 00022, cower: 02022, poker: 02022, homer: 02022]
ratty: [slate: 00120, fatty: 02222, catty: 02222, batty: 02222, patty: 02222, tatty: 02222]
catch: [slate: 00110, taunt: 12000, patch: 02222, match: 02222, hatch: 02222, batch: 02222]
stamp: [slate: 20210, start: 22200, staid: 22200, stank: 22200, staff: 22200, stash: 22200]
baste: [slate: 10122, paste: 02222, caste: 02222, haste: 02222, taste: 02222, waste: 02222]
watch: [slate: 00110, taunt: 12000, patch: 02222, match: 02222, hatch: 02222, batch: 02222]
goner: [slate: 00001, fever: 00022, rider: 00022, cower: 02022, poker: 02022, homer: 02022]
fight: [slate: 00010, tight: 02222, might: 02222, wight: 02222, right: 02222, night: 02222]
willy: [slate: 01000, golly: 00222, billy: 02222, hilly: 02222, dilly: 02222, filly: 02222]

The program sometimes runs in a different order, which I think has something to do with how sets work in Python? Please forgive my code, I am a self taught programmer with a philosophy degree.

The code

https://github.com/tomlockwood/wordle-solve

(The word sets are in ./lib/words.py)

What’s next

Some of the code in the repo supports multiple game types, and I went down the road of writing some alternative game strategies. The most promising one ran overnight on my low-spec laptop and then made it run out of memory. I decided to cache some of the results on disk to prevent this, and that database (sqlite) ballooned out to 50GB in size.

I’ve decided to explore Rust for this, and so far what was taking 1GB of RAM in Python is taking, literally 1MB in Rust! I’m trying to cram things into 8-bits, and haven’t done that kind of thing before, but we’ll see, maybe there will be a follow-up blog post! I think it is totally possible to win every game in an average of under 3.5!

The current solver is a hard-mode solver, and my theory is that an easy mode solver will do better. Observing this program’s solutions has seemed to increase my skills at wordle a little!

Thanks for reading! Feel free to e-mail me.