← Así se hace un juego: Snake de Daily Challenge · Lección 4/16
Letras, sprites y paleta
Una fuente de 3 × 5
Daily Challenge no carga una tipografía para el marcador: cada letra es un dibujo de 3 píxeles de ancho por 5 de alto, guardado como cinco textos de unos y ceros. Así está la A en renderer.js:
'A':['010','101','111','101','101']
Para escribir, text() recorre cada letra, y por cada '1' pinta un píxel de 1 × 1. Después avanza 4 píxeles: 3 de la letra y 1 de espacio. Por eso un texto mide largo × 4 − 1 píxeles.
$ node leccion.js
Pulsa CORRER (o Ctrl+Enter).
Qué mirar
Ahí está «SNAKE»: 5 píxeles de alto y 19 de ancho. La fuente entera tiene unas 60 entradas como éstas; agrega la T (['111','010','010','010','010']) y escribe «SKATE».
Toda la lección en una página
Una fuente de 3 × 5
Daily Challenge no carga una tipografía para el marcador: cada letra es un dibujo de 3 píxeles de ancho por 5 de alto, guardado como cinco textos de unos y ceros. Así está la A en renderer.js:
'A':['010','101','111','101','101']
Para escribir, text() recorre cada letra, y por cada '1' pinta un píxel de 1 × 1. Después avanza 4 píxeles: 3 de la letra y 1 de espacio. Por eso un texto mide largo × 4 − 1 píxeles.
// Cinco letras de la fuente real de renderer.js
const FONT = {
S: ["111", "100", "111", "001", "111"],
N: ["110", "101", "101", "101", "101"],
A: ["010", "101", "111", "101", "101"],
K: ["101", "101", "110", "101", "101"],
E: ["111", "100", "111", "100", "111"],
};
function text(str) {
const renglones = ["", "", "", "", ""];
for (const ch of str) {
const g = FONT[ch];
for (let r = 0; r < 5; r++)
renglones[r] += g[r].replaceAll("1", "#").replaceAll("0", " ") + " ";
}
for (const r of renglones) console.log(r);
}
text("SNAKE");
console.log("ancho en píxeles:", "SNAKE".length * 4 - 1);
Ahí está «SNAKE»: 5 píxeles de alto y 19 de ancho. La fuente entera tiene unas 60 entradas como éstas; agrega la T (['111','010','010','010','010']) y escribe «SKATE».
El bicho de premio
Los dibujos más grandes usan la misma idea. En Snake, cada cinco frutas sale un bicho de premio, como en el Snake II de Nokia: un sprite de 10 × 10 en el que '1' es píxel encendido y '.' apagado. Es de 1 bit: no guarda colores, sólo «hay o no hay». El color se lo pone quien lo pinta:
r.sprite(BICHO, x, y, PAL.yellow)
Como es sólo texto, se puede transformar con código: voltearlo, contarlo, compararlo.
// El sprite tal cual está en snake.js
const BICHO = [".1......1.", "..1....1..", "...1111...", "..111111..", ".11.11.11.",
".11111111.", "..111111..", ".1.1..1.1.", "1..1..1..1", ".........."];
const pintar = filas => filas.forEach(f => console.log(f.replaceAll("1", "#").replaceAll(".", " ")));
pintar(BICHO);
const encendidos = BICHO.join("").split("").filter(c => c === "1").length;
console.log("píxeles encendidos:", encendidos, "de", BICHO.length * BICHO[0].length);
console.log("--- de cabeza:");
pintar([...BICHO].reverse());
Antenas, ojos (los huecos del renglón 5) y patas. El renglón de abajo está vacío a propósito: el bicho ocupa una casilla de 10 × 10 y así no se pega a la de abajo.
128 colores con una fórmula
La Atari 2600 tenía una paleta fija: 16 tonos por 8 luminosidades. core/palette.js la reproduce con una función atari(hue, lum) que devuelve un color CSS:
- Tono
0son los grises:v = lum * 34, de negro a casi blanco. - Los demás tonos dan la vuelta al círculo cromático al revés, como el chip de video de la consola (el TIA):
h = (55 - (hue - 1) * 24 + 720) % 360. - Más luminosidad sube la
ldel HSL y baja un poco la saturación.
Luego PAL les pone nombre a los que más se usan, para no recordar números: PAL.green es atari(13, 4).
// La función real de core/palette.js
function atari(hue, lum) {
hue &= 15; lum &= 7;
if (hue === 0) { const v = Math.round(lum * 34); return "rgb(" + v + "," + v + "," + v + ")"; }
const h = (55 - (hue - 1) * 24 + 720) % 360;
const l = 14 + lum * 10;
const s = 90 - lum * 5;
return "hsl(" + h + "," + s + "%," + l + "%)";
}
const PAL = { black: atari(0, 0), white: atari(0, 7), yellow: atari(1, 6),
red: atari(4, 4), blue: atari(9, 4), green: atari(13, 4), lgreen: atari(13, 6) };
for (const [nombre, color] of Object.entries(PAL)) console.log(nombre.padEnd(7), color);
La serpiente usa green para el cuerpo y lgreen (el mismo tono, más claro) para la cabeza; los muros son blue y la fruta, red. hue &= 15 recorta cualquier número a 0-15: un tono fuera de rango nunca rompe nada.