import { FLOOR, W as LW } from "../scene.ts"; import { animate } from "./animation.ts"; import { petCommand } from "./commands.ts"; import { petEngine } from "./engine.ts"; import { emit, store, ui, view } from "./store.ts"; import { clamp } from "./utils.ts"; import { isRemote } from "./hosting.ts"; // ---------- catch minigame (after the pet said yes) ---------- // The engine records the accepted game; this module plays it and reports the // score once, by the game's id. export function gameId() { return isRemote() ? ui.remoteGameId : petEngine().game?.id || null; } let starting = 0; /** Starts the game the pet accepted, after its answer has had a moment on screen. */ export function startAcceptedGame() { if (view.game || starting || !gameId()) return; starting = setTimeout(() => { starting = 0; if (gameId()) startGame(); }, 600); } export function startGame() { if (!store.tabOwned || !store.state.pet || store.state.asleep || view.game) return; view.game = { stars: [], score: 0, timeLeft: 20, last: performance.now(), spawn: 0, dir: 0, held: new Map(), }; view.target = view.x; emit(); } export function gameControl(id: string, dir: number) { const g = view.game; if (!g) return; g.held.delete(id); if (dir) g.held.set(id, dir); // The newest held input wins; releasing another finger/key cannot stop it. g.dir = [...g.held.values()].at(-1) || 0; view.target = view.x; } export function stopGameControls() { if (!view.game) return; view.game.held.clear(); view.game.dir = 0; view.target = view.x; } export function stepGame(now: number) { const g = view.game!; const dt = Math.min(0.05, (now - g.last) / 1000); g.last = now; g.timeLeft -= dt; g.spawn -= dt; if (g.spawn <= 0) { g.stars.push({ x: 8 + Math.random() * (LW - 16), y: -4, v: 18 + Math.random() * 14 + (20 - g.timeLeft), bad: Math.random() < 0.18, }); g.spawn = 0.55 + Math.random() * 0.5; } if (g.dir) view.target = clamp(view.x + g.dir * 70 * dt, 10, LW - 10); const dx = view.target - view.x; if (dx) { view.facing = Math.sign(dx); view.x += Math.sign(dx) * Math.min(Math.abs(dx), 70 * dt); view.walking = true; } else view.walking = false; for (const s of g.stars) { s.y += s.v * dt; if (!s.done && s.y > FLOOR - 16 && s.y < FLOOR - 4 && Math.abs(s.x - view.x) < 10) { s.done = true; if (s.bad) { g.score = Math.max(0, g.score - 1); animate("wow", 400, true); } else { g.score++; animate("happy", 400, true); } } } g.stars = g.stars.filter((s) => !s.done && s.y < FLOOR + 4); if (g.timeLeft <= 0) endGame(); } export function endGame() { const score = view.game!.score, id = gameId(); view.game = null; view.target = view.x; emit(); if (id) void petCommand({ type: "gameComplete", gameId: id, score }); }