Boost your Wordle streak with Go

Programming Snapshot – A Go Wordle Cracker

© Lead Image © bowie15,

© Lead Image © bowie15,

Article from Issue 260/2022

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.

Figure 1: This Wordle was solved in three guesses …
Figure 2: … with a little help from a Go program.

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


01 package main
03 import (
04   "bufio"
05   "os"
06   "strings"
07   "unicode/utf8"
08 )
10 const WordleLen = 5
12 func dictRead(file string) ([]string, error) {
13   words := []string{}
15   f, err := os.Open(file)
16   if err != nil {
17     return words, err
18   }
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   }
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


01 package main
03 import (
04   "fmt"
05 )
07 const dictFile = "scrabble.txt"
08 const startWord = "oater"
10 func main() {
11   dict, err := dictRead(dictFile)
12   if err != nil {
13     panic(err)
14   }
16   newWord := startWord
18   for round := 0; ; round++ {
19     list(dict, false)
21     if round != 0 {
22       newWord = bestBang(dict)
23     }
24     fmt.Printf("Try next: '%s'\n", newWord)
25     fmt.Printf("hints(%d)> ", len(dict))
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 }
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

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Solving Wordle with Regexes

    Five letters, one word, six tries – that's Wordle. You can solve any Wordle in just a few steps and gain practical experience using grep and regular expressions.

comments powered by Disqus