163 lines
3.9 KiB
TypeScript
163 lines
3.9 KiB
TypeScript
import {
|
|
copyGrid,
|
|
getColor,
|
|
isEmpty,
|
|
isInside,
|
|
setColorEmpty,
|
|
} from "@snk/types/grid";
|
|
import { getHeadX, getHeadY } from "@snk/types/snake";
|
|
import type { Snake } from "@snk/types/snake";
|
|
import type { Grid, Color, Empty } from "@snk/types/grid";
|
|
import type { Point } from "@snk/types/point";
|
|
import type { AnimationOptions } from "@snk/gif-creator";
|
|
import { createSnake } from "./snake";
|
|
import { createGrid } from "./grid";
|
|
import { createStack } from "./stack";
|
|
import { h } from "./xml-utils";
|
|
import { minifyCss } from "./css-utils";
|
|
|
|
export type DrawOptions = {
|
|
colorDots: Record<Color, string>;
|
|
colorEmpty: string;
|
|
colorDotBorder: string;
|
|
colorSnake: string;
|
|
sizeCell: number;
|
|
sizeDot: number;
|
|
sizeDotBorderRadius: number;
|
|
dark?: {
|
|
colorDots: Record<Color, string>;
|
|
colorEmpty: string;
|
|
colorDotBorder?: string;
|
|
colorSnake?: string;
|
|
};
|
|
};
|
|
|
|
const getCellsFromGrid = ({ width, height }: Grid) =>
|
|
Array.from({ length: width }, (_, x) =>
|
|
Array.from({ length: height }, (_, y) => ({ x, y }))
|
|
).flat();
|
|
|
|
const createLivingCells = (
|
|
grid0: Grid,
|
|
chain: Snake[],
|
|
cells: Point[] | null
|
|
) => {
|
|
const livingCells: (Point & {
|
|
t: number | null;
|
|
color: Color | Empty;
|
|
})[] = (cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
|
|
x,
|
|
y,
|
|
t: null,
|
|
color: getColor(grid0, x, y),
|
|
}));
|
|
|
|
const grid = copyGrid(grid0);
|
|
for (let i = 0; i < chain.length; i++) {
|
|
const snake = chain[i];
|
|
const x = getHeadX(snake);
|
|
const y = getHeadY(snake);
|
|
|
|
if (isInside(grid, x, y) && !isEmpty(getColor(grid, x, y))) {
|
|
setColorEmpty(grid, x, y);
|
|
const cell = livingCells.find((c) => c.x === x && c.y === y)!;
|
|
cell.t = i / chain.length;
|
|
}
|
|
}
|
|
|
|
return livingCells;
|
|
};
|
|
|
|
export const createSvg = (
|
|
grid: Grid,
|
|
cells: Point[] | null,
|
|
chain: Snake[],
|
|
drawOptions: DrawOptions,
|
|
animationOptions: Pick<AnimationOptions, "frameDuration">
|
|
) => {
|
|
const width = (grid.width + 2) * drawOptions.sizeCell;
|
|
const height = (grid.height + 5) * drawOptions.sizeCell;
|
|
|
|
const duration = animationOptions.frameDuration * chain.length;
|
|
|
|
const livingCells = createLivingCells(grid, chain, cells);
|
|
|
|
const elements = [
|
|
createGrid(livingCells, drawOptions, duration),
|
|
createStack(
|
|
livingCells,
|
|
drawOptions,
|
|
grid.width * drawOptions.sizeCell,
|
|
(grid.height + 2) * drawOptions.sizeCell,
|
|
duration
|
|
),
|
|
createSnake(chain, drawOptions, duration),
|
|
];
|
|
|
|
const viewBox = [
|
|
-drawOptions.sizeCell,
|
|
-drawOptions.sizeCell * 2,
|
|
width,
|
|
height,
|
|
].join(" ");
|
|
|
|
const style =
|
|
generateColorVar(drawOptions) +
|
|
elements
|
|
.map((e) => e.styles)
|
|
.flat()
|
|
.join("\n");
|
|
|
|
const svg = [
|
|
h("svg", {
|
|
viewBox,
|
|
width,
|
|
height,
|
|
xmlns: "http://www.w3.org/2000/svg",
|
|
}).replace("/>", ">"),
|
|
|
|
"<desc>",
|
|
"Generated with https://github.com/Platane/snk",
|
|
"</desc>",
|
|
|
|
"<style>",
|
|
optimizeCss(style),
|
|
"</style>",
|
|
|
|
...elements.map((e) => e.svgElements).flat(),
|
|
|
|
"</svg>",
|
|
].join("");
|
|
|
|
return optimizeSvg(svg);
|
|
};
|
|
|
|
const optimizeCss = (css: string) => minifyCss(css);
|
|
const optimizeSvg = (svg: string) => svg;
|
|
|
|
const generateColorVar = (drawOptions: DrawOptions) =>
|
|
`
|
|
:root {
|
|
--cb: ${drawOptions.colorDotBorder};
|
|
--cs: ${drawOptions.colorSnake};
|
|
--ce: ${drawOptions.colorEmpty};
|
|
${Object.entries(drawOptions.colorDots)
|
|
.map(([i, color]) => `--c${i}:${color};`)
|
|
.join("")}
|
|
}
|
|
` +
|
|
(drawOptions.dark
|
|
? `
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--cb: ${drawOptions.dark.colorDotBorder || drawOptions.colorDotBorder};
|
|
--cs: ${drawOptions.dark.colorSnake || drawOptions.colorSnake};
|
|
--ce: ${drawOptions.dark.colorEmpty};
|
|
${Object.entries(drawOptions.dark.colorDots)
|
|
.map(([i, color]) => `--c${i}:${color};`)
|
|
.join("")}
|
|
}
|
|
}
|
|
`
|
|
: "");
|