back to homepage

How I Made My Own Wordle

I built my own version of Wordle over a weekend. Here's how I did it.

2022-01-30

If you haven't heard of Wordle by now, you might've recently emerged from a cryogenic sleep chamber. If you're like the rest of us computer-toucher types, you've been enjoying Josh Wardle's daily language game, based on Lingo.

I've been in the process of re-developing my site, and having finished a couple other games[flags][bpm], I thought I would take a crack at my own version of the internet's favourite daily challenge. Here's how I did it.

Rules Of The Game

To get started, we need to know the rules and parameters.

I'm going to add a few more wrinkles.

The Words

For any good language game, we'll need a list of words. I downloaded mine from this github repository (the words_alpha.txt file). Next, we'll need to create a list of reasonable guesses, because no one's going to be happy if their word is "tariqa" or "aahed". We also need a separate list of all the possible words so we can validate our players guesses (we don't want them to be able to enter "aeiou", for example).

To do this I created a script in python to narrow down the list of words using zipf_frequency from wordfreq, plus words from the nltk.corpus, and I used better_profanity to clean things up.

The script looks something like this:

with  open('words.txt') as  words:
	for  word  in  words:
		if  word in word_list:
			freq = get_frequency(word)
			if  freq > 0:
				all[word] = True
			if  freq > 3.1: # this is an arbitrary cut-off
				if  profanity.contains_profanity(word):
					continue
				else:
					common_words.append(word)

	# output files of common words of length 4,5,6 & save them
(the real script is a bit more complex, obviously)

The world lists I was able to generate were pretty good, but I still needed to go through each list to remove some words (ex. names and other proper nouns). My final lists for 4, 5, & 6 letter words have about 1500, 2200, and 2800 words respectively.

Now that we have our word lists, we can get to work on the game.

Game Time

Wordle is a pretty simple game to get working, but there is a lot to keep track of. I won't give away too much code, but hopefully it'll be enough to get you started on your own version.

Props & State

First, let's set up our game component with it's props and state.

Props

and

State

The initial state will be set with a couple useEffect hooks, that run when the round changes:

useEffect(() => {
	setWord(words[round]);
}, [round]);

And when the word changes:

useEffect(() => {
	const letterMap: string[][] = [[]];
	const guessMap: string[][] = [[]];
	[0, 1, 2, 3, 4, 5].map((n) => {
		letterMap[n] = new Array(word.length).fill('');
		guessMap[n] = new Array(word.length).fill('');
	});
	setLetters(letterMap);
	setGuesses(guessMap);
	setGuessedLetters({});
	setGuessesMade(0);
	setWordPosition(0);
}, [word]);

Next we'll add some event functions, onKeyEntered, onBack, and onEnter

const onKeyEntered = (letter: string) => {
	// update `letters`
	// incremenent `wordPosition`
};

const onBack = () => {
	// update `letters`
	// decremenent `wordPosition`
};

const onEnter = () => {
	// check that the word is long enough (no missing chars)
	// make the guess (I use a separate function here)
};

Next, we need the function to actually make the guess. We can call it makeGuess (if we're feeling creative). I'll leave the code up to you, but it should be just a few lines.

const makeGuess = (guess: string) => {
	// check if the word is in the longer list of acceptable words

	/* map the guess to the two arrays (`letters`, `guesses`)
	 * (it's a bit cleaner if you make another function for this)
	 * basically, loop through the characters in the guess
	 * and based on their position, update the guesses array with
	 * the appropriate state ('correct', 'has_letter', 'none').
	 */

	if (word === guess) {
		// increment the round and end it with a success message
	} else if (guessesMade === 5) {
		// increment the round and end it with a fail message
	} else {
		// increment the round and set the word position to `0`
	}
};

And we'll add an event listener that uses our three functions.

const keyDown = (event: KeyboardEvent) => {
	const key = event.code;
	if (key === 'Backspace') {
		onBack();
	}
	if (key === 'Enter') {
		onEnter();
	}
	if (key.includes('Key')) {
		const [, letter] = key.split('Key');
		onKeyEntered(letter);
	}
};

useEffect(() => {
	document.addEventListener('keydown', keyDown);
	return () => document.removeEventListener('keydown', keyDown);
});

The UI

The game component's UI is made up of two main elements, the keyboard, and the grid. We can start with the grid. Using React's JS in HTML capabilities, we can take our letters array and .map() it:

letters.map((row, row_index) => {
	return (
		<div>
			{row.map((col, col_index) => {
				return <div>{letters[row_index][col_index]}</div>;
			})}
		</div>
	);
});
I'll leave the classes and styles up to you ;)

Remember you can get the grid block's guess state like so:

const guess_state = guesses[row_index][col_index]; // 'correct'|'has_letter'|'none'

We can do something similar with the Keyboard element

{
	[
		['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
		['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
		['back', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'enter'],
	].map((row) => {
		return (
			<div>
				{row.map((key) => {
					return <span onClick={onClick}>{key}</span>;
				})}
			</div>
		);
	});
}
Remember to colour code the keys here as well!

That's pretty much it! Now all you need is a bit of code that selects game settings, feeds a list of words into the component, and starts the game, plus a function to run when the game finishes.

You can try the game here. If you have any questions about the (pseudo)code above, you can reach out to me via email or Linkedin!

Thanks for reading!

back to homepage

Nicholas Harrison

nicholas.robin.harrison[at]gmail.com© 2021-2024