Spaces:
Paused
Paused
Download src/web/js/snakeMotion.js from deepondas/laya-snake: direct link, hf CLI and curl.
- Browser
- Download file 4.36 kB
-
https://huggingface.co/spaces/deepondas/laya-snake/resolve/main/src/web/js/snakeMotion.js
- Command line
-
hf download hf://spaces/deepondas/laya-snake/src/web/js/snakeMotion.js
-
curl -L -o snakeMotion.js https://huggingface.co/spaces/deepondas/laya-snake/resolve/main/src/web/js/snakeMotion.js
4.36 kB
| export const GLIDE_MS = 160; | |
| // The previous snake and when the latest move started, for visual interpolation only. | |
| export const motion = { prev: null, at: 0 }; | |
| // Recent food pickups, for the 2D sparkle. | |
| export const effects = []; | |
| // Remembers the previous snake so frames can glide between cells, and marks eaten food. | |
| export function trackMotion(old, s) { | |
| const now = performance.now(); | |
| while (effects.length && now - effects[0].at > 1000) effects.shift(); | |
| if (!old || !old.snake.length || !s.snake.length || old.round !== s.round) { | |
| motion.prev = null; | |
| return; | |
| } | |
| const [ox, oy] = old.snake[0], [nx, ny] = s.snake[0]; | |
| if (ox === nx && oy === ny) return; | |
| if (Math.abs(ox - nx) + Math.abs(oy - ny) === 1) { motion.prev = old.snake; motion.at = now; } | |
| else motion.prev = null; | |
| if (s.score > old.score) effects.push({ x: nx + 0.5, y: ny + 0.5, at: now }); | |
| } | |
| // Allocation-free polyline through the snake in cell units, head first, with arc lengths. | |
| export class Centerline { | |
| constructor() { | |
| this.n = 0; this.total = 0; this.cap = 0; | |
| this.x = this.y = this.d = null; | |
| this.px = new Float32Array(64); this.py = new Float32Array(64); | |
| } | |
| // Grows the buffers to hold at least `n` samples. | |
| ensure(n) { | |
| if (n <= this.cap) return; | |
| this.cap = Math.max(n, this.cap * 2, 256); | |
| this.x = new Float32Array(this.cap); this.y = new Float32Array(this.cap); this.d = new Float32Array(this.cap); | |
| } | |
| // Appends one sample. | |
| push(x, y) { this.x[this.n] = x; this.y[this.n] = y; this.n++; } | |
| // Writes the point `dist` cells from the head into `out` (clamped to the body). | |
| pointAt(dist, out) { | |
| const n = this.n; | |
| if (n === 1 || dist <= 0) { out.x = this.x[0]; out.y = this.y[0]; return out; } | |
| if (dist >= this.total) { out.x = this.x[n - 1]; out.y = this.y[n - 1]; return out; } | |
| let lo = 0, hi = n - 1; | |
| while (hi - lo > 1) { const m = (lo + hi) >> 1; if (this.d[m] <= dist) lo = m; else hi = m; } | |
| const span = this.d[hi] - this.d[lo], t = span > 1e-9 ? (dist - this.d[lo]) / span : 0; | |
| out.x = this.x[lo] + (this.x[hi] - this.x[lo]) * t; | |
| out.y = this.y[lo] + (this.y[hi] - this.y[lo]) * t; | |
| return out; | |
| } | |
| } | |
| // Fills `cl` for this frame: cell centres, head and tail gliding between the last two states, with | |
| // every corner replaced by a quadratic curve between the midpoints of its two edges. The curve | |
| // stays inside the corner cell, so it never cuts across a cell the snake is not in. | |
| export function buildCenterline(state, now, cl) { | |
| const cells = state.snake, n = cells.length; | |
| cl.n = 0; cl.total = 0; | |
| if (!n) return cl; | |
| if (cl.px.length < n + 1) { cl.px = new Float32Array((n + 1) * 2); cl.py = new Float32Array((n + 1) * 2); } | |
| const px = cl.px, py = cl.py; | |
| for (let i = 0; i < n; i++) { px[i] = cells[i][0] + 0.5; py[i] = cells[i][1] + 0.5; } | |
| let m = n; | |
| const e = motion.prev ? Math.min(1, Math.max(0, (now - motion.at) / GLIDE_MS)) : 1; | |
| const ease = e * (2 - e); | |
| if (n > 1 && ease < 1) { | |
| px[0] = px[1] + (px[0] - px[1]) * ease; py[0] = py[1] + (py[0] - py[1]) * ease; | |
| const oldTail = motion.prev[motion.prev.length - 1], newTail = cells[n - 1]; | |
| if (oldTail[0] !== newTail[0] || oldTail[1] !== newTail[1]) { | |
| const ox = oldTail[0] + 0.5, oy = oldTail[1] + 0.5; | |
| px[m] = ox + (px[n - 1] - ox) * ease; py[m] = oy + (py[n - 1] - oy) * ease; m++; | |
| } | |
| } | |
| cl.ensure(m * 8 + 16); | |
| if (m === 1) { cl.push(px[0], py[0]); cl.d[0] = 0; return cl; } | |
| const fx = (px[0] + px[1]) / 2, fy = (py[0] + py[1]) / 2; | |
| for (let k = 0; k < 4; k++) cl.push(px[0] + (fx - px[0]) * k / 4, py[0] + (fy - py[0]) * k / 4); | |
| for (let i = 1; i < m - 1; i++) { | |
| const sx = (px[i - 1] + px[i]) / 2, sy = (py[i - 1] + py[i]) / 2; | |
| const ex = (px[i] + px[i + 1]) / 2, ey = (py[i] + py[i + 1]) / 2; | |
| for (let k = 0; k < 8; k++) { | |
| const t = k / 8, u = 1 - t; | |
| cl.push(u * u * sx + 2 * u * t * px[i] + t * t * ex, u * u * sy + 2 * u * t * py[i] + t * t * ey); | |
| } | |
| } | |
| const lx = px[m - 1], ly = py[m - 1], mx = (px[m - 2] + lx) / 2, my = (py[m - 2] + ly) / 2; | |
| for (let k = 0; k <= 4; k++) cl.push(mx + (lx - mx) * k / 4, my + (ly - my) * k / 4); | |
| let len = 0; | |
| cl.d[0] = 0; | |
| for (let i = 1; i < cl.n; i++) { len += Math.hypot(cl.x[i] - cl.x[i - 1], cl.y[i] - cl.y[i - 1]); cl.d[i] = len; } | |
| cl.total = len; | |
| return cl; | |
| } | |