diff --git a/bezier-1.ts b/bezier-1.ts index 7892b5e..f3c2747 100644 --- a/bezier-1.ts +++ b/bezier-1.ts @@ -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[] = []; @@ -8,24 +8,24 @@ function update(ctx: CanvasRenderingContext2D, time: number) { const p = (Math.sin(0.001 * time) + 1.0) * 0.5; if (points.length == 2) { 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) { const [a, b, c] = points; - const ab = lerp(a, b, p); - const bc = lerp(b, c, p); - const abc = lerp(ab, bc, p); + const ab = lerpPoint(a, b, p); + const bc = lerpPoint(b, c, p); + const abc = lerpPoint(ab, bc, p); drawDashedLine(ctx, ab, bc, 0xFF0000); drawCircle(ctx, ab, 1, 0xFF0000); drawCircle(ctx, bc, 1, 0xFF0000); drawCircle(ctx, abc, 1, 0xFFFFFF); } else if (points.length === 4) { const [a, b, c, d] = points; - const ab = lerp(a, b, p); - const bc = lerp(b, c, p); - const cd = lerp(c, d, p); - const abc = lerp(ab, bc, p); - const bcd = lerp(bc, cd, p); - const abcd = lerp(abc, bcd, p); + const ab = lerpPoint(a, b, p); + const bc = lerpPoint(b, c, p); + const cd = lerpPoint(c, d, p); + const abc = lerpPoint(ab, bc, p); + const bcd = lerpPoint(bc, cd, p); + const abcd = lerpPoint(abc, bcd, p); drawDashedLine(ctx, ab, bc, 0xFF0000); drawDashedLine(ctx, bc, cd, 0xFF0000); @@ -71,4 +71,3 @@ function init() { } init(); - diff --git a/common.ts b/common.ts index 8d9c35a..3a0edf1 100644 --- a/common.ts +++ b/common.ts @@ -14,7 +14,7 @@ export const DIRECTIONS: Point[] = [ { x: 1, y: 1}, // SE ]; -export function lerp(a: Point, b: Point, p: number) { +export function lerpPoint(a: Point, b: Point, p: number) { return { x: a.x + (b.x - a.x) * 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) { - const _lerp = (a: number, b: number, t: number) => (1-t)*a + b*t; - const red = _lerp(a >> 16, b >> 16, t) - const green = _lerp((a >> 8) & 0xff, (b >> 8) & 0xff, t) - const blue = _lerp(a & 0xff, b & 0xff, t) + const red = lerp(a >> 16, b >> 16, t) + const green = lerp((a >> 8) & 0xff, (b >> 8) & 0xff, t) + const blue = lerp(a & 0xff, b & 0xff, t) 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) { const eps = 0.001; // to prevent issues with float comparaison (p <= 1) const curve = []; for (let p = 0; p - 1 < eps; p += res) { - const ab = lerp(a, b, p); - const bc = lerp(b, c, p); - const abc = lerp(ab, bc, p); + const ab = lerpPoint(a, b, p); + const bc = lerpPoint(b, c, p); + const abc = lerpPoint(ab, bc, p); curve.push(abc); } 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 curve = []; for (let p = 0; p - 1 < eps; p += res) { - const ab = lerp(a, b, p); - const bc = lerp(b, c, p); - const cd = lerp(c, d, p); - const abc = lerp(ab, bc, p); - const bcd = lerp(bc, cd, p); - const abcd = lerp(abc, bcd, p); + const ab = lerpPoint(a, b, p); + const bc = lerpPoint(b, c, p); + const cd = lerpPoint(c, d, p); + const abc = lerpPoint(ab, bc, p); + const bcd = lerpPoint(bc, cd, p); + const abcd = lerpPoint(abc, bcd, p); curve.push(abcd); } return curve; diff --git a/snake.ts b/snake.ts index 9505b24..bf6341b 100644 --- a/snake.ts +++ b/snake.ts @@ -1,13 +1,14 @@ import Vec2d from "./vec.js"; -import { fillCircle, lerpRgb } from "./common.js"; +import { fillCircle, lerp, lerpRgb } from "./common.js"; // constants -const SNAKE_SIZE = 20; +const SNAKE_RADIUS = 10; const SNAKE_PADDING = 10; -const SEGMENT_SPACING = SNAKE_SIZE + SNAKE_PADDING; +const SEGMENT_SPACING = SNAKE_RADIUS + SNAKE_PADDING; const HEAD_COLOR = 0xf43f5e; const TAIL_COLOR = 0xf4c3cc; const APPLE_COLOR = 0x58f474; +const APPLE_COLOR_2 = 0x0a6f4b4; const DIRECTIONS = { up: new Vec2d(0, -1), @@ -32,10 +33,25 @@ function resizeCanvas(ctx: CanvasRenderingContext2D) { let lastTime: number | undefined = undefined; -function update(ctx: CanvasRenderingContext2D, t: number) { - if (lastTime == null) lastTime = t; - const dt = (t - lastTime) / 1000; // s - lastTime = t; +function drawSnake(ctx: CanvasRenderingContext2D, bounds: Vec2d) { + for (let i = 0; i < snake.length - 1; ++i) { + // 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 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; 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 //const head = wrapCoords(snake_body[0], bounds); const head = snake[0].wrap(bounds) - // check if snake head is overlapping apple - if (head.distance(apple) < SNAKE_SIZE) { + if (head.distance(apple) < SNAKE_RADIUS * 2) { apple = Vec2d.random(bounds.x, bounds.y) const { x: lastX, y: lastY } = snake[snake.length - 1]; 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); - fillCircle(ctx, apple, SNAKE_SIZE / 2, APPLE_COLOR); - - // draw snake - for (let i = 0; i < snake.length - 1; ++i) { - // 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) + const t = (Math.sin(0.01 * time) + 1.0) * 0.5; + const apple_size = lerp(SNAKE_RADIUS, SNAKE_RADIUS * 0.9, t) + fillCircle(ctx, apple, apple_size, APPLE_COLOR); // draw apple + drawSnake(ctx, bounds); window.requestAnimationFrame((t) => update(ctx, t)); } - function init() { const canvas = document.getElementById("game-canvas") as HTMLCanvasElement | null; 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 "j": interpolation_factor++; break; case "k": interpolation_factor = Math.max(1, interpolation_factor-1); break; - case "p": + case "Space": case "p": paused = !paused; setStatusLine(paused ? "paused" : "", null); break;