2048 game — Implemented with React and Redux

Dimitar Valchanov
6 min readJan 9, 2016

--

If you are a Javascript developer, you most probably have heard of React. It helps you build modular applications that scale well. And the best part, you can use it for everything — websites, mobile applications and server rendering — just name it.

Unfortunately, it’s only the visible part of the application, the part that keeps up to date on state changes. But for that to happen we first need a place to save our state.

And that’s where Redux, which is a Flux implementation, comes in. It contains the application’s state, helping us to easily manipulate it. The data flow is unidirectional, making it easy to understand and predict. And in addition, it gives us some cool functionality like hot reload and time travel.

There are other implementations of these two, but for me the big advantage to start using them is the great community that is always willing to help, the abundance of resources that can get you started and a technology that is rapidly evolving and getting better. Not bad at all.

2048

I started using React and Redux a couple of months ago myself, but I think the best way to understand them even better is to try to explain them to other people and to build something a little different, in this case a game. I have chosen the 2048 game by Gabriele Cirulli, because it’s fun and I personally have spent considerable amount of time getting to that sacred 2048 tile.

I’ve added little twist to the game, by introducing a gift tile (well, it was Christmas, gifts everywhere) that can be merged with any other tile, doubling its value. The chance for this tile to appear would be 1%.

The final result:

This is not going to be a guide. I am just going over the technologies used and some code samples. I’ve always thought that the best way to learn a new technology is to actually use it or try to understand the code of other developers. That’s why I strongly suggest you to check the repository. It is thoroughly documented and you should have no problems reason about what each piece of code accomplishes.

Or you can check the end result in this DEMO.

The goal of this game was to try something new. There are a lot of parts that can be improved, so feel free to contribute.

Technology Stack

React and Redux are great, but to get the best out of them we will use them with other tools.

Webpack
A module bundler, letting us split our code. Has great and easy integration with 3rd party loaders and plugins and is easily customisable.

Babel
A compiler, letting us write the latest version of JavaScript today. We are going to use it to write ES2015 and to even use decorators. It also has support for React, letting us transpile JSX to JS.

ImmutableJS
An immutable data collection, making it plain simple to track any changes in the state.

ESLint
A JavaScript linter, helping us keep best practices and preventing us from making errors. It has very useful ES2015 and React rules, which are going to be helpful in making the code consistent.

Structure

The directory structure is self-explanatory and expected:

- src/
- images/
- styles/
- scripts/
- actions/
- components/
- containers/
- reducers/
- utils/
- helpers.js
- main.js

main.js — an entry point file for the application. We connect our module bundler to this file, so it can have access to everything connected as well.

helper.js — helper functions, directly tied to this game.

containers/ — containing all top level components. Most of the time we can think of containers as pages. In this case we have only one page, which is the game but usually in a more complex application we would have more of these.

components/ — the building blocks of our application. Here we are going to have our Board, which is going to contain a Grid, that is later going to be filled with Tiles, with which the game is played.

actions/ — all actions that are expected to happen in our application. No changes should be made to the state, unless they are made with the actions that we declare here.

reducers/ — the place where we explain how the application state should change in accordance to the called actions. This is the only place where the state can be changed.

utils/ — utility functions helping us to extract common functionality. These are abstract ones, that can be easily used in another application as well.

Code Samples

Let’s look at some code samples. This is not a guide, so I am just going over my thought process when implementing these.

Initial State

When we start a Redux implementation, it’s really important to know what our state is going to look like. This can help us with getting a better perspective of the application. There’s no way to know every bit of functionality we are going to need, but we can at least figure out the core.

In this game we know that we need to track if the player has won. We have two states for that — win or lose, so we can use a boolean — `true` and `false`. And a default value of `null` when the final result is not known yet.

There is also score in this game and we need a way to track it, starting from zero.

const defaultState = Map({
win: null,
score: 0
});

For the actual implementation of the game we have empty cells that need to be filled with tiles. We are going to separate them, because by having a collection of empty cells, we can easily choose random cell that should be filled with a tile. Then we just remove that cell from the collection.

That cell is not actually removed, it’s just moved in the grid. The grid is the actual representation of the game board. It contains arrays of arrays, each one representing a cell and each one being filled with tiles, which positions we get from the cells collection.

`generateCells` and `generateGrid` are just generators that iterate over the size of the board and create the corresponding structure.

const defaultState = Map({
win: null,
score: 0,
cells: generateCells(SIZE),
grid: generateGrid(SIZE)
});

Another thing we need is to save the game and it would be useful to have a property that specifies the difference between a saved and currently played game.

Also we need a method that checks if there is a saved game. If there is such it will load its state, if not it will just use the default state.

const defaultState = Map({
win: null,
score: START_SCORE,
cells: generateCells(SIZE, SIZE),
grid: generateGrid(SIZE, SIZE),
fromSaved: false
});
const initialState = startSavedGame() || defaultState;

Adding a Tile

As we already know, we have empty cells collection from where we can choose a random empty cell with its position and move it in the grid, which represents the actual board (the new tile will appear on screen only when in the grid).

With ImmutableJS, we are updating the cell positioned in the path specified by the first parameter (for example [‘grid’, 0, 1]). After that the found cell is passed in the callback function and we add our new tile to it. Since the presentation of the tile initially is just its position, we are also adding an `id` which is tracked and lets us later use it as key for each React component. We are also adding the value of the tile, which can be either a passed value (when we are merging two tiles) or the initial value (which is 2).

Stateless Components

In the end of each game we need to show an overlay over the board with a message and a new game button. This sounds like a very simple component, it just takes data and shows it and doesn’t need to hold any state. And because of that we can use a stateless component, which is just a pure function that returns JSX, like the `render()` method.

We have a function, taking two arguments — the win state and a new game callback function, which is going to be handled by the parent component. We are passing a callback, because if we do the same for other components as well, we will have all the logic in one place and the components will be just simple and pure representations of the state.

Conclusion

I think that covers, some interesting parts of the game. For me it was a lot of fun working on it. It allowed me to better understand how the used libraries work together and to get experience building something different — a game.

Hope you all have a fun weekend.

DEMO

--

--

Dimitar Valchanov

Frond-end developer, passionate about creating beautiful applications.