Boost your Wordle streak with Go
Programming Snapshot – A Go Wordle Cracker
Wordle, a simple online word game, took the world by storm in February. Mike Schilli has developed a command-line tool to boost his Wordle streak using some unapproved tactics.
It's often difficult to predict what will fail and what will generate massive hype, but no one would have expected the Scrabble-style Wordle [1] game to become an Internet hit in 2022. Entire magazine articles deal with the phenomenon [2], people at work show off their results every day, and the New York Times acquired the game from the developer for millions of dollars [3].
The game involves guessing a five-letter word. If the player enters the word correctly, they win the game. If the player's guess doesn't quite get the word, the game marks letters that are in the word and in the right place in green and letters that are in the word but in the wrong place in yellow. Wordle marks guessed letters that do not appear in the target word in gray. To win, you have to guess the word in no more than six attempts, using the app's clues in the smartest way possible.
Automate Me!
Worlde picks words from the Scrabble dictionary, but excludes conjugations or plurals as solutions. In the English version, CAMEL would appear, but not CAKES. Alas, Wordle will accept all five-letter Scrabble words, including plurals, as guesses, of which there are 12,972 in the English language [4].
The game's simple rules make writing a computer program to run against the puzzles a no-brainer. After all, Wordle color-codes the guesses, and a program can use these clues to filter out entries from a complete word list, whittling down the options in each round. The player just needs to pass in the Wordle clues to a helper program in a coded format: I'll use 2
for a letter match, 1
for a letter in the wrong place, and
for letters that do not occur at all. For example, if the puzzle solution is CAMEL and I guessed LAMER, the code would be 12220
: The L at the beginning of the guess is in the wrong place, the middle letters AME are spot on, and the final R does not occur in the target word.
In Figure 2, my wordle
cracker program suggests OATER as the first word. The reason for this unusual word is that it contains a bunch of common vowels, and checking them right at the start often quickly takes you to a solution – but I'll return to that subject later. On the official Wordle page, Figure 1 shows that the word suggested by the program returns three yellow partial matches for O, A, and R, which means these letters occur in the target word, but in another location. If I now pass in this information to the cracker as oater 11001
, the Go program then lists 110 matches
(i.e., there are still 110 words left on the list). Everything else has been eliminated based on the evaluation. At this point, you could type l
to list these 110 words and select one manually, but instead I'll just blindly follow the cracker's choice, ABORD.
The Wordle web page in Figure 1 evaluates this new guess with two green full hits for A and O, while R is still in the wrong place as shown by the yellow highlighting. If I now pass in this result to the cracker as abord 20210
, the cracker immediately decides that only two words are left from the initial list, AROHA and AROMA, and it displays them without further ado. Because it is unlikely that the New York Times would use the New Zealand Maori word for love and affection (aroha) as the solution, I go with AROMA and ignore the cracker's suggestion. Figure 1 confirms that this is the right choice.
Rating Algorithm Exposed
How does the Go program whittle down the word list? First of all, my Wordle cracker needs a reference list of valid words to choose from. A list of Scrabble words [4] is a good starting point. Words in the file are in all caps, one per line, of which there are 279,496. Because Wordle only allows five-letter words, line 23 in Listing 1 filters out everything else, leaving us with 12,972 entries. If you're wondering why the Go program determines the length of each word using the function RuneCountInString()
from the utf8 package, that's because I might want to support foreign languages one day, and their multibyte characters. If you limit the scope to English, a call to length()
will do instead.
Listing 1
dict.go
01 package main 02 03 import ( 04 "bufio" 05 "os" 06 "strings" 07 "unicode/utf8" 08 ) 09 10 const WordleLen = 5 11 12 func dictRead(file string) ([]string, error) { 13 words := []string{} 14 15 f, err := os.Open(file) 16 if err != nil { 17 return words, err 18 } 19 20 s := bufio.NewScanner(f) 21 for s.Scan() { 22 word := strings.ToLower(s.Text()) 23 if utf8.RuneCountInString(word) != WordleLen { 24 continue 25 } 26 words = append(words, word) 27 } 28 err = s.Err() 29 if err != nil { 30 return words, err 31 } 32 33 return words, nil 34 }
Listing 1 defines Wordle's supported word length in a constant named WordleLen
in line 10. In other words, it could easily tackle six-letter Wordles if you just changed one line. The dictRead()
function starting in line 12 expects the name of the file with the word list. It opens the file for reading in line 15 and uses a line-by-line scanner in line 20 to read all the words one by one starting in line 21. The lists contain words in uppercase, just like the Scrabble tiles, which explains why line 22 converts the words to lowercase to allow you to enter them in lowercase, relieving you from holding down the Shift key for input.
Start Cracking
The main()
program in Listing 2 defines the Scrabble dictionary file as scrabble.txt
in the current directory. You can download the file online [4]. The startWord
variable in line 8 defines the word that the algorithm will suggest first in absence of any hints, and I'll use "oater" because it has a nice assortment of vowels.
Listing 2
wordle.go
01 package main 02 03 import ( 04 "fmt" 05 ) 06 07 const dictFile = "scrabble.txt" 08 const startWord = "oater" 09 10 func main() { 11 dict, err := dictRead(dictFile) 12 if err != nil { 13 panic(err) 14 } 15 16 newWord := startWord 17 18 for round := 0; ; round++ { 19 list(dict, false) 20 21 if round != 0 { 22 newWord = bestBang(dict) 23 } 24 fmt.Printf("Try next: '%s'\n", newWord) 25 fmt.Printf("hints(%d)> ", len(dict)) 26 27 var word, score string 28 fmt.Scanf("%s %s", &word, &score) 29 if len(score) == 0 { 30 if word == "l" { 31 list(dict, true) 32 } else { 33 fmt.Printf("Invalid input\n") 34 } 35 continue 36 } 37 dict = filter(dict, word, score) 38 } 39 } 40 41 func list(matches []string, full bool) { 42 if len(matches) > 30 && !full { 43 fmt.Printf("%d matches ('l' to list).\n", len(matches)) 44 } else { 45 for _, w := range matches { 46 fmt.Printf("%s\n", w) 47 } 48 } 49 }
The for
loop starting in line 18 guides the user through the rounds of guesses. The Scanf()
function in line 28 waits for user input. The user can either type l
to list the words that are still in the race or enter the result of a guess in the previously discussed oater 01201
format. For the list
function, the code calls list()
starting in line 41 to output the remaining words in the array slice dict
line by line. The full
flag here determines whether list()
displays massively long lists or only those that contain 30 words or fewer. wordle()
outputs short lists automatically in each round; the full list is only displayed if explicitly requested by the user pressing l
.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Gnome Fans Everywhere Rejoice for the Latest Release
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.