136 lines
3.7 KiB
JavaScript
136 lines
3.7 KiB
JavaScript
// Wires the modules together and runs the frame loop.
|
|
|
|
import { createSettings } from './settings.js';
|
|
import { setLang, detectLang } from './i18n.js';
|
|
import { Game } from './game.js';
|
|
import { Renderer } from './renderer.js';
|
|
import { createInput, blockBrowserGestures } from './input.js';
|
|
import { sfx, buzz, setMuted, setHaptics, installAudioUnlock } from './audio.js';
|
|
import { createWakeLock } from './device.js';
|
|
import { getTheme } from './themes.js';
|
|
import { createUI } from './ui.js';
|
|
|
|
const params = new URLSearchParams(location.search);
|
|
const DEBUG = params.has('debug') && params.get('debug') !== '0';
|
|
const WAKE_HOLD_MS = 2 * 60 * 1000; // keep the screen on this long after the last touch
|
|
|
|
const canvas = document.getElementById('stage');
|
|
const settings = createSettings();
|
|
setLang(settings.get().lang || detectLang());
|
|
|
|
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
|
|
const renderer = new Renderer(canvas);
|
|
const wakeLock = createWakeLock();
|
|
|
|
const game = new Game({
|
|
emit: onGameEvent,
|
|
getSettings: () => settings.get(),
|
|
isReduced: () => reducedMotion.matches,
|
|
});
|
|
|
|
const noTouch = navigator.maxTouchPoints === 0 && matchMedia('(pointer: fine)').matches;
|
|
const ui = createUI({ settings, debug: DEBUG, showDesktopHint: noTouch && !DEBUG });
|
|
|
|
function applySettings(s) {
|
|
const theme = getTheme(s.theme);
|
|
renderer.setTheme(theme);
|
|
document.documentElement.dataset.theme = s.theme;
|
|
document.querySelector('meta[name="theme-color"]').content = theme.bg;
|
|
setMuted(s.muted);
|
|
setHaptics(s.haptics);
|
|
}
|
|
applySettings(settings.get());
|
|
settings.subscribe(applySettings);
|
|
|
|
// ---- game events -> sound, haptics, visuals, text ----
|
|
|
|
function onGameEvent(type, data = {}) {
|
|
const now = performance.now();
|
|
renderer.onEvent(type, data, now, game);
|
|
ui.onEvent(type, data);
|
|
switch (type) {
|
|
case 'join':
|
|
sfx.join(data.ring.colorIdx);
|
|
buzz(8);
|
|
break;
|
|
case 'beat':
|
|
sfx.beat(data.index);
|
|
buzz(25);
|
|
break;
|
|
case 'tick':
|
|
sfx.tick(data.progress, data.final);
|
|
buzz(data.final ? 35 : 8);
|
|
break;
|
|
case 'reveal':
|
|
sfx.reveal();
|
|
buzz([90, 60, 450]);
|
|
break;
|
|
case 'eliminate':
|
|
sfx.crack();
|
|
if (data.chickened) sfx.chicken();
|
|
buzz([40, 30, 120]);
|
|
break;
|
|
case 'orderStep':
|
|
sfx.pop(data.n);
|
|
buzz(data.n === 1 ? 120 : 20);
|
|
break;
|
|
case 'chicken':
|
|
sfx.chicken();
|
|
buzz([60, 50, 60, 50, 60]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ---- input ----
|
|
|
|
blockBrowserGestures(canvas);
|
|
installAudioUnlock();
|
|
|
|
const input = createInput(canvas, {
|
|
debug: DEBUG,
|
|
onDown: (id, x, y, now) => game.fingerDown(id, x, y, now),
|
|
onMove: (id, x, y) => game.fingerMove(id, x, y),
|
|
onUp: (id, now) => game.fingerUp(id, now),
|
|
});
|
|
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.visibilityState === 'hidden') {
|
|
input.clearReal();
|
|
game.cancelAll(performance.now());
|
|
}
|
|
});
|
|
|
|
if (DEBUG) {
|
|
document.body.classList.add('debug');
|
|
document.getElementById('debug-hud').hidden = false;
|
|
const n = Number(params.get('fingers'));
|
|
if (n > 0) input.addFakeCircle(Math.min(n, 12));
|
|
}
|
|
|
|
// ---- frame loop ----
|
|
|
|
let lastTouchAt = -Infinity;
|
|
|
|
function frame() {
|
|
const now = performance.now();
|
|
game.update(now);
|
|
renderer.draw(game, now);
|
|
ui.update(game, now);
|
|
|
|
if (game.anyDown()) lastTouchAt = now;
|
|
wakeLock.set(now - lastTouchAt < WAKE_HOLD_MS);
|
|
|
|
requestAnimationFrame(frame);
|
|
}
|
|
requestAnimationFrame(frame);
|
|
|
|
// ---- PWA ----
|
|
|
|
if ('serviceWorker' in navigator && !DEBUG) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('sw.js').catch(() => {});
|
|
});
|
|
}
|
|
|
|
// Handy in the console while testing.
|
|
if (DEBUG) Object.assign(window, { game, settings, renderer });
|