Wordle exploded into virality and was graced with an acquisition from the New York Times to solidify it’s quality as an attractive word game. Have you ever wondered how it works? I was curious, so I took a look online to see what others had found and ended up making a bot that can play it.

How does Wordle work?

It’s pretty straightforward. You get six guesses to find the five letter answer word. Each guess will tell you if the letters you chose are Correct, Misplaced, or Not Present. Using the power of deduction you can find the answer in a few guesses.

Now from a technical standpoint it’s pretty cool! There are two separate groups of words the game uses. An “answer” dictionary and a “guess” dictionary. You can guess anything in the “guess” dictionary which is about 10,000 words, but answers are pulled from an “answer” list of only about 2,300 more common words. This way you don’t get ridiculous answers like “saive” but you can happily guess things even though they’re never going to be the answer.

So how could a bot work through this problem?

My general approach was to guess words that eliminate the largest number of possibilities every time. This means that the first word guess is super important! We want to reduce the possible guesses as much as possible. How do we find such a word? Well, the way I did it was like this:

  • Make five ‘column occurrence’ lists of the alphabet to store letter scores.
  • Make an ‘overall occurrence’ alphabet list to store how many time each letter occurs.
  • Examine each answer word, and add to their scores in these lists.
  • Pick the most used letters based on occurrence score
  • Find words that use those letters.
  • Use the column lists to total up the scores for the word, and pick the one with the highest score.

Voila, we can do this for the Guess Dictionary or the Answer Dictionary, either way it’s getting us close to the ideal starting word. There’s a decent range of words to choose from now, so we can just take our pick or let the system pick one.

After the first guess, the bot does the following:

char letter = previousGuess.Word[i];
foreach (ScoredWord answer in possibleAnswers.ToList())
{
    // Green letters
    if (previousGuess.WordState[i] == LetterState.Correct)
    {
        if (answer.Word[i] != letter)
        {
            possibleAnswers.Remove(answer);
            continue;
        }
    }

    // Yellow letters
    if (previousGuess.WordState[i] == LetterState.Misplaced)
    {
        if (!answer.Word.Contains(letter) || answer.Word[i] == letter)
        {
            possibleAnswers.Remove(answer);
            continue;
        }
    }

    // Black letters
    if (previousGuess.WordState[i] == LetterState.NotPresent)
    {
        if (answer.Word.Contains(letter))
        {
            possibleAnswers.Remove(answer);
            continue;
        }
    }
}

At this point, we’re just repeating this loop until we get it right. But how do we choose which word to pick next? We have a reduced list, but there’s still many possible valid guesses! We need a deterministic way to define the “best next guess”. My first try did have it just picking a random word – and this resulted in some very human results – but we want a Robot. An objective solution. A system.

// score remaining words
foreach (ScoredWord possibility in possibleAnswers)
{
    possibility.Score = 0;
    // add up the score of each letter in a word for it's placement.
    for (int letterIndex = 0; letterIndex < 5; letterIndex++)
    {
        char character = possibility.Word[letterIndex];
        switch (letterIndex)
        {
            case 0:
                foreach (ScoredLetter s in m_letter1Scores.Where(s => s.Letter == character))
                {
                    possibility.AddScore(s.Score);
                    break;
                }
                break;
            case 1:
                foreach (ScoredLetter s in m_letter2Scores.Where(s => s.Letter == character))
                {
                    possibility.AddScore(s.Score);
                    break;
                }
                break;
            case 2:
                foreach (ScoredLetter s in m_letter3Scores.Where(s => s.Letter == character))
                {
                    possibility.AddScore(s.Score);
                    break;
                }
                break;
            case 3:
                foreach (ScoredLetter s in m_letter4Scores.Where(s => s.Letter == character))
                {
                    possibility.AddScore(s.Score);
                    break;
                }
                break;
            case 4:
                foreach (ScoredLetter s in m_letter5Scores.Where(s => s.Letter == character))
                {
                    possibility.AddScore(s.Score);
                    break;
                }
                break;
        }
    }
}

This scores every remaining word based on the likelihood of each character being in that slot, which was already calculated, so we just add those scores up for a total score for each word we’re considering guessing.

Finally, we sort the list by those scores and pick the top scoring word!

It does a pretty good job of getting it in 3 – 4 guesses. STARE was my first word choice, but others also work well – ROATE was high on the list, CRANE is also great. In general there’s a number of choices that area high enough on the list to deeply cut possibilities down but nothing is perfect. You’ll get varied results and guess numbers every time.

You can download the .unitypackage file on the Unity Forums for free.

Don’t forget to check out our other products on the Unity Asset Store!