Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136import { VIEW, type GameState } from "./engine";import { OBSTACLES, SPRITES, type ObstacleKind, type SpriteName } from "./sprites";
export interface Palette { canvas: string; ink: string; muted: string; dark: boolean;}
export interface Dims { w: number; h: number;}
export const PLAYER_X = 56;
const OBSTACLE_SPRITE: Record<ObstacleKind, SpriteName> = { standing: "sheepStanding", sleeping: "sheepSleeping", pair: "sheepStanding", stack: "sheepStack"};
const PEBBLE_PERIOD = 960;
const PEBBLES = (() => { let seed = 1337; const rand = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296; return Array.from({ length: 48 }, () => ({ d: rand() * PEBBLE_PERIOD, len: 2 + Math.floor(rand() * 7), dy: 4 + Math.floor(rand() * 7) }));})();
const paths = new Map<string, Path2D>();
const pathFor = (name: SpriteName) => { let path = paths.get(name); if (!path) { path = new Path2D(SPRITES[name].path); paths.set(name, path); } return path;};
const drawSprite = (ctx: CanvasRenderingContext2D, name: SpriteName, x: number, y: number) => { ctx.save(); ctx.translate(Math.round(x), Math.round(y)); ctx.fill(pathFor(name), "evenodd"); ctx.restore();};
const drawOnGround = (ctx: CanvasRenderingContext2D, name: SpriteName, x: number) => drawSprite(ctx, name, x, VIEW.ground - SPRITES[name].h);
const readVar = (styles: CSSStyleDeclaration, name: string, fallback: string) => styles.getPropertyValue(name).trim() || fallback;
const luminance = (color: string): number | null => { const rgb = color.match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/); if (rgb) return (0.2126 * +rgb[1] + 0.7152 * +rgb[2] + 0.0722 * +rgb[3]) / 255; if (!color.startsWith("#")) return null; let hex = color.slice(1); if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c); if (hex.length < 6) return null; const r = parseInt(hex.slice(0, 2), 16); const g = parseInt(hex.slice(2, 4), 16); const b = parseInt(hex.slice(4, 6), 16); if ([r, g, b].some(Number.isNaN)) return null; return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;};
export const readPalette = (el: HTMLElement): Palette => { const styles = getComputedStyle(el); const canvas = readVar(styles, "--color-background-canvas", "#fff"); return { canvas, ink: readVar(styles, "--color-foreground-default", "#000"), muted: readVar(styles, "--color-foreground-muted", "#666"), dark: (luminance(canvas) ?? 1) < 0.5 };};
const drawGround = (ctx: CanvasRenderingContext2D, offset: number, width: number) => { ctx.fillRect(0, VIEW.ground, width, 1); for (const pebble of PEBBLES) { const x = (((pebble.d - offset) % PEBBLE_PERIOD) + PEBBLE_PERIOD) % PEBBLE_PERIOD; if (x > width) continue; ctx.fillRect(Math.round(x), VIEW.ground + pebble.dy, pebble.len, 1); }};
const drawObstacle = (ctx: CanvasRenderingContext2D, kind: ObstacleKind, x: number) => { const sprite = OBSTACLE_SPRITE[kind]; drawOnGround(ctx, sprite, x); if (kind === "pair") drawOnGround(ctx, sprite, x + OBSTACLES.pair.w - SPRITES[sprite].w);};
const playerSprite = (state: GameState): SpriteName => { if (state.phase === "dead") return "runnerDead"; if (!state.player.grounded) return "runnerJump"; return state.player.frame === 1 ? "runnerB" : "runnerA";};
export const renderFrame = ( ctx: CanvasRenderingContext2D, state: GameState, palette: Palette, dims: Dims) => { const inverted = state.night && !palette.dark; const sky = inverted ? palette.ink : palette.canvas; const ink = inverted ? palette.canvas : palette.ink;
ctx.clearRect(0, 0, dims.w, dims.h); ctx.fillStyle = sky; ctx.fillRect(0, 0, dims.w, dims.h); ctx.fillStyle = ink;
if (state.night) { for (const star of state.stars) drawSprite(ctx, "star", star.x, star.y); drawSprite(ctx, "moon", dims.w - 88, 24); } else { for (const cloud of state.clouds) drawSprite(ctx, "cloud", cloud.x, cloud.y); }
drawGround(ctx, state.groundOffset, dims.w);
for (const obstacle of state.obstacles) drawObstacle(ctx, obstacle.kind, obstacle.x);
const sprite = playerSprite(state); drawSprite(ctx, sprite, PLAYER_X, state.player.y - SPRITES[sprite].h);};