2048 is a single player sliding puzzle. Goal of the game is to combine tiles with powers of 2 and get to the tile containing 2048 (that is 2 to the 10-th power).
Link to Wikipedia: https://en.wikipedia.org/wiki/2048_(video_game)
Here is a live coding session that shows how it can be done.
Duration: 5:00
Disclaimer: never use this code in production. It was created for fun.
Let's break down the solution and comment on some complex or interesting things.
This is by far the most complex mini-project so far involving a number of non-trivial solutions and tricks.
Technically the game is built using JavaScript without any libraries and should work in any modern browser.
0:13 - as usual starting with HTML template. Our game field with be a HTML table. Right now we are defining only the top-level table element. All rows and cells will be created later by means of JavaScript function - this way it will be easier to deal with cells later.
0:21 - quite routine HTML operations are done here. We need to create table rows and cells programmatically. We do it this way because we need to apply some styling for all of them. Also we would benefit from storing references to the HTML elements - we'll need them in near future for manipulations.
I feel that it is quite annoying that in most of the projects where we deal with rectangular game field more or less the same code is used. Maybe it makes sense to extract it to some shared library.
Pay attention to "cell" class set on each cell - we'll define the style of cell later and it will be one for all cells.
In the end we'll get htmlElements
array initialized and it contains references to all cells on game field.
0:52 - initialization function will be used to prepare the game. Right now it only creates game field elements.
0:55 - define the styles. Also not doing anything complex here. Each cell will be rectangular with a black border around. Make sure that the text is aligned to the middle of the cell.
1:07 - right now we have only visual part. Now we need to define internal model. It will be an array containing numbers that should be displayed in the cells. For convenience using 0 as empty cell.
Initially the game field is empty.
Also for the game we need to create new numbers in empty cells. In original game there was a rule that in 90% of the cases new number should be 2 and in remaining 10% it is 4. Let's implement the same. For that purpose generate 2 random numbers for X and Y coordinate and check if the cell is still empty.
The game will start with 3 filled cells.
1:30 - draw
function will be used to synchronize the internal state with the HTML elements. Remember that we'll be working only with cells
array and then using draw
function to draw the results of every move.
Here htmlElements
array comes into play - we have references to all table cells and it makes it easy to find the proper cell.
1:50 - now we have game model ready and next step would be to implement the actions that will happen when player will press up, down, left and right arrows.
The cells should slide to corresponding direction.
First of all we need to define functions that do the moves. For simplicity we define separate function for each direction.
Also coding the event listener for key down events. It will be triggered every time a user presses any key (with a focus on HTML document). Some magic numbers are used here for the key codes. You can either google them or do an intermediate step to output values of keyCode
to console and play around a bit.
Right now key press should trigger one of 4 functions.
2:11 - probably the most important and complex step of the project. We are getting to implementation of move logics.
For complex tasks it makes sense to break it down into smaller manageable part. This approach will be taken also here.
Imagine that we need to slide only one single row to the left.
Let's take the following row as an example and let's see how it behaves during sliding operation.
0 | 2 | 2 | 2 |
2 | 2 | 2 |
4 | 0 | 2 |
4 | 2 |
4 | 2 | 0 | 0 |
slide
function. And it will be our building block for all moves.
2:48 - having slide
function at hand the moveLeft
is trivial - it's basically just slide of each and every row.
2:58 - moving or sliding to the right requires a trick. Of course we can implement the same sliding but to the right, but actually we can just mirror the board and reuse existing sliding functionality.
Let's review it on an example:
0 | 0 | 0 | 0 |
0 | 2 | 4 | 0 |
2 | 2 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 4 | 2 | 0 |
0 | 0 | 2 | 2 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 4 | 2 |
0 | 0 | 0 | 4 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
2 | 4 | 0 | 0 |
4 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
moveRight
function is using exactly this approach.
3:22 - moving up and down also can be done with a little trick - quite similar to moveRight
function. But instead of mirroring we'll use transposing - it is mirroring the board against the main diagonal.
Let's take the same example and see how it works with moving up:
moveRight
:
0 | 0 | 0 | 0 |
0 | 2 | 4 | 0 |
2 | 2 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 2 | 0 |
0 | 2 | 2 | 0 |
0 | 4 | 0 | 0 |
0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
2 | 4 | 4 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
moveDown
function can be implemented as combination of mirror
, transpose
and moveLeft
. Or just transpose
and moveRight
. You can manually check that either of these approaches work.
3:47 - we have a minor flaw in our logics. There can be situations when some move (e.g. move up) does not change the game field. In other words not all moves are possible.
Let's examine the following situation:
8 | 4 | 2 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
slide
function and check if it changes a row or not. And then propagate this changed
flag to all move functions.
arrayA.join(',') == arrayB.join(',')
4:13 - currently it's quite hard to distinguish the cells. We can use different colors for different number cells as it was in original game.
We'll use different colors and different algorithm how to calculate colors. The formula is based on HSL
(hue, saturation, lightness) color model:
hsl(20 + 24 * Math.log2(2048 / v), 100%, 50%)
Saturation is 100% to get bright color, it's ok to have 50% lighness. The most interesting part is hue component.
For hue we are choosing an interval from 20 to 260. Smallest value on the field will be 2 meaning that hue value will be 260. For 2048 hue value will be 20.
Refer to the following table for used colors.
2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 |
4:32 - last bit of the solution - check when the game is over and it is not possible to do any move. There are 2 cases when game can continue:
isGameOver
function to check both scenarios. And update key press handler to check if game is over after each move.
As you see this mini-project contains some non-trivial solutions and tricks. However the implementation is quite straight-forward.
What makes is so long is the need to iterate across whole game field in multiple functions.
Sources: https://github.com/5minute/examples/tree/main/2048
See live results:https://jsfiddle.net/uft4jgd9/