If you’ve recently opened any social media app, be it Facebook, Twitter, or if you are a vivid reader of Forbes or Guardian, you’ve must have seen the new viral craze called WORDLE. This lovely, neat and simple game where the user must guess the five-letter word of the day within five steps. If you are unfamiliar with the game, please do give it a try here and come back to this article. I’ll wait… Done?

Basic investigation and hypothesis

WORDLE is a so-called single-page web application without any kind-of backend. The only external resource or interaction it ever makes with the outside world is when Google Analytics is connected for primary tracking purposes. If you play a game, nothing will be sent to the server; all the validation is done in the client itself. Meaning that the application must carry all the possible words embedded inside the JavaScript “bundle”. It also means that the algorithm for choosing the words and validation must be baked into it. However, the state of the game is tied to your browser and to store the state in the browser the game must use either some cookies or LocalStorage.

Looking into how the application behaves and monitoring traffic with Chrome DevTools soon confirmed this hypothesis. No network calls and local storage has serialized JSON object that represents the current state of the game, theme and statistics.

Inspecting WORDLE Web Application with Chrome Devtools

The sandbox

If the game is so neatly packed and if everything is bundled, then it is feasible to mirror the site, indemnify the JavaScript bundle and fiddle with the code. We start by creating a local mirror of the web application with the help of wget tool.

$ wget --mirror --convert-links --adjust-extension \
  --page-requisites --no-parent https://www.powerlanguage.co.uk/wordle/

Then we need to “deminify” or decompress the JavaScript. This is the process that reverses the process of minifying. Minifying, or minification, is a process where you remove unnecessary characters from your code, whether they might be whitespace (such as indentation), code that isn’t ever used, comments in your code or long names for variables that can be replaced with something shorter.

Minification of your code usually results in it taking up less space, making it faster to send from a server to a client, as well as using less bandwidth in doing so. This improves the user experience on your site as it can load much faster. It also adds an element of security; since it becomes tough to figure out what the code does if it is all mingled and shortened. In this step, however, we use the same code and compile it back to “original” as much as that is possible. In this case, js-beautify was used.

$ npx js-beautify www.powerlanguage.co.uk/wordle/main.*.js > \
  www.powerlanguage.co.uk/wordle/main.js

After this step, we can boot up any web-server (in my case Node.js http-server) and we can start serving the site from our local machine.

$ npx http-server www.powerlanguage.co.uk/wordle -p 8000 -c-1

Jumping into the hole 🕳🐇

Now that we have a local “copy” of the web application running on our machine, we can open the site in the browser. Then it’s a wise idea to trace how user interactions map from UI to actual business logic code. Using Chrome DevTools you can click on “keyboard” (game-keyboard element), and inside the Event Listeners tab, you’ll see the code listening to these virtual keyboard interactions. As seen in the screenshot below, it all starts at line 1269 of our “deminifed” main.js.

Inspecting WORDLE Web Application with Chrome Devtools

From this point on we start exploring where the “checks” happen where the Array of words lives and how check or validation is connected to it. Some more investigation leads us to La and Ta arrays. Remember that these must have more descriptive names in original implementation and that the minification process must have shortened them. Still good.

The evaluateRow function

Now we have two important pieces; we know where the words live where the check happens, and we know that “solution” must be somehow pre-computed before the check can actually happen. So, let’s find this assignment next…

The "solution"

Ok. We now know that there is some function with the name Da that takes “today” (probably Date), and it computes word for the day. Let’s explore how this Da function looks and what it does.

var Ha = new Date(2021, 5, 19, 0, 0, 0, 0)

function Na(e, a) {
    var s = new Date(e),
        t = new Date(a).setHours(0, 0, 0, 0) - s.setHours(0, 0, 0, 0);
    return Math.round(t / 864e5)
}

function Da(e) {
    var a, s = Ga(e);
    return a = s % La.length, La[a]
}

function Ga(e) {
    return Na(Ha, e)
}

var Ba = "abcdefghijklmnopqrstuvwxyz",
    Fa = [].concat(g(Ba.split("").slice(13)), g(Ba.split("").slice(0, 13)));

function Wa(e) {
    for (var a = "", s = 0; s < e.length; s++) {
        var t = Ba.indexOf(e[s]);
        a += t >= 0 ? Fa[t] : "_"
    }
    return a
};

Impressive. I guess.

Now, let us rewrite it and this time, let’s use some more meaningful function names and remove “minification” hacks introduced when minification occurred. We should also use modern JavaScript. To ensure that our code works, let’s also add some assertions against the known words.

const assert = require('assert')

const normalWords = ["cigar", "rebut", "...omitted"];

const gameBeginning = new Date('19 June 2021').setHours(0, 0, 0, 0);

const dateIndex = (beginning, date) =>
    Math.round((date.setHours(0, 0, 0, 0) - beginning) / 864e5)

const wordleForDate = (date) =>
    normalWords[dateIndex(gameBeginning, date) % normalWords.length];

assert.equal(wordleForDate(new Date('26 Jan 2022')), 'whack');
assert.equal(wordleForDate(new Date('25 Jan 2022')), 'sugar');

assert.equal(wordleForDate(new Date('06 Aug 2022')), 'aphid');

The Algorithm

The core algorithm of the WORDLE game is time or rather date based. First, it computes the difference between the current date and the 19th of June 2021 (set by author). In JavaScript, the Date object is represented by a number of milliseconds since 1 January 1970, 00:00:00 UTC, with leap seconds ignored. The second part of getting this number is a freaky division by the number 864e5 and rounding of it. 864e5 is the same as 86400000 or 10006060*24 and represents 24 hours or a day.

After this number is computed, we do an array lookup. In this lookup, we take the “seed”, and we calculate reminder with the length of a given array. A simplified version would then read:

const array = ['A','B','C','D','E']
const seed = Math.round(41.2)
const index = seed % array.length

console.log(array[index])

Conclusion

Although this is now an extremely popular game and it is played by millions worldwide, I believe that it is still important to point out that so-called “client validation” where all the business logic or in this case game logic is validated on users device or client is extremely dangerous and it should be used with caution. If you are building new applications, please make sure that you validate user input on the “server-side” and that your core algorithm is never leaked.

Have fun!

Bunch of WORDLEs

P.s.: If you are struggling with WORDLE’s and would like to get WORDLE for any day, my DMs are open. Oh, and I don’ take crypto, but I LOVE coffee. 😘


This post was also published as an article on my LinkedIn profile.