Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139import { drawScene, W as LW } from "../scene.ts";import { nextAnim } from "./animation.ts";import { act, eggView } from "./care.ts";import { OFFER_ICON } from "./config.ts";import { gameControl, stepGame, stopGameControls } from "./game.ts";import { tick, weatherNow } from "./simulation.ts";import { brain, store, view } from "./store.ts";import { clamp } from "./utils.ts";import { isRemote } from "./hosting.ts";import { ui } from "./store.ts";
function frame(now: number) { if (!store.tabOwned) return; // another tab has the pet now if (!store.badEgg) tick(); nextAnim(now); view.effects = view.effects.filter((f) => now < f.until); // The pet only walks where the brain sent it ([walk:left] / [walk:right]). if ( store.state.pet && !store.state.asleep && !view.anim && !view.game && Math.abs(view.target - view.x) >= 1 ) { view.walking = true; view.facing = Math.sign(view.target - view.x); view.x += view.facing * 0.3; } else if (!view.game) view.walking = false; if (view.game) stepGame(now); const d = new Date(); const dozing = !!store.state.pet && brain.state !== "ready"; drawScene( ctx, { lightsOff: store.state.lightsOff, weather: isRemote() && ui.remoteWeather ? ui.remoteWeather : weatherNow(), hour: d.getHours(), shelf: store.state.shelf, poop: store.state.poop, egg: store.state.pet ? null : eggView(), pet: store.state.pet && { stage: store.state.pet!.stage, color: store.state.pet!.color, x: view.x, facing: view.facing, walking: view.walking, anim: view.anim, asleep: store.state.asleep || dozing, sick: store.state.sick, }, effects: view.effects, game: view.game, want: view.game || dozing || now > view.wantUntil ? null : view.want, dream: store.state.asleep && !dozing && now < view.dreamUntil ? view.dream : null, offer: view.game || !store.state.offer ? null : store.state.offer.item || OFFER_ICON[store.state.offer.kind], thinking: view.thinking && !view.game, clock: view.game ? null : `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`, }, now, );}
let ctx: CanvasRenderingContext2D;export function attachCanvas(canvas: HTMLCanvasElement) { const context = canvas.getContext("2d"); if (!context) throw new Error("Canvas 2D is unavailable"); ctx = context; let request = 0; const loop = (now: number) => { if (!store.tabOwned) return; frame(now); request = requestAnimationFrame(loop); }; request = requestAnimationFrame(loop); const controller = new AbortController(), options = { signal: controller.signal }; canvas.addEventListener( "pointerdown", (e) => { const r = canvas.getBoundingClientRect(), x = ((e.clientX - r.left) / r.width) * LW; if (view.game) { view.target = clamp(x, 10, LW - 10); return; } if (store.state.pet && Math.abs(x - view.x) < 14) act("pat"); }, options, ); canvas.addEventListener( "pointermove", (e) => { if (!view.game || !(e.buttons || e.pointerType === "mouse")) return; const r = canvas.getBoundingClientRect(); view.target = clamp(((e.clientX - r.left) / r.width) * LW, 10, LW - 10); }, options, ); const key = (down: boolean) => (e: KeyboardEvent) => { if (!view.game) return; const id = `key:${e.code || e.key.toLowerCase()}`; if (!down) { if (view.game.held.has(id)) { gameControl(id, 0); e.preventDefault(); } return; } const target = e.target instanceof Element ? e.target : null; if (target?.closest("input, textarea, select, [contenteditable], [role=tab]")) return; const k = e.key.toLowerCase(), pad = (k === " " || k === "enter") && target?.closest("#pad-left, #pad-right"); const dir = pad ? pad.id === "pad-left" ? -1 : 1 : k === "arrowleft" || k === "a" ? -1 : k === "arrowright" || k === "d" ? 1 : 0; if (!dir) return; e.preventDefault(); if (!e.repeat) gameControl(id, dir); }; document.addEventListener("keydown", key(true), options); document.addEventListener("keyup", key(false), options); return () => { controller.abort(); cancelAnimationFrame(request); stopGameControls(); };}