DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

I Built 2048 in One Function and a 4 4 Array

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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()));
Enter fullscreen mode Exit fullscreen mode
  • 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]));
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Clickbait heading - at a quick count there are 9 functions in your source code