// Entry point. Owns the frame loop, the button wiring, and the fallback for // machines without WebGL. import { Game } from './game.js'; import { Hud } from './hud.js'; import { Input } from './input.js'; import { AudioKit } from './audio.js'; const MAX_FRAME_DT = 1 / 20; function hasWebGL() { try { const canvas = document.createElement('canvas'); return !!(canvas.getContext('webgl2') || canvas.getContext('webgl')); } catch { return false; } } if (!hasWebGL()) { document.getElementById('unsupported').hidden = false; document.getElementById('menu').hidden = true; } else { boot(); } function boot() { const hud = new Hud(); const input = new Input(); const audio = new AudioKit(); const game = new Game(document.getElementById('scene'), { hud, input, audio, onStateChange: () => {}, }); input.onFirstGesture = () => audio.unlock(); const buttons = { start: document.getElementById('start-btn'), retry: document.getElementById('retry-btn'), resume: document.getElementById('resume-btn'), }; buttons.start.addEventListener('click', () => { audio.ui(); game.start(); }); buttons.retry.addEventListener('click', () => { audio.ui(); game.restart(); }); buttons.resume.addEventListener('click', () => game.resume()); // Enter starts and restarts, which is what a keyboard player expects from an // arcade game. Escape only goes back to the menu from the game over screen. addEventListener('keydown', (e) => { if (e.code === 'Enter' || e.code === 'NumpadEnter') { if (game.state === 'menu') game.start(); else if (game.state === 'gameover') game.restart(); else if (game.state === 'paused') game.resume(); } if (e.code === 'Escape' && game.state === 'gameover') game.toMenu(); }); hud.show('menu'); hud.showHud(false); let last = performance.now(); function frame(now) { requestAnimationFrame(frame); const dt = Math.min(MAX_FRAME_DT, (now - last) / 1000); last = now; game.tick(dt); game.render(); } requestAnimationFrame(frame); window.__game = game; }