Wordle is an incredibly popular word game where the player tries to guess a five-letter word. The player makes guesses and the game tells the player how close they are by color-coding letters. The game has a fun history as a side-project turned gift that was later acquired by the New York Times, and—if you haven’t played the game yet—you should give it a spin.
While the full version is mobile-friendly, tracks your stats, and has multiple color modes, today, I’m going to show you how to build a slightly simpler version, in 8 simple pull requests, and using stacks: a workflow that lets you break out your work into multiple, dependent pull requests.
Before we get started, this is what I’m going to build.
From a product perspective, Wordle is fairly straightforward. It only needs to:
Select a hidden word
Track keyboard presses
Show the user a history of their guesses
And how close those guesses were to the hidden word
To code that step-by-step:
Step 1: Use Create React App (9,119 lines, almost entirely generated)
This PR is exclusively the output of running
yarn create react-app wordle-tutorial --template typescript. It will serve as scaffolding for the remainder of the wordle app.
This PR lets users type words in using the key-press APIs. We're using key-press APIs (as opposed to an input) so that we can customize the display of the characters (as they need to be in their own box). One down-side of this is that this won't work on mobile (as the keyboard is hidden by default), but we can stack that on-top.
Step 3: Keep track of past guesses (18 lines)
In this PR we keep track of past guesses the user has made. We store them in state and render them as a list. This is fairly straightforward.
Step 4: Add SCSS support (994 lines, mostly generated)
Unrelated to Wordle, but necessary product infra is adding support for sass. We did this by following the instructions in https://stackoverflow.com/questions/55071266/how-to-use-react-with-typescript-and-sass
Step 5: Show which letters are correct (119 lines)
This is the meat of the game. Here we add the functionality of picking a hidden word (for now, hard-coded to "magic") and showing users how close their guesses are.
Step 6: Add a win condition and reset button (40 lines)
We both let the user know when they've won (entered the hidden word), and added a reset button that resets the game state (allowing the user to play again).
Step 7: Add a real dictionary with words (2319 lines, mostly data)
This replaces our previously hard-coded hidden word ("magic") with one pulled randomly from a dictionary of five letter words. We pulled the dictionary from the source code of https://www.nytimes.com/games/wordle/index.html
Step 8: Add a deploy script (106 lines)
This deploys our code to gh-pages, https://withgraphite.github.io/wordle-tutorial/. In order to do this, we followed the instructions here: https://gist.github.com/cobyism/4730490. And modified it to support force-pushing here: https://stackoverflow.com/questions/33832046/git-subtree-split-no-new-revisions-were-found
Ta-da! That’s it! Those 8 quick changes were all we needed to build Wordle with React.
Beyond Wordle, hopefully, you felt that those PRs were easy to read, and perhaps you even noticed that none of them were actually merged yet.
This workflow—where one PR is based on the work of another, instead of off main / master / trunk—is called stacking. Stacking is how we build everything at Graphite, and it allows developers on a team to show each other not just what they built but how they built it.
For example, this is what building Wordle from scratch without stacks looks like:
Create Wordle from scratch (12,571 lines)
Was that harder to read?
In the case of Wordle, stacking allowed me, as a teammate, to:
Break out my work: It’s much easier to think about Wordle not as one big lift, but as many small changes. For example, adding SCSS support is necessary for the changes further down the line, but when it’s all presented at once, it’s hard to follow what the author is doing.
Guide my reviewer, you, through the code: In comparison to the larger PR, as a reviewer, it’s easier to follow changes when similar changes are grouped together and those groups are ordered sensibly.
Stay unblocked: even though not one of those PRs has been reviewed or merged, I can continue to stack changes on top, allowing me to develop without having massive PRs. For example, now that I’ve written the basic game, I can start on adding Wordle’s on-screen keyboard without having to wait for my teammates to review my other PRs.
Merge as I go: The longer you go without merging, the greater your chance of a merge conflict. By breaking out small, atomic units of work, you can reduce the risk of merge conflicts by merging your code back into trunk regularly.
Request separate reviewers: There is no reason why the person who reviews the deploy script has to be the same person who reviews your SCSS. Depending on your team, those are likely different people with different concerns. By breaking out your change, two different people can review two different parts.
Ensure CI is passing at each step: Reviewing broken code is hard. If you’ve ever reviewed a change commit by commit you probably have felt the pain of having to review a set of changes that were wrong (only to realize it was fixed later down the line), by stacking, you can ensure that the code is passing CI at every point.