Ir al contenido
Ayotl

← Así se hace un juego: Snake de Daily Challenge · Lección 8/16

Choques, comida y puntos

✕
1/7

¿Está libre?

Antes de meter la cabeza nueva, Snake pregunta si esa casilla está libre. Esto es isFree, completa:

if (x < 0 || y < 0 || x >= COLS || y >= ROWS) return false; if (this.walls[y][x]) return false; return !this.body.some(b => b.x === x && b.y === y);

Fuera del tablero, muro o cuerpo: perdiste. Pero hay una excepción fina. La cola se va a mover en el mismo paso, así que meter la cabeza donde ahora está la cola es válido… siempre que no estés comiendo, porque al comer la cola se queda:

const hitsTail = !eating && head.x === tail.x && head.y === tail.y;

  • x muro
  • c cabeza
  • s cuerpo (la de la izquierda arriba es la cola)
leccion.js

$ node leccion.js

Pulsa CORRER (o Ctrl+Enter).

Qué mirar

La casilla de la izquierda no está libre (es cuerpo), pero es la cola: Snake deja pasar. Sin esa excepción, una serpiente de cuatro no podría dar vueltas en un cuadrito de 2 × 2.

Toda la lección en una página

¿Está libre?

Antes de meter la cabeza nueva, Snake pregunta si esa casilla está libre. Esto es isFree, completa:

if (x < 0 || y < 0 || x >= COLS || y >= ROWS) return false; if (this.walls[y][x]) return false; return !this.body.some(b => b.x === x && b.y === y);

Fuera del tablero, muro o cuerpo: perdiste. Pero hay una excepción fina. La cola se va a mover en el mismo paso, así que meter la cabeza donde ahora está la cola es válido… siempre que no estés comiendo, porque al comer la cola se queda:

const hitsTail = !eating && head.x === tail.x && head.y === tail.y;

  • x muro
  • c cabeza
  • s cuerpo (la de la izquierda arriba es la cola)
const COLS = 6, ROWS = 5;
const walls = ["xxxxxx", "x....x", "x....x", "x....x", "xxxxxx"].map(f => [...f].map(c => c === "x"));
// cabeza en (3,1), luego (3,2), (2,2) y la cola en (2,1): un cuadrito
const body = [{ x: 3, y: 1 }, { x: 3, y: 2 }, { x: 2, y: 2 }, { x: 2, y: 1 }];

function isFree(x, y) {
  if (x < 0 || y < 0 || x >= COLS || y >= ROWS) return false;
  if (walls[y][x]) return false;
  return !body.some(b => b.x === x && b.y === y);
}

console.log("derecha (4,1):", isFree(4, 1));
console.log("arriba (3,0), muro:", isFree(3, 0));
console.log("fuera (9,9):", isFree(9, 9));
const tail = body[body.length - 1];
const head = { x: 2, y: 1 };   // izquierda: donde está la cola
console.log("izquierda (2,1):", isFree(head.x, head.y), "pero ¿es la cola?", head.x === tail.x && head.y === tail.y);

La casilla de la izquierda no está libre (es cuerpo), pero es la cola: Snake deja pasar. Sin esa excepción, una serpiente de cuatro no podría dar vueltas en un cuadrito de 2 × 2.

Comer: crecer y acelerar

Comer es no hacer pop: entra la cabeza y la cola se queda, así que la serpiente mide uno más. Además, en ese mismo paso:

this.score += 10 + Math.max(0, 8 - this.interval) * 5; // más rápido = más puntos this.interval = Math.max(3, 8 - Math.floor(this.eaten / 4)); this.events.push('eat');

Cada fruta vale 10, más 5 por cada tick que la serpiente ya va más rápido que al principio. Ir rápido es más difícil, y paga más. Y si al buscar lugar para la fruta nueva no queda ninguno libre, llenaste el tablero: la partida termina con 500 de premio.

let interval = 8, eaten = 0, score = 0;
for (let fruta = 1; fruta <= 24; fruta++) {
  eaten++;
  const puntos = 10 + Math.max(0, 8 - interval) * 5;
  score += puntos;
  if (fruta % 4 === 0) console.log("fruta", fruta, "vale", puntos, "| total", score, "| se mueve cada", interval);
  interval = Math.max(3, 8 - Math.floor(eaten / 4));
}

Las primeras cuatro frutas valen 10; de la 21 en adelante, 35 cada una. Fíjate en el orden del código real: los puntos se calculan con el intervalo de antes de comer, y luego se actualiza.

Dónde sale la fruta, el hambre y el bicho

La fruta sale en una casilla libre al azar: randomEmpty junta todas las casillas libres en un arreglo y elige una con el generador de la semilla del día (this.rng.pick(free)). Como el generador es el mismo en el teléfono y en el servidor, la fruta sale en el mismo lugar en los dos.

Dos reglas más, sacadas de Nokia y de una revisión de partidas:

  • Hambre: HAMBRE = 30 * 30, 30 segundos de ticks. Si no comes en ese tiempo, pierdes. Un comentario del código cuenta que sin ella, dar vueltas en un cuadrito no se acababa nunca.
  • Bicho de premio: cada 5 frutas aparece lejos de la cabeza y dura BICHO_DURA = 150 ticks (5 segundos). Vale 50 + 5 * Math.ceil(vida / 30): de 75 si vas por él al instante a 55 en el último segundo. No alarga ni acelera; es puntaje puro.
// El generador real de core/rng.js
function mulberry32(seed) {
  let a = seed >>> 0;
  const next = () => {
    a = (a + 0x6D2B79F5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
  return { pick: arr => arr[Math.floor(next() * arr.length)] };
}

const libres = [];
for (let y = 1; y < 4; y++) for (let x = 1; x < 5; x++) libres.push({ x, y });
const rngTelefono = mulberry32(2026), rngServidor = mulberry32(2026);
console.log("libres:", libres.length);
console.log("teléfono:", rngTelefono.pick(libres), "servidor:", rngServidor.pick(libres));

for (const vida of [150, 120, 60, 31, 30, 1])
  console.log("bicho con vida", vida, "vale", 50 + 5 * Math.ceil(vida / 30));

Con la misma semilla, el teléfono y el servidor eligen la misma casilla. Cambia una de las dos semillas y deja de coincidir: por eso la semilla del día la decide el servidor, no tu teléfono.