Clone Minesweeper in 15 minutes with TypeScript, React, and Redux by@rjzaworski

Clone Minesweeper in 15 minutes with TypeScript, React, and Redux

RJ Zaworski HackerNoon profile picture

RJ Zaworski


When Minesweeper shipped with Windows 3.1 it wasn’t simply to entertain. The addictive little game masked an ulterior motive: introducing users to the right button of the mouse. The two-button mouse is old news by now, but Minesweeper remains just the right size for a different introduction.

Over the next fifteen minutes, we’ll build a Minesweeper clone (github) that uses TypeScript, React, and Redux to build a robust browser application. It’s a simple, synchronous project, but it’s enough to introduce patterns that use Redux and TypeScript to help tame the complexity of much larger applications.


Getting Started

No time for configuration—the clock is ticking! We can use create-react-app-typescript to set up a project with sensible defaults. Fine tuning can come later.

$ create-react-app minesweeper — scripts-version=react-scripts-ts$ cd minesweeper$ yarn install

We’ll also need to add redux, redux-react, and type definitions for the latter (redux brings its own).

$ yarn add redux react-redux$ yarn add @types/react-redux

Game Logic

Next, we’re going to cheat (but only a little). While we’ll use React for the frontend UI and Redux for persistence, we can save time by borrowing an existing model of the game itself.

This particular implementation happens to be friendly, local, and synchronous, but it’s easy to imagine moving MinesweeperAPI to the far end of an HTTP request. Its behavior would remain the same in either case: given the current state of the Game and a request to reveal or flag a single cell, the MinesweeperAPI will return a new state updated to reflect the consequences of the request.

Next, we’ll need to tell TypeScript about the “shape” of the Game. This consists of Options—the size of the board and how many mines are hidden on it—a moveCount, and several indices comprising the state of the board. The finished type declaration documents this nicely:

This time with Redux

We’ve got a game—we just need somewhere to put it. We’ll prepare Redux to store and update the state of the Game by mirroring the public MinesweeperAPI in typed actions of our own:

Except for the type definitions these look just like the action creators we would write in vanilla JavaScript. But by stating our assumptions up front, the TypeScript compiler can enforce the shape of these actions elsewhere in the app. If we try to create an action that isn’t declared in the Action union, the program won’t compile.

Playing the Game

We’ll get even more benefit from static typing inside the store. One of the challenges of scaling Redux is the complexity of increasingly “deep” reducers, accessors, and state. By declaring their shape preemptively, the TypeScript compiler can warn us about typos, missing parameters, and broken references.

To keep things simple, we’ll build the store around the existing Game type from the domain model. Even with this relatively flat data-structure, we’ll sleep easier at night with TypeScript’s assurance that the rest of the app is following the rules.

Break it! Try accessing action.options within the 'REVEAL_LOCATION' branch, and check out the resulting compiler error.

Building the Frontend

Speaking of “check out”, the clock! Time’s tight, but we still have time for a bit of civic beautification.

To get things going, let’s connect the Redux store to the component tree by wrapping a [Provider]( around our root element. This works exactly as in JavaScript:

ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root') as HTMLElement);

Next, we’ll scaffold a root application component and [connect]( it to the provided store.

TypeScript adds several new facets here. The mapStateToProps and mapDispatchToProps functions passed into connect each have their own type declarations, whose intersection represents the component’s own Props. When the dust settles,<App /> will receive the entire Redux Game plus the three available actions and a grid array to simplify rendering. In return for the added ceremony, we get type-safety all the way down: unknown actions, malformed props, and undefined references will now all trigger errors at compile time.

Rendering a Cell

<App /> is wired into the store but isn’t much to look at yet. To fix that, let’s create a component representing a single cell on the grid:

Leaving CSS and rendering optimization as for a future exercise (we’re on a schedule, here!) this looks nearly like it would in JavaScript. Inference is the name of the game: once TypeScript understands the boundaries of the application, it can infer the correct types for most of the details inside. This won’t much matter to our little speed-run, but it will save much ceremony as the project scales up.

The one thing the compiler won’t infer nicely are new data crossing the application boundary. When a cell is clicked, for instance, we need to tell TypeScript what sorts of events an independent event handler is prepared to receive:

e: React.MouseEvent<HTMLButtonElement>

The compiler can now verify that we’re handling the event correctly. If we pass on a KeyboardEvent or attach the handler to something other than a <button />, we’ll be greeted with an error at compile time.

Finishing the board

We’re nearly there! We need two more things to get the game running inside the <App />: a [componentDidMount]( hook to create the initial game state, and a render method to put the CellComponent grid on the screen.

This is just JavaScript—with all the types declared upstream, TypeScript will infer the details the rest of the way down.

Wrapping Up

Pencils down. 14:59. Tack on a beautiful design, and there it is: statically-verified Minesweeper in just under 15 minutes. We’ve left plenty of details for other TypeScript tutorials, but the patterns introduced here can carry over to projects of all sizes and shapes.

The finished game isn’t the prettiest thing. No tests, suboptimal rendering, and plenty of opportunity for the design team, but we’re finally to the part where TypeScript shines brightest. As the project evolves, all the assumptions embodied in our type declarations will be preserved for reference—and backed by a friendly warnings anytime something breaks the rules.

Check out the finished project on Github!

react to story with heart
react to story with light
react to story with boat
react to story with money

Related Stories

. . . comments & more!