2048 feels like a "real" game — smooth tiles, merging, score, game over. So people assume it needs a framework or a physics engine. It doesn't. The entire game is sixteen numbers and one function.
This is Day 5 of my GameFromZero series. Let me show you the trick.
The board is a 4×4 array
let grid = Array.from({ length: 4 }, () => Array(4).fill(0)); // 0 = empty
That's the whole game state. The screen is just a render of this array. Get the array right and the pixels follow.
One function does everything: slide()
Sliding a row to the left is four steps:
function slide(row) {
let r = row.filter(v => v); // 1. drop the zeros
for (let i = 0; i < r.length - 1; i++)
if (r[i] === r[i + 1]) { r[i] *= 2; r[i + 1] = 0; } // 2. merge each pair ONCE
r = r.filter(v => v); // 3. drop the merged-away zeros
while (r.length < 4) r.push(0); // 4. pad back to length 4
return r;
}
The subtle rule: [2,2,2,2] must become [4,4,0,0], not [8,0,0,0]. Because we zero out the second tile right after merging and then filter, a tile can't merge twice in one move. That single detail is what makes it play like real 2048.
Four directions, ONE function
You only wrote slide() for LEFT. You get the other three for free:
if (dir === "L") grid = grid.map(slide);
if (dir === "R") grid = grid.map(row => slide([...row].reverse()).reverse());
if (dir === "U") grid = transpose(transpose(grid).map(slide));
if (dir === "D") grid = transpose(transpose(grid).map(r => slide([...r].reverse()).reverse()));
- Right = reverse the row, slide left, reverse back.
- Up/Down = transpose the grid so columns become rows, slide, transpose back.
Transpose is a two-liner:
const transpose = g => g[0].map((_, c) => g.map(row => row[c]));
Spawn only on a real move
After a move, drop a new tile — 90% a 2, 10% a 4 — into a random empty cell. But only if the board actually changed (otherwise pressing into a wall would flood the board):
const before = JSON.stringify(grid);
// ...do the move...
if (JSON.stringify(grid) !== before) spawn();
Game over = no moves anywhere
It's only over when the board is full and no two neighbours are equal:
function movesLeft() {
for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) {
if (!grid[r][c]) return true;
if (c < 3 && grid[r][c] === grid[r][c + 1]) return true;
if (r < 3 && grid[r][c] === grid[r + 1][c]) return true;
}
return false;
}
The big idea
2048 is your first non-realtime game. Snake and Pong tick on a 60 FPS timer. 2048 only updates on a keypress. Same skeleton either way: state → transform → render. Master that shape once and every turn-based puzzle is the same evening's work.
▶️ Play it + read the step-by-step breakdown: https://dev48v.infy.uk/game/day5-2048.html
This was Day 5 of GameFromZero. A new game every day, built from scratch.
Top comments (1)
Clickbait heading - at a quick count there are 9 functions in your source code