Boost your Wordle streak with Go
The Machine's Brain
The Wordle cracker's real brain power lies in the filter()
function (called in line 37 of Listing 2) and whittling down the list of words. The main program passes in the list of words still in play, the current guess, and the evaluation score returned by the online Wordle app. The function returns the word list reduced to reflect the evaluation rules, which the main program immediately assigns again to the words-in-play list dict
. Following this approach, the program keeps shrinking the word list until only the correct solution remains.
Listing 3 shows the implementation of filter()
, along with a grades()
function that, much like the online app, can evaluate a guess attempt against a target word. It tells the user which letters are correct, which are in the wrong place, and which do not occur at all. The grades()
function returns the rating for a target word in the want
parameter and for a guess in guess
, containing a string of numbers such as 01201.
Listing 3
filter.go
01 package main 02 03 import ( 04 "strconv" 05 ) 06 07 type Grade int 08 09 const ( 10 NoMatch Grade = iota 11 OtherPos 12 Match 13 ) 14 15 func grades(guess, want string) string { 16 hints := make([]Grade, len(guess)) 17 for i, _ := range hints { 18 hints[i] = NoMatch 19 } 20 21 wantMap := map[byte]int{} 22 23 // wanted letter counts 24 for i := 0; i < len(want); i++ { 25 wantMap[want[i]] += 1 26 } 27 28 // full matches 29 for i := 0; i < len(guess); i++ { 30 guessRune := guess[i] 31 if guessRune == want[i] { 32 hints[i] = Match 33 wantMap[guessRune] -= 1 34 } 35 } 36 37 for i := 0; i < len(guess); i++ { 38 guessRune := guess[i] 39 if hints[i] == Match { 40 continue 41 } 42 if wantMap[guessRune] > 0 { 43 hints[i] = OtherPos 44 wantMap[guessRune] -= 1 45 } 46 } 47 48 res := "" 49 for _, hint := range hints { 50 res += strconv.Itoa(int(hint)) 51 } 52 return res 53 } 54 55 func filter(words []string, guess, graded string) []string { 56 res := []string{} 57 58 for _, word := range words { 59 if graded == grades(guess, word) { 60 res = append(res, word) 61 } 62 } 63 64 return res 65 } 66 67 func bestBang(words []string) string { 68 best := "" 69 count := 0 70 71 for _, word := range words { 72 runes := map[rune]bool{} 73 74 for _, rune := range word { 75 runes[rune] = true 76 } 77 78 if count == 0 || len(runes) > count { 79 count = len(runes) 80 best = word 81 } 82 } 83 84 return best 85 }
To determine the rating of a guess attempt, line 16 generates an array slice with integers whose positions match those of the letters in the guess. If there is a 2
in the corresponding position in the array slice, the letter in the guess is in the correct position, and so on. To initialize the array slice, the for
loop, starting in line 17, first sets all entries to NoMatch
(i.e.,
) because of the enum-style constant in line 10, which enumerates the constants in ascending order starting at
, thanks to the keyword iota
.
Line 21 then creates a hash map named wantMap
that counts how many times each letter should be seen in the word. For example, if the word to be guessed is LOOSE, it assigns a value of 1
to the letters L, S, and E, and a value of 2
to the letter O. The for
loop starting in line 29 then goes through all the letters in the guess and sets the corresponding hints
entries to 2
if the letter exactly matches the solution in want
. Each of these matches decrements the value of the total required number for this letter in wantMap
by one.
Starting in line 37, the cracker program evaluates the positions that contain a letter that needs to be somewhere else. If the current position is not a direct hit, but contains a letter from the wantMap
, the hints
array slice will contain a value of 1
for the current position, and the counter in the wantMap
will be decremented by one. If the letter appears again later in the word and its counter in wantMap
is still not used up, you would find another 1
there.
When it's all said and done, grades()
converts the integer array slice containing the ratings of each letter element by element into a string using the strconv.Itoa()
conversion function to return it to the caller as a compact packet. The Wordle website probably uses a similar scoring algorithm.
Filtering by Score
But what does all this have to do with filtering out words from the Scrabble list that are no longer potential solutions based on the Wordle page's evaluation of the guesses? The algorithm in filter()
uses the following trick to drop words that are no longer possible: It goes through the remaining list of words entry by entry, assuming in each round that the current entry is the secret solution to the Wordle puzzle. Then line 59 consults the grades()
evaluator, asking it what the evaluation of the current guess word is compared to the assumed solution from the word list. Now think about this: If grades()
comes back with the same rating as the algorithm from the official Wordle website (which you have because you entered it in its codified format), the assumed solution word is a genuine candidate for the solution. Otherwise, the filter can delete it – simple but effective.
To enable the main program to come up with a good suggestion from the remaining word list on the Try next
prompt, the bestBang()
function, starting in line 67, picks a word with as many unique letters as possible. This increases the probability that the solution will actually contain one or more of these letters and that the user can eliminate even more wrong solutions in the next step. To do this, bestBang()
counts the number of different letters in each word in a hash map named runes
. It remembers the word with the highest score (i.e., with the highest entropy) and returns it to the caller when done.
To compile the listings in this article, you just need to call
go build wordle.go dict.go filter.go
This gives you an executable wordle
binary. Note that the cracker does not need any third-party libraries. Instead, it makes do with the pool from the Go standard library. The English Scrabble file required to run the program can be downloaded free of charge from the web [4].
Flying Start
If you want to be the first to cross the finish line, you need to get off the starting block quickly – and not only in a 100-meter race. Since Wordle's meteoric rise, scientists have thrown information theory math at the problem to determine the word that gives you a flying start in the game [5].
In general, words with many different letters turn out to be a good choice. At the same time, common letters are extremely valuable because they improve the probability of good tips returned by the Wordle scorer. This why the I started with OATER to seed the cracker.
« Previous 1 2 3 Next »
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
-
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.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.