How to Make a Wordle Solver with Twilio Serverless, Studio, and SMS

February 23, 2022
Written by
Reviewed by

wordle solver

Like many word nerds and puzzle lovers, I am obsessed with Wordle, a word puzzle game created by Brooklyn-based software engineer Josh Wardle for his word game-loving partner. I made a Wordle version over SMS with Twilio Serverless to play even more Wordle, but sometimes, I get stuck while playing. Read on to learn how to build a SMS Wordle solver using Twilio Studio, Twilio Functions, the Twilio Serverless Toolkit, and the Datamuse API to find words given a set of constraints, or test it out by texting anything to +18063046212!

This was built with my coworker Craig Dennis on my Twitch channel.

textexample

 

Want a brief overview of how it's built? Check out this Tiktok! 

Prerequisites

  1. A Twilio account - sign up for a free one here and receive an extra $10 if you upgrade through this link
  2. A Twilio phone number with SMS capabilities - configure one here
  3. Node.js installed - download it here

Get Started with the Twilio Serverless Toolkit

The Serverless Toolkit is CLI tooling that helps you develop locally and deploy to Twilio Runtime. The best way to work with the Serverless Toolkit is through the Twilio CLI. If you don't have the Twilio CLI installed yet, run the following commands on the command line to install it and the Serverless Toolkit:

npm install twilio-cli -g
twilio login
twilio plugins:install @twilio-labs/plugin-serverless

Create your new project and install our lone requirement superagent, an HTTP client library to make HTTP requests in Node.js, by running:

twilio serverless:init wordle-solver --template=blank && cd wordle-solver && npm install superagent

Hit the Datamuse API to Receive Potential Wordle Words with JavaScript

You can do a lot with the Datamuse API. For example, to retrieve words that start with t, end in k, and have two letters in-between, you would hit https://api.datamuse.com/words?sp=t??k and see:

words from datamuse according to constraints
 

Make a file in the functions folder of your wordle-solver serverless project from solver.js. At the top, import superagent and make a helper function to, given a letter and a word, return indices found to later calculate black letters from the yellow squares and guesses input.

const superagent = require("superagent");
function findIndices(letter, word) {
  return word
    .split("")
    .map((l, i) => {
      if (l === letter) {
        return i;
      }
    })
    .filter((index) => index >= 0);
}

The meat of the code is in the Function handler method:

exports.handler = function (context, event, callback) {
  // Here's an example of setting up some TWiML to respond to with this function
  let greenSquares = String(event.green.toLowerCase());
  let yellowSquares = event.yellow ? event.yellow.toLowerCase() : "";

  let guesses = event.guesses.toLowerCase().split(",");
  // Finds yellow places (right letter wrong space)
  // Looks like {'e': [4, 3], 'a': [0]}
  const yellowIndices = yellowSquares.split("").reduce((indices, letter) => {
    guesses.forEach((guess) => {
      if (indices[letter] === undefined) {
        indices[letter] = [];
      }
      const foundIndices = findIndices(letter, guess);
      indices[letter] = indices[letter].concat(foundIndices);
    });
    return indices;
  }, {});
  console.log(`yellowIndices ${JSON.stringify(yellowIndices)}`);
  console.log(`guess ${guesses}, greenSquares ${greenSquares}, yellowSquares ${yellowSquares}`);
  const blackSquares = guesses
    // To an array of arrays of letters
    .map((word) => word.split(""))
    // To a single array
    .flat()
    // Only the missing letters
    .filter((letter) => {
      return !yellowSquares.includes(letter) && !greenSquares.includes(letter);
    }); //get black squares
  console.log(`blackSquares ${blackSquares}`);
  let messagePattern = greenSquares + `,//${yellowSquares + '?'.repeat(5 - yellowSquares.length)}`;
  //let messagePattern = greenSquares + `,*${yellowSquares}*`; 
  console.log(`messagePattern ${messagePattern}`);
  superagent.get(`https://api.datamuse.com/words?max=1000&sp=${messagePattern}`).end((err, res) => {
    if (res.body.length <= 2) { //Datamuse doesn't have any related words
      console.log("no related words");
      return callback(null, { "words": [] });
    } //if
    let allWords = res.body.map(obj => obj.word);
    let wordsWithoutBlackLetters = allWords.filter(
      word => {
        return word.split("").every(letter => !blackSquares.includes(letter));
      });
    console.log(`wordsWithoutBlackLetters ${wordsWithoutBlackLetters}`);
    const withoutIncorrectYellow = wordsWithoutBlackLetters.filter((word) => {
      // for each letter in the indices
      for (const [letter, indices] of Object.entries(yellowIndices)) {
        for (const index of indices) {
          if (word.charAt(index) === letter) {
            // Short circuit (Johnny 5 alive)
            return false;
          }
        }
      }
      // It's a keeper!
      return true;
    });
    return callback(null, { 
      "words": withoutIncorrectYellow.slice(0, 10), //due to message length restrictions and these are the likeliest words
      "guesses": guesses
    });
  });
};

The complete code can be found on GitHub here.

Go back to the wordle-solver root directory and run twilio serverless:deploy. Copy the function URL from the output and save it for later. It will look like this:  https://wordle-solver-xxxx-dev.twil.io/solver. Now the Function is deployed, but we need to make the Twilio Studio Flow that will call this Twilio Function to return possible Wordle words to the user texting in.

Make the App Logic with Twilio Studio

I tried to build this Wordle Solver solely with Twilio Functions, but Craig insisted Twilio Studio was perfectly-suited for this project. Studio is Twilio's drag-and-drop visual builder, a no-code to low-code platform. I had not used Studio that much and after seeing Craig work his magic on Twitch, I am now a Studio evangelist/convert!

Open up this gist and copy the JSON to a file - you'll need to replace a few variables (service_sid, environment_sid, and function_sid) so this makes it easier to edit.

To get service_sid, run the following command with the Twilio CLI:

twilio api:serverless:v1:services:list

Keep adding on what you get, so from the last command (take the service_sid corresponding to our project wordle-solver), run

twilio api:serverless:v1:services:environments:list --service-sid= SERVICE-SID-FROM-THE-LAST-COMMAND

to get the environment_sid. Then run the following command to get the function_sid.

twilio api:serverless:v1:services:functions:list --service-sid=YOUR-SERVICE-SID-FROM-ABOVE

Lastly, replace the url with the URL of your Twilio Function URL ending in "/solver" you receive when you deploy your serverless function.

To make a new Twilio Studio flow, log in to your Twilio account and go to the Studio Dashboard. Then, click the blue plus sign and give your flow the name “wordle-solver” Click next in the setup modal, scroll down and choose “Import from JSON” from the provided templates.

import from JSON

Paste in the JSON (with the replaced placeholders) copied from the gist. Once you finish the setup, you should see a flowchart like the one below. Hit the Publish button at the top of the flow. 

start of Studio flow

When someone first texts a Twilio number (that will be configured with this Studio Flow), they will be asked what words they guessed. We check their input using a Split Based On… widget that uses regex to make sure they only sent five-letter words separated by commas and if they did, we set a variable called guesses.

 

split based on regex

Else, the flow goes back to the initial Send and Wait for Reply widget to ask them what they guessed again. Then the Flow asks for their green squares and yellow squares, with similar corresponding conditional widgets using more regex. If the user sent a "!" to represent no yellow squares, we replace that with an empty string to pass to the Datamuse API to return possible Wordle words based on the green and yellow square input using a Run Function widget.

function URL in Studio

My widget config looks like this:

run function config

We also pass our Twilio Function either the variables we set or the user input as  Function parameters:

function parameters

We then send a message from Twilio Studio with the words returned from the Datamuse API and check if whatever the user guessed was the correct Wordle word. If it was, they receive a congrats message. Else, the Studio Flow asks what they guessed and adds it to the guesses variable in Studio before going back up to ask what their green squares are again. This flow should run until the user has solved Wordle!

Configure the Studio Flow with a Twilio Phone Number

In the phone numbers section of your Twilio Console, select the Twilio number you purchased and scroll down to the Messaging section. Under A MESSAGE COMES IN change Webhook to Studio Flow and select wordle-solver (or whatever you named your Studio Flow.)

configure phone # with studio flow

Test it by texting anything to your Twilio number! You can use the solver during your daily Wordle game or you can also make your own Wordle here.

wordle solver gif in action

 

What's Next for Twilio Serverless, Studio, and Word Games?

Twilio Studio blew my mind. You can use it to:

  • Handle state
  • Parse complex conditional statements with regex
  • Seamlessly integrate with Twilio Functions

Thank you so much to Craig for constantly improving the app and teaching me and so many others what we could use Studio for.

Let me know online what you're building with it.