← Todos los artículos

Cómo construí 2048 en un fin de semana — todo el bucle del juego

2026-06-29

Hace poco añadí 2048 a ToolKoala, y la sorpresa más agradable fue lo pequeño que es el juego de verdad. Las animaciones y los pulidos llevan tiempo, pero las reglas — deslizar, fusionar, generar, comprobar el fin de la partida — caben en unas sesenta líneas. Así funciona todo el bucle.

El tablero es solo un array plano

La cuadrícula es de 4×4, pero la guardo como un array plano de 16 números, donde 0 significa vacío:

[2, 0, 0, 4,
 0, 2, 0, 0,
 0, 0, 0, 0,
 4, 0, 0, 2]

Los arrays planos son fáciles de copiar, comparar y guardar en localStorage. La fila r, columna c vive en el índice r * 4 + c.

Una sola función hace el trabajo de verdad

Cada movimiento — izquierda, derecha, arriba, abajo — es la misma operación sobre una línea de cuatro fichas: empuja todo hacia un extremo y fusiona los vecinos iguales una sola vez. Eso es una única función:

function compress(line) {
  const arr = line.filter(v => v)   // descarta los ceros
  const res = []
  let gained = 0
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === arr[i + 1]) {     // dos fichas iguales se tocan → fusionar
      res.push(arr[i] * 2)
      gained += arr[i] * 2
      i++                            // salta la que acabamos de fusionar
    } else {
      res.push(arr[i])
    }
  }
  while (res.length < 4) res.push(0) // rellena de vuelta hasta longitud 4
  return { line: res, gained }
}

El i++ tras una fusión es la regla que impide que 4 4 4 4 se colapse en un solo 16. Se convierte en 8 8 — cada ficha se fusiona como mucho una vez por movimiento.

Cuatro direcciones a partir de una

Solo escribí "deslizar a la izquierda". Las otras tres la reutilizan:

  • Derecha — invierte la fila, comprime a la izquierda, invierte de vuelta.
  • Arriba / Abajo — lee el tablero por columnas en lugar de por filas, y luego la misma lógica de izquierda/derecha.

Así que un movimiento toma cada fila o columna, opcionalmente la invierte, ejecuta compress y la vuelve a escribir. Sin código separado para cada flecha — solo qué línea leo y si la volteo.

Genera, luego comprueba si estás atascado

Tras un movimiento que de verdad cambió el tablero, una nueva ficha aparece en una celda vacía al azar — un 2 la mayoría de las veces, un 4 de vez en cuando. Luego compruebo si todavía es posible algún movimiento: ¿hay celdas vacías, o dos vecinos iguales cualesquiera? Si no, es fin de la partida.

function canMove(g) {
  if (g.includes(0)) return true            // celda vacía → sí
  // cualquier vecino igual horizontal/vertical → todavía es posible una fusión
  // ...comprobado con un pequeño bucle...
  return false
}

La comprobación de "¿cambió de verdad el tablero?" importa: si un movimiento no hace nada, no deberías conseguir una ficha nueva gratis. Pulsar izquierda contra una pared tiene que ser una operación sin efecto.

El bug que me enseñó a usar refs

La primera versión leía el tablero directamente del estado de React dentro del manejador de teclas. Con repetición rápida de teclas, dos pulsaciones de flecha podían dispararse antes de que React volviera a renderizar, así que la segunda leía un tablero desactualizado y se perdían movimientos. La solución fue mantener un ref que reflejara el último tablero y la última puntuación, y hacer que el manejador de movimiento leyera el ref. El estado dirige lo que ves; el ref dirige la lógica. Después de eso, hasta aporrear las flechas se comporta bien.

Reanudar y mejor puntuación, todo local

Dos cosas viven en localStorage: tu mejor puntuación y el tablero actual. Guardo el tablero en cada movimiento; lo borro cuando termina la partida. Al cargar, si hay una partida sin terminar válida, la restauro y muestro una pequeña nota de "continuar" — el mismo patrón que uso para Sudoku y Sopa de letras. No se sube nada; tu partida es tuya, en tu dispositivo.

Pruébalo

Puedes jugar a 2048 aquí — teclado o deslizar, y funciona sin conexión una vez que la página ha cargado. Si te gusta este tipo de cosas, el generador de Sudoku tiene un algoritmo más interesante detrás.

Preguntas frecuentes

¿Es difícil de programar 2048? El núcleo es sorprendentemente pequeño — una función de "deslizar y fusionar una línea", reutilizada para las cuatro direcciones, más generar una ficha y comprobar el fin de la partida. El pulido (animaciones, controles táctiles, guardar el progreso) es donde se va la mayor parte del tiempo.

¿Cómo funciona la regla de fusión? Cuando dos fichas con el mismo número se empujan juntas, se combinan en una sola ficha con el doble del valor — y cada ficha solo puede fusionarse una vez por movimiento, así que una fila de cuatro 2 se convierte en dos 4, no en un 8.

¿Cómo se manejan las cuatro direcciones? Solo necesitas implementar una dirección. Derecha es izquierda con la fila invertida; arriba y abajo son la misma lógica aplicada a columnas en lugar de a filas.

¿El 2048 de ToolKoala guarda mi partida? Sí — tu mejor puntuación y el tablero actual se almacenan en el almacenamiento local de tu navegador, así que puedes cerrar la pestaña y retomarla luego. Nunca sale de tu dispositivo.

— Milo 🐨