import { getSnakeLength , snakeToCells } from "@snk/types/snake" ; import type { Snake } from "@snk/types/snake" ; import type { Color } from "@snk/types/grid" ; import type { Point } from "@snk/types/point" ; import { h } from "./utils" ; export type Options = { colorDots : Record < Color , string > ; colorEmpty : string ; colorBorder : string ; colorSnake : string ; sizeCell : number ; sizeDot : number ; sizeBorderRadius : number ; } ; const percent = ( x ) => ( x * 100 ) . toFixed ( 2 ) ; const lerp = ( k , a , b ) => ( 1 - k ) * a + k * b ; export const createSnake = ( chain , { sizeCell , sizeDot } , duration ) => { const snakeN = chain [ 0 ] ? getSnakeLength ( chain [ 0 ] ) : 0 ; const snakeParts = Array . from ( { length : snakeN } , ( ) => [ ] ) ; for ( const snake of chain ) { const cells = snakeToCells ( snake ) ; for ( let i = cells . length ; i -- ; ) snakeParts [ i ] . push ( cells [ i ] ) ; } const svgElements = snakeParts . map ( ( _ , i , { length } ) => { const dMin = sizeDot * 0.8 ; const dMax = sizeCell * 0.9 ; const iMax = Math . min ( 4 , length ) ; const u = ( 1 - Math . min ( i , iMax ) / iMax ) ** 2 ; const s = lerp ( u , dMin , dMax ) ; const m = ( sizeCell - s ) / 2 ; const r = Math . min ( 4.5 , ( 4 * s ) / sizeDot ) ; return h ( "rect" , { class : `s s${ i }` , x : m . toFixed ( 1 ) , y : m . toFixed ( 1 ) , width : s . toFixed ( 1 ) , height : s . toFixed ( 1 ) , rx : r . toFixed ( 1 ) , ry : r . toFixed ( 1 ) } ) ; } ) ; const transform = ( { x , y } ) => `transform:translate(${ x * sizeCell }px,${ y * sizeCell }px)` ; const styles = [ `.s{ 
      shape-rendering:geometricPrecision;
      fill:var(--cs);
      animation: none linear ${ duration }ms infinite
    }` , ... snakeParts . map ( ( positions , i ) => { const id = `s${ i }` ; const animationName = id ; return [ `@keyframes ${ animationName } {` + removeInterpolatedPositions ( positions . map ( ( tr , i , { length } ) => ( { ... tr , t : i / length } ) ) ) . map ( ( p ) => `${ percent ( p . t ) }%{${ transform ( p ) }}` ) . join ( "" ) + "}" , `.s.${ id }{${ transform ( positions [ 0 ] ) };animation-name: ${ animationName }}` , ] ; } ) , ] . flat ( ) ; return { svgElements , styles } ; } ; const removeInterpolatedPositions = < T extends Point > ( arr ) => arr . filter ( ( u , i , arr ) => { if ( i - 1 < 0 || i + 1 >= arr . length ) return true ; const a = arr [ i - 1 ] ; const b = arr [ i + 1 ] ; const ex = ( a . x + b . x ) / 2 ; const ey = ( a . y + b . y ) / 2 ; return ! ( Math . abs ( ex - u . x ) < 0.01 && Math . abs ( ey - u . y ) < 0.01 ) ; } ) ;