snake: update

This commit is contained in:
2026-08-05 14:40:20 +02:00
parent d65d72d856
commit b540dbdcef
3 changed files with 58 additions and 51 deletions
+11 -12
View File
@@ -1,4 +1,4 @@
import { Point, lerp, drawCircle, drawLine, drawDashedLine, resizeCanvas } from "./common.js" import { Point, lerpPoint, drawCircle, drawLine, drawDashedLine, resizeCanvas } from "./common.js"
let points: Point[] = []; let points: Point[] = [];
@@ -8,24 +8,24 @@ function update(ctx: CanvasRenderingContext2D, time: number) {
const p = (Math.sin(0.001 * time) + 1.0) * 0.5; const p = (Math.sin(0.001 * time) + 1.0) * 0.5;
if (points.length == 2) { if (points.length == 2) {
const [start, end] = points; const [start, end] = points;
drawCircle(ctx, lerp(start, end, p), 1, 0xFF0000); drawCircle(ctx, lerpPoint(start, end, p), 1, 0xFF0000);
} else if (points.length === 3) { } else if (points.length === 3) {
const [a, b, c] = points; const [a, b, c] = points;
const ab = lerp(a, b, p); const ab = lerpPoint(a, b, p);
const bc = lerp(b, c, p); const bc = lerpPoint(b, c, p);
const abc = lerp(ab, bc, p); const abc = lerpPoint(ab, bc, p);
drawDashedLine(ctx, ab, bc, 0xFF0000); drawDashedLine(ctx, ab, bc, 0xFF0000);
drawCircle(ctx, ab, 1, 0xFF0000); drawCircle(ctx, ab, 1, 0xFF0000);
drawCircle(ctx, bc, 1, 0xFF0000); drawCircle(ctx, bc, 1, 0xFF0000);
drawCircle(ctx, abc, 1, 0xFFFFFF); drawCircle(ctx, abc, 1, 0xFFFFFF);
} else if (points.length === 4) { } else if (points.length === 4) {
const [a, b, c, d] = points; const [a, b, c, d] = points;
const ab = lerp(a, b, p); const ab = lerpPoint(a, b, p);
const bc = lerp(b, c, p); const bc = lerpPoint(b, c, p);
const cd = lerp(c, d, p); const cd = lerpPoint(c, d, p);
const abc = lerp(ab, bc, p); const abc = lerpPoint(ab, bc, p);
const bcd = lerp(bc, cd, p); const bcd = lerpPoint(bc, cd, p);
const abcd = lerp(abc, bcd, p); const abcd = lerpPoint(abc, bcd, p);
drawDashedLine(ctx, ab, bc, 0xFF0000); drawDashedLine(ctx, ab, bc, 0xFF0000);
drawDashedLine(ctx, bc, cd, 0xFF0000); drawDashedLine(ctx, bc, cd, 0xFF0000);
@@ -71,4 +71,3 @@ function init() {
} }
init(); init();
+17 -14
View File
@@ -14,7 +14,7 @@ export const DIRECTIONS: Point[] = [
{ x: 1, y: 1}, // SE { x: 1, y: 1}, // SE
]; ];
export function lerp(a: Point, b: Point, p: number) { export function lerpPoint(a: Point, b: Point, p: number) {
return { return {
x: a.x + (b.x - a.x) * p, x: a.x + (b.x - a.x) * p,
y: a.y + (b.y - a.y) * p y: a.y + (b.y - a.y) * p
@@ -22,20 +22,23 @@ export function lerp(a: Point, b: Point, p: number) {
} }
export function lerpRgb(a: number, b: number, t: number) { export function lerpRgb(a: number, b: number, t: number) {
const _lerp = (a: number, b: number, t: number) => (1-t)*a + b*t; const red = lerp(a >> 16, b >> 16, t)
const red = _lerp(a >> 16, b >> 16, t) const green = lerp((a >> 8) & 0xff, (b >> 8) & 0xff, t)
const green = _lerp((a >> 8) & 0xff, (b >> 8) & 0xff, t) const blue = lerp(a & 0xff, b & 0xff, t)
const blue = _lerp(a & 0xff, b & 0xff, t)
return (red << 16) | (green << 8) | blue return (red << 16) | (green << 8) | blue
} }
export function lerp(a: number, b: number, t: number) {
return (1 - t) * a + b * t;
}
export function quadraticBezier(a: Point, b: Point, c: Point, res=0.05) { export function quadraticBezier(a: Point, b: Point, c: Point, res=0.05) {
const eps = 0.001; // to prevent issues with float comparaison (p <= 1) const eps = 0.001; // to prevent issues with float comparaison (p <= 1)
const curve = []; const curve = [];
for (let p = 0; p - 1 < eps; p += res) { for (let p = 0; p - 1 < eps; p += res) {
const ab = lerp(a, b, p); const ab = lerpPoint(a, b, p);
const bc = lerp(b, c, p); const bc = lerpPoint(b, c, p);
const abc = lerp(ab, bc, p); const abc = lerpPoint(ab, bc, p);
curve.push(abc); curve.push(abc);
} }
return curve; return curve;
@@ -45,12 +48,12 @@ export function cubicBezier(a: Point, b: Point, c: Point, d: Point, res=0.05) {
const eps = 0.001; // to prevent issues with float comparaison (p <= 1) const eps = 0.001; // to prevent issues with float comparaison (p <= 1)
const curve = []; const curve = [];
for (let p = 0; p - 1 < eps; p += res) { for (let p = 0; p - 1 < eps; p += res) {
const ab = lerp(a, b, p); const ab = lerpPoint(a, b, p);
const bc = lerp(b, c, p); const bc = lerpPoint(b, c, p);
const cd = lerp(c, d, p); const cd = lerpPoint(c, d, p);
const abc = lerp(ab, bc, p); const abc = lerpPoint(ab, bc, p);
const bcd = lerp(bc, cd, p); const bcd = lerpPoint(bc, cd, p);
const abcd = lerp(abc, bcd, p); const abcd = lerpPoint(abc, bcd, p);
curve.push(abcd); curve.push(abcd);
} }
return curve; return curve;
+30 -25
View File
@@ -1,13 +1,14 @@
import Vec2d from "./vec.js"; import Vec2d from "./vec.js";
import { fillCircle, lerpRgb } from "./common.js"; import { fillCircle, lerp, lerpRgb } from "./common.js";
// constants // constants
const SNAKE_SIZE = 20; const SNAKE_RADIUS = 10;
const SNAKE_PADDING = 10; const SNAKE_PADDING = 10;
const SEGMENT_SPACING = SNAKE_SIZE + SNAKE_PADDING; const SEGMENT_SPACING = SNAKE_RADIUS + SNAKE_PADDING;
const HEAD_COLOR = 0xf43f5e; const HEAD_COLOR = 0xf43f5e;
const TAIL_COLOR = 0xf4c3cc; const TAIL_COLOR = 0xf4c3cc;
const APPLE_COLOR = 0x58f474; const APPLE_COLOR = 0x58f474;
const APPLE_COLOR_2 = 0x0a6f4b4;
const DIRECTIONS = { const DIRECTIONS = {
up: new Vec2d(0, -1), up: new Vec2d(0, -1),
@@ -32,10 +33,25 @@ function resizeCanvas(ctx: CanvasRenderingContext2D) {
let lastTime: number | undefined = undefined; let lastTime: number | undefined = undefined;
function update(ctx: CanvasRenderingContext2D, t: number) { function drawSnake(ctx: CanvasRenderingContext2D, bounds: Vec2d) {
if (lastTime == null) lastTime = t; for (let i = 0; i < snake.length - 1; ++i) {
const dt = (t - lastTime) / 1000; // s // draw a trail of overlapping circles by interpolating between the body positions
lastTime = t; for (let j = 0; j < interpolation_factor; ++j) {
const pos = snake[i].lerp(snake[i + 1], j / interpolation_factor).wrap(bounds);
const t = (i + j / interpolation_factor) / snake.length;
const color = lerpRgb(HEAD_COLOR, TAIL_COLOR, t);
const size = lerp(SNAKE_RADIUS, SNAKE_RADIUS / 2, t);
fillCircle(ctx, pos, size, color)
}
}
const tail = snake[snake.length - 1].wrap(bounds)
fillCircle(ctx, tail, SNAKE_RADIUS / 2, TAIL_COLOR)
}
function update(ctx: CanvasRenderingContext2D, time: number) {
if (lastTime == null) lastTime = time;
const dt = (time - lastTime) / 1000; // s
lastTime = time;
apple = apple as Vec2d; apple = apple as Vec2d;
const bounds = new Vec2d(ctx.canvas.width, ctx.canvas.height); const bounds = new Vec2d(ctx.canvas.width, ctx.canvas.height);
@@ -57,8 +73,7 @@ function update(ctx: CanvasRenderingContext2D, t: number) {
// convert snake_head coords to screen space // convert snake_head coords to screen space
//const head = wrapCoords(snake_body[0], bounds); //const head = wrapCoords(snake_body[0], bounds);
const head = snake[0].wrap(bounds) const head = snake[0].wrap(bounds)
// check if snake head is overlapping apple if (head.distance(apple) < SNAKE_RADIUS * 2) {
if (head.distance(apple) < SNAKE_SIZE) {
apple = Vec2d.random(bounds.x, bounds.y) apple = Vec2d.random(bounds.x, bounds.y)
const { x: lastX, y: lastY } = snake[snake.length - 1]; const { x: lastX, y: lastY } = snake[snake.length - 1];
snake.push(new Vec2d(lastX, lastY)); snake.push(new Vec2d(lastX, lastY));
@@ -67,26 +82,16 @@ function update(ctx: CanvasRenderingContext2D, t: number) {
} }
} }
// draw apple // drawing
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
fillCircle(ctx, apple, SNAKE_SIZE / 2, APPLE_COLOR); const t = (Math.sin(0.01 * time) + 1.0) * 0.5;
const apple_size = lerp(SNAKE_RADIUS, SNAKE_RADIUS * 0.9, t)
// draw snake fillCircle(ctx, apple, apple_size, APPLE_COLOR); // draw apple
for (let i = 0; i < snake.length - 1; ++i) { drawSnake(ctx, bounds);
// draw a trail of overlapping circles by interpolating between the body positions
for (let j = 0; j < interpolation_factor; ++j) {
const pos = snake[i].lerp(snake[i + 1], j / interpolation_factor).wrap(bounds);
const color = lerpRgb(HEAD_COLOR, TAIL_COLOR, (i + j / interpolation_factor) / snake.length)
fillCircle(ctx, pos, SNAKE_SIZE / 2, color)
}
}
const tail = snake[snake.length - 1].wrap(bounds)
fillCircle(ctx, tail, SNAKE_SIZE / 2, TAIL_COLOR)
window.requestAnimationFrame((t) => update(ctx, t)); window.requestAnimationFrame((t) => update(ctx, t));
} }
function init() { function init() {
const canvas = document.getElementById("game-canvas") as HTMLCanvasElement | null; const canvas = document.getElementById("game-canvas") as HTMLCanvasElement | null;
if (!canvas) throw new Error("unable to get canvas HTML element"); if (!canvas) throw new Error("unable to get canvas HTML element");
@@ -109,7 +114,7 @@ function init() {
case "ArrowRight": case "d": new_direction = DIRECTIONS.right; break; case "ArrowRight": case "d": new_direction = DIRECTIONS.right; break;
case "j": interpolation_factor++; break; case "j": interpolation_factor++; break;
case "k": interpolation_factor = Math.max(1, interpolation_factor-1); break; case "k": interpolation_factor = Math.max(1, interpolation_factor-1); break;
case "p": case "Space": case "p":
paused = !paused; paused = !paused;
setStatusLine(paused ? "paused" : "", null); setStatusLine(paused ? "paused" : "", null);
break; break;