import { describe, expect, it } from 'vitest';
import { shiftFrame, bobFrame, squashFrame, mirrorFrame, swingFrame, deriveCycle } from '../frames.mjs';
/** Кадр 4×4 из строк символов: '.' — прозрачный, '#' — непрозрачный белый. */
const frame = (rows) => {
const w = rows[0].length;
const h = rows.length;
const rgba = new Uint8Array(w * h * 4);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
if (rows[y][x] === '#') rgba[(y * w + x) * 4 + 3] = 255;
}
}
return rgba;
};
/** Кадр обратно в строки символов (по alpha). */
const toRows = (rgba, w, h) => {
const rows = [];
for (let y = 0; y < h; y++) {
let row = '';
for (let x = 0; x < w; x++) row += rgba[(y * w + x) * 4 + 3] ? '#' : '.';
rows.push(row);
}
return rows;
};
const SRC = frame([
'....',
'.##.',
'.##.',
'....'
]);
describe('shiftFrame', () => {
it('сдвигает содержимое, пустота — прозрачность', () => {
expect(toRows(shiftFrame(SRC, 4, 4, 1, 0), 4, 4)).toEqual([
'....',
'..##',
'..##',
'....'
]);
expect(toRows(shiftFrame(SRC, 4, 4, 0, -1), 4, 4)).toEqual([
'.##.',
'.##.',
'....',
'....'
]);
// Сдвиг за край — кадр полностью прозрачный
expect(toRows(shiftFrame(SRC, 4, 4, 4, 0), 4, 4)).toEqual(['....', '....', '....', '....']);
});
});
describe('bobFrame', () => {
it('поднимает тело на dy (по умолчанию 1)', () => {
expect(toRows(bobFrame(SRC, 4, 4), 4, 4)).toEqual([
'.##.',
'.##.',
'....',
'....'
]);
expect(toRows(bobFrame(SRC, 4, 4, 2), 4, 4)).toEqual([
'.##.',
'....',
'....',
'....'
]);
});
});
describe('squashFrame', () => {
it('приседание: спрайт на 1 px вниз, нижний ряд срезается', () => {
const src = frame([
'....',
'.##.',
'.##.',
'.##.'
]);
expect(toRows(squashFrame(src, 4, 4), 4, 4)).toEqual([
'....',
'....',
'.##.',
'.##.'
]);
// Спрайт стал ниже: было 3 ряда содержимого, осталось 2
const out = squashFrame(src, 4, 4);
expect(out[(0 * 4 + 1) * 4 + 3]).toBe(0);
expect(out[(3 * 4 + 1) * 4 + 3]).toBe(255);
});
});
describe('mirrorFrame', () => {
it('отражает по горизонтали', () => {
const src = frame([
'#...',
'.##.',
'....',
'....'
]);
expect(toRows(mirrorFrame(src, 4, 4), 4, 4)).toEqual([
'...#',
'.##.',
'....',
'....'
]);
});
});
describe('swingFrame', () => {
it('поворот вокруг низ-центра: 0 рад — кадр без изменений', () => {
expect(swingFrame(SRC, 4, 4, 0)).toEqual(SRC);
});
it('точка опоры остаётся на месте при малом угле', () => {
const out = swingFrame(SRC, 4, 4, 0.2);
// Нижняя пара пикселей фигуры (y=2) около оси — почти не сместилась
expect(out[(2 * 4 + 1) * 4 + 3]).toBe(255);
// Верх фигуры (y=1) ушла за пределы или сместилась — но что-то осталось
expect(toRows(out, 4, 4).join('')).not.toBe('................');
});
});
describe('deriveCycle', () => {
it('по дескриптору на кадр, операции применяются по порядку', () => {
const cycle = deriveCycle(SRC, 4, 4, [{}, { bob: 1 }, { mirror: true }]);
expect(cycle).toHaveLength(3);
expect(toRows(cycle[0], 4, 4)).toEqual(toRows(SRC, 4, 4));
expect(toRows(cycle[1], 4, 4)).toEqual(toRows(bobFrame(SRC, 4, 4), 4, 4));
expect(toRows(cycle[2], 4, 4)).toEqual(toRows(mirrorFrame(SRC, 4, 4), 4, 4));
});
});