Skip to content

Instantly share code, notes, and snippets.

@jayrobin
Last active January 14, 2022 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jayrobin/fd072d9d35253ade399547a946e7c9f7 to your computer and use it in GitHub Desktop.
Save jayrobin/fd072d9d35253ade399547a946e7c9f7 to your computer and use it in GitHub Desktop.
Taking the fun out of Wordle

Taking the fun out of Wordle

Wordle is great and you should definitely play it. I play it and love it, but I'm also a lazy, lazy man. Sometimes I fire up Wordle and my brain isn't caffeinated enough to figure out five whole letters. So, like a typical engineer, I spent far too long over-engineering an automated solution.

Where's the word of the day coming from?

My first guess was to check the network tab, but there were no fetch requests in there. I'm happy to see how lightweight everything is though!

My next thought was to check the JavaScript, and I soon found these two huge arrays of words:

The first is a list of all possible daily words, and the second (much larger) list is all valid entries. This means that the word of the day is always on the client, and is picked by using the current date as an index into the array. That's cool, but I don't want to replicate that logic (and the entire dictionary) in my automated solution.

Next I check client storage: cookies, session storage, and local storage. There I find what I'm looking for...localStorage.gameState

The solution is RIGHT THERE in localStorage. I could grab that, parse it, and throw out the solution to the console:

const gameState = JSON.parse(localStorage.getItem("gameState"));
const { solution } = gameState;
console.log(`The solution is ${solution}, you cheater`);

If I did that, I still need to press all the individual keys and then press enter. That's seven whole things. I don't have time for seven things!

Press the keys for me plz

Thankfully I can use window.dispatchEvent to send my own events, so I check the code again and spot that it's specifically listening for keydown events.

I fire Wordle in incognito, open the console, and try mimicking an 'a' key press.

Now we're cooking with gas!

I already have the solution, so if I want to enter that as individual key presses I can just split it into an array of characters, loop over it, and create a keypress for each one:

const gameState = JSON.parse(localStorage.getItem("gameState"));
const { solution } = gameState;

solution.split('').forEach((letter) => {
  window.dispatchEvent(new KeyboardEvent("keydown", { key: letter }));
});

And the result:

But that's not quite lazy enough: I want it to get the solution, enter it in, AND submit it for me. Easy enough with another key press at the end:

  // ...
}
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));

Ok let's give it a test toast:

So we're done, right?!

WRONG

By my count, we've used 283 characters here - do you think strings just grow on trees? We're better than that. We can DO better than that.

Minify to the MAX

Enter Code Golf, where the goal is to solve a given problem using as few characters as possible. The people that are good at this are probably some sort of wizard. For example, this is a fully functional implementation of Flappy Bird in just 220 characters (past it in the browser URL bar to play):

data:text/html,<body onload=z=c.getContext`2d`,setInterval(`c.width=W=150,Y<W&&P<Y&Y<P+E|9<p?z.fillText(S++${Y=`,9,9|z.fillRect(p`}*0,Y-=--M${Y+Y},P+E,9,W),P))):p=M=Y=S=6,p=p-6||(P=S%E,W)`,E=49) onclick=M=9><canvas id=c>

The code is basically unreadable, because it's expected to use lots of tricks and nuances of the language to shorten the code which usually would be avoided as it harms readability. Does this mean that code golf creates bad habits and should be avoided by beginners? I don't think so: to get better at Code Golf you need to learn the language really well to expand your toolset, and know what methods and tricks to use in what situations. Also it makes you great at reading minified code! Maybe, I don't know, probably not.

So let's take another look at our original implementation:

const gameState = JSON.parse(localStorage.getItem("gameState"));
const { solution } = gameState;

solution.split('').forEach((letter) => {
  window.dispatchEvent(new KeyboardEvent("keydown", { key: letter }));
});
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));

(283 chars)

The most obvious thing that jumps out to me that we could trim is all the logic to parse gameState as JSON. It's a plain string in localStorage, and the part of the string that has the solution is always in the same place (on a clean game), between characters 105 and 110. So, instead of making a nice object from it, we can just pull the individual letters of the solution from the gameState string:

for(let i = 105; i < 111; i++) {
  const key = localStorage.getItem("gameState")[i];
  window.dispatchEvent(new KeyboardEvent("keydown", { key }));
}
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));

(219 chars)

Immediately I can spot some near duplicated code: window.dispatchEvent(new KeyboardEvent("keydown", { key: [THIS BIT IS DIFFERENT] })). It's 99% the same, except for the key. What if I created a function that I can pass a key to, and it will dispatch a keydown event for me? Then I can replace both instances where I'm doing this manually:

const d = (key) => window.dispatchEvent(new KeyboardEvent("keydown", { key }));
for(let i = 105; i < 111; i++) {
  d(localStorage.getItem("gameState")[i]);
}
d("Enter");

(169 chars)

That saved us a bunch of characters, and I think we've maxed out that kind of refactoring and need to move into gross territory. You see the const and lets?

d = (key) => window.dispatchEvent(new KeyboardEvent("keydown", { key }));
for(i = 105; i < 111; i++) {
  d(localStorage.getItem("gameState")[i]);
}
d("Enter");

(159 chars)

BAM! Gone! The variables d and i are now globals. Obviously you wouldn't do this in actual code, but we just saved a whole 10 characters there! Where we're declaring i = 105 in the for loop, we can declare as many variables as we like and separate them with commas. This won't save much, but we can remove a semicolon and a newline by moving our helper function in there too. While we're at it, let's get rid of window. - dispatchEvent is in the global scope, so that's just a waste.

for(d = (key) => dispatchEvent(new KeyboardEvent("keydown", { key })), i = 105; i < 111; i++) {
  d(localStorage.getItem("gameState")[i]);
}
d("Enter");

(152 chars)

At this point, we've basically done all we can except trimming all that wasteful whitespace:

for(d=(key)=>dispatchEvent(new KeyboardEvent("keydown",{key})),i=105;i<111;i++){d(localStorage.getItem("gameState")[i]);}
d("Enter");

(132 chars)

And finally, because the code block inside the for loop is a one-liner, we can scrap the curly braces and move the 'Enter' keypress onto the same line, separating the two statements with a semicolon (basically doing this: for(i=0;i<10;i++)console.log(this works ${i});console.log("now I'm done")):

for(d=(key)=>dispatchEvent(new KeyboardEvent("keydown",{key})),i=105;i<111;i++)d(localStorage.getItem("gameState")[i]);d("Enter")

And there you have it: 129 characters to automatically beat Wordle. Obviously I had to create a bookmarklet for one-click cheating:

Going to Wordle every day to press the button is pretty annoying, so I guess tomorrow I'm going to have to automate that too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment