Automating Wordle with Cypress and GitHub Actions
| March 8, 2022 | in
Like many of you, I was introduced to the Wordle craze a few weeks ago by a peer who thought I was too focused on “actual work”. Naturally, I dropped everything to try this new game and then passed a link to the game around to my friends and family like a cursed videotape. It was a fun little distraction; it was going to be a good time. Alas, my thoughts and mood soon darkened as I realized, “I’m terrible at this game”. On the second day I played Wordle, I couldn’t get the right answer. I despaired. Realizing that my soft human brain was holding me back, I decided to automate the game-playing experience.
In this post, I’ll walk you through how I automated the daily Wordle by removing the human element. I will try to keep this post accessible with as little technical jargon as possible.
First thing’s first, I had to find a tool that would let me click around and inspect parts of a web page in a programmatic way. That is to say, in a way that I could program. For example, I needed a tool that I could tell to visit a web page, enter letters, and then determine the game’s “state” for each letter. There are three states for letters that have been attempted in Wordle:
- Absent (gray): this letter is not in the word
- Present (yellow): this letter is in the word, but you haven’t guessed the right position
- Correct (green): this letter is in the word, and you have it in the right position
Cypress has great documentation and a collection of examples for a large variety of situations that you might need to test. It was simple to install and satisfyingly quick to begin using. The experience is just plain pleasant.
In this project, Cypress handles the logistics:
- Runs “test specifications”
- Loads the Wordle website
- Simulates button clicks and keyboard entries
- Reads results off the screen
- Saves a screenshot of successful results
The “algorithm” in this project is a set of instructions that will lead Cypress to find the correct answer to the game. It is simple, intuitive, and works (in the abstract) much like how the brain works. Neurologists, feel free to correct me in the comments.
At the time of writing, there are 12,947 words to choose from in the word bank used by Wordle. Those words and the answers to every day’s puzzle are thinly veiled underneath the Wordle web page to all who would seek them out. It’s necessary to have the exact word bank for this algorithm to work since Wordle will not accept words as wrong answers that are not in that word bank. With all these words loaded up in memory, we have to search through them. For this, I use the ubiquitous and near-universally dreaded construct for searching text: the regular expression.
Simply put, a regular expression is a way to specify a search pattern. Because I don’t want you to leave, I will keep this discussion as basic as possible. If I want to match a single character, I can put the characters I’m looking for in square brackets. For example, this will match any lowercase letter in the English alphabet:
And if I wanted to match any five-letter word, I could repeat this five times:
There are more concise ways to express this pattern, but this isn’t a course on regular expressions, so please bear with me for a moment. This is a handy way to keep track of what letters are still available in each position of the Wordle. Each pair of square brackets represents one letter in the Wordle. In my pattern above, you can see that no letter has been eliminated from any spot.
You may see where I’m going with this. Each guess drops letters from the possible letters in the pattern above. Eventually, we’ll get the word by chance, or we’ll eliminate all other words, and the answer will be the only remaining word to select. Let’s go through a partial example.
Now, you may have a favorite word that you use to start your Wordle every day, but I decided it would be more exciting to pick a random word each day (I later fancied this up a bit by selecting only words that didn’t have repeating letters). At each step, the algorithm will pick a number between 1 and the current count of all remaining possible words (12,947, to start). Today, the program chose to start with “FLOCK”:
This was a lucky first guess. Behind the scenes, Wordle marks these letters with the information I need. As mentioned above, I can now use Cypress to peek at which letters are absent, present, or correct.
What does this information tell us?
- “F” can be dropped from every position in the pattern
- “L” can be dropped from every position except the second position, which is now just “L”
- “O” can be dropped from every position except the third position, which is now just “O”
- “C” can be dropped from the fourth position
- “K” can be dropped from every position in the pattern
Updating the search pattern for the next guess, we have:
There are a lot of remaining letters left in the pattern, but even so, searching the remaining words with this pattern has cut down the word bank considerably. Now we only have 90 words to choose from:
“alods, aloed, aloes, aloha, aloin, alone, along, aloud, alowe, bloat…”
…and so on. At this point, I try to be a bit more clever and remove words that have duplicate letters; for example, “aloha” has two a’s. This cuts us down to 76 possible words to choose from. The answer may indeed be “aloha”, but in general I think this is a better strategy for making guesses in the long run. The tradeoff is that I’ll likely take more steps to find words like “aloha”.
The algorithm picks a new random number, between 1 and 76 this time, and selects the word “clove”. From this, Wordle tells us that “C” is correct while “V” and “E” are both absent. Therefore:
- “C” can be dropped from every position except the first position, which is now just “C”
- “V” can be dropped from every position
- “E” can be dropped by every position
The pattern is now:
At this point, I only have 16 words left to guess from. None of them have repeated letters. I will not talk through the rest of the turns, but hopefully you can see how simple this algorithm is to follow. By the way, the solution for this day was “CLOTH”.
It pains me to admit this, but my algorithm is not guaranteed to find the correct answer before it runs out of available attempts. Sometimes it will need to try again (Cypress provides a handy way to retry on failure, which I have taken advantage of). Interestingly, the better the initial guesses are, the harder it can be to narrow things down later on. For instance, take a look at this embarrassing failure:
If you’re a regular, English-speaking, human person, this series of answers should shock and appall you. My algorithm assigns no value to letters that are more “common” (“n” is more common than “v”), and it isn’t clever enough to pick a word that may maximize the amount of information gleaned from an answer (in my example above, a single word guess that contained some possible combination of the letters “F”, “M”, “P”, “V”, and “N” would have yielded more information in one turn than guessing one letter at a time). You might argue that some of these words are hardly words at all, and indeed my spell-checker would tend to agree. Nevertheless, they’re in the word bank. They are words to Wordle. Wordle has spoken.
If you are decent at solving Wordle, you’ve definitely got my algorithm beat. Mine is simple, naive, and fast. For all its flaws and shortcomings, it’s good enough for me.
This project wouldn’t be complete if I had to run the program each day to get the result. At that point, my whole idea of automation crumbles, and the project is a complete failure. Fortunately, GitHub “Actions” provide all sorts of ways to automate tasks on projects hosted in GitHub. Automation is exactly what I’m looking for in this project. I’d never used GitHub Actions before, so this was yet another good opportunity to poke around and learn something. GitHub provides, among other things, a way for developers to share projects and code. It’s great, and it was an obvious choice for hosting this little project.
Navigating around GitHub Actions is a little clunky to me, but creating the desired “workflow” was straightforward enough to get it done in an hour or so, including the time spent figuring out what I wanted and pursuing several dead-ends. The end result of my tinkering in GitHub Actions is the following automated workflow, which takes about five minutes to run:
- Every day at 8:00 AM CST (2:00 PM GMT), run Cypress on the project
- Cypress connects to Wordle and solves the day’s puzzle
- Cypress takes a screenshot of the successful result
- GitHub updates the screenshot in the project for all the world to see
And with that, I now solve the Wordle each morning without having to lift a finger. I encourage you to check out this project and check out the day’s results here, on GitHub.
This project gave me a great introduction to Cypress and GitHub Actions and gave me an excuse to use these tools in a way that felt much more real than any “quick start” or tutorial. Plus, it’s fun to solve puzzles, and it just feels nice to conceive of, implement, deploy, and declare the project finished, all in one weekend. It’s not every day you get that sense of closure and completeness on a software project. I hope you have enjoyed reading about my shenanigans.
- Cheatle: Automating Wordle with Cypress
- Cypress: Fast, easy and reliable testing for anything that runs in a browser
- GitHub Actions
Next Steps for the Adventurous
You may have long moved from Wordle onto one of its more intense variations (Quordle, Octordle, Semantle). I would love to see a project automating any of these games, but that is an exercise left to the reader. I do not plan on undertaking that challenge myself. As far as I’m concerned, if you’ve automated one word game, you’ve automated them all.