/**
* Мини-канвас с палитрой: пиксель, ромб, шум, ASCII-карты спрайтов.
*/
import { RGBA, TRANSPARENT } from './palette.mjs';
import { encodePng } from './png.mjs';
export class Canvas {
constructor(width, height) {
this.width = width;
this.height = height;
this.data = Buffer.alloc(width * height * 4);
this.data.fill(0);
}
set(x, y, color, alpha = 255) {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) return;
const i = (y * this.width + x) * 4;
if (color === null) {
this.data.fill(0, i, i + 4);
return;
}
const [r, g, b] = RGBA[color] ?? color;
this.data[i] = r;
this.data[i + 1] = g;
this.data[i + 2] = b;
this.data[i + 3] = alpha;
}
get(x, y) {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) return null;
const i = (y * this.width + x) * 4;
if (this.data[i + 3] === 0) return null;
return [this.data[i], this.data[i + 1], this.data[i + 2], 255];
}
toPng() {
return encodePng(this.width, this.height, this.data);
}
}
/** Детерминированный ГПСЧ (mulberry32). */
export function rng(seed) {
return () => {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Залить ромб 2:1 по канвасу: base — базовый цвет; свет на верхних гранях,
* тень на нижних. light/dark — ключи палитры или null.
* Центр ромба (cx, cy), ширина w, высота h.
*/
export function fillDiamond(canvas, cx, cy, w, h, base, light = null, dark = null) {
const hw = w / 2;
const hh = h / 2;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const rx = x - hw + 0.5;
const ry = y - hh + 0.5;
if (Math.abs(rx) / hw + Math.abs(ry) / hh <= 1) {
canvas.set(cx - hw + x, cy - hh + y, base);
}
}
}
for (let i = 0; i < hw; i++) {
// Верхний склон: от левого угла к верхней вершине (свет) и от вершины к правому углу.
const leftY = cy - Math.round((i / hw) * hh);
const rightY = cy - hh + Math.round((i / hw) * hh);
if (light) {
canvas.set(cx - hw + i, leftY, light);
canvas.set(cx + i, rightY, light);
}
// Нижний склон: от левого угла вниз к нижней вершине (тень) и от неё к правому углу.
const botLeftY = cy + Math.round((i / hw) * hh);
const botRightY = cy + hh - Math.round((i / hw) * hh);
if (dark) {
canvas.set(cx - hw + i, botLeftY, dark);
canvas.set(cx + i, botRightY, dark);
}
}
}
/**
* ASCII-карта -> спрайт. Каждый символ — ключ палитры; '.' — прозрачный пиксель.
* Дополнительные символы передаются в extra ('x' -> 'P1' и т.п.).
*/
export function fromAscii(rows, extra = {}) {
const h = rows.length;
const w = Math.max(...rows.map((r) => r.length));
const canvas = new Canvas(w, h);
for (let y = 0; y < h; y++) {
for (let x = 0; x < rows[y].length; x++) {
const ch = rows[y][x];
if (ch === '.') {
continue;
}
const key = extra[ch] ?? ch;
if (!RGBA[key]) {
throw new Error(`Неизвестный символ '${ch}' в строке ${y}: ${rows[y]}`);
}
canvas.set(x, y, key);
}
}
return canvas;
}