144 lines
4.6 KiB
JavaScript
144 lines
4.6 KiB
JavaScript
/**
|
|
* minigames/reaction.js
|
|
* ⚡ Reaktionstest — Drück den Knopf so schnell wie möglich!
|
|
*/
|
|
window.MG_reaction = (function() {
|
|
|
|
const ID = 'reaction';
|
|
const EMOJI = '⚡';
|
|
const NAME = 'Reaktionstest';
|
|
const DESC = 'Wenn der Kreis GRÜN wird — so schnell wie möglich klicken!';
|
|
const CONTROLS = 'Klick / Leertaste';
|
|
const MULTI = 3;
|
|
|
|
function run(wrap, W, H, cfg, onDone) {
|
|
const { canvas, ctx } = MGAPI.makeCanvas(wrap, W, H);
|
|
const theme = cfg.theme || { primary: '#ef4444' };
|
|
const ROUNDS = 3;
|
|
const WIN_MS = 600;
|
|
|
|
let phase = 'wait';
|
|
let waitEnd = 0;
|
|
let reStart = 0;
|
|
let times = [];
|
|
let best = Infinity;
|
|
let stopped = false;
|
|
let raf, endTimer;
|
|
|
|
function nextWait() {
|
|
phase = 'wait';
|
|
waitEnd = performance.now() + (2 + Math.random() * 3) * 1000;
|
|
}
|
|
nextWait();
|
|
|
|
const react = () => {
|
|
if (phase === 'ready') {
|
|
const t = performance.now() - reStart;
|
|
times.push(t);
|
|
best = Math.min(best, t);
|
|
if (times.length >= ROUNDS) {
|
|
clearTimeout(endTimer);
|
|
endTimer = setTimeout(() => onDone(best < WIN_MS), 800);
|
|
phase = 'done';
|
|
} else {
|
|
nextWait();
|
|
}
|
|
} else if (phase === 'wait') {
|
|
phase = 'toosoon';
|
|
setTimeout(nextWait, 1000);
|
|
}
|
|
};
|
|
|
|
const onKey = e => { if (e.code === 'Space') { e.preventDefault(); react(); } };
|
|
document.addEventListener('keydown', onKey);
|
|
canvas.addEventListener('click', react);
|
|
|
|
function loop(ts) {
|
|
if (stopped) return;
|
|
raf = requestAnimationFrame(loop);
|
|
|
|
if (phase === 'wait' && ts > waitEnd) {
|
|
phase = 'ready';
|
|
reStart = performance.now();
|
|
}
|
|
|
|
// Hintergrund
|
|
const bg = phase === 'ready' ? '#14532d'
|
|
: phase === 'toosoon' ? '#7f1d1d'
|
|
: phase === 'done' ? '#0f0e17'
|
|
: '#050508';
|
|
ctx.fillStyle = bg;
|
|
ctx.fillRect(0, 0, W, H);
|
|
|
|
const cx = W / 2, cy = H / 2;
|
|
|
|
if (phase === 'wait') {
|
|
// Pulsierender Kreis (rot = warten)
|
|
const pulse = 0.85 + 0.15 * Math.sin(ts * 0.003);
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 60 * pulse, 0, Math.PI * 2);
|
|
ctx.fillStyle = 'rgba(239,68,68,0.2)';
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#ef444488';
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
MGAPI.text(ctx, '⏳', cx, cy - 6, { size: 36 });
|
|
MGAPI.text(ctx, 'Warte...', cx, cy + 44, { size: 14, color: '#a7a3c2' });
|
|
MGAPI.text(ctx, `${times.length} / ${ROUNDS} Runden`, cx, H - 20, { size: 12, color: '#64748b' });
|
|
}
|
|
|
|
if (phase === 'ready') {
|
|
// Leuchtender grüner Kreis
|
|
ctx.shadowColor = '#22c55e';
|
|
ctx.shadowBlur = 40;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 65, 0, Math.PI * 2);
|
|
ctx.fillStyle = 'rgba(22,163,74,0.4)';
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#22c55e';
|
|
ctx.lineWidth = 4;
|
|
ctx.stroke();
|
|
ctx.shadowBlur = 0;
|
|
MGAPI.text(ctx, '⚡', cx, cy - 8, { size: 44 });
|
|
MGAPI.text(ctx, 'JETZT!', cx, cy + 48,
|
|
{ size: 22, family: "'Fredoka One',cursive", color: '#fff', shadow: '#22c55e', shadowBlur: 15 });
|
|
MGAPI.text(ctx, 'Klick oder Leertaste!', cx, cy + 78, { size: 13, color: 'rgba(255,255,255,0.6)' });
|
|
}
|
|
|
|
if (phase === 'toosoon') {
|
|
MGAPI.text(ctx, '😅', cx, cy - 10, { size: 48 });
|
|
MGAPI.text(ctx, 'Zu früh!', cx, cy + 46,
|
|
{ size: 22, family: "'Fredoka One',cursive", color: '#fca5a5' });
|
|
}
|
|
|
|
if (phase === 'done') {
|
|
MGAPI.resultScreen(ctx, W, H, best < WIN_MS,
|
|
best < WIN_MS ? `${Math.round(best)} ms — super schnell!` : `${Math.round(best)} ms — noch etwas langsam`);
|
|
}
|
|
|
|
// Statistik
|
|
if (times.length > 0 && phase !== 'done') {
|
|
const last = times[times.length - 1];
|
|
MGAPI.text(ctx, `Letzte: ${Math.round(last)} ms | Beste: ${Math.round(best)} ms`,
|
|
cx, H - 14, { size: 11, color: 'rgba(255,255,255,0.4)' });
|
|
}
|
|
}
|
|
|
|
raf = requestAnimationFrame(loop);
|
|
return {
|
|
stop() {
|
|
stopped = true;
|
|
cancelAnimationFrame(raf);
|
|
clearTimeout(endTimer);
|
|
document.removeEventListener('keydown', onKey);
|
|
canvas.removeEventListener('click', react);
|
|
},
|
|
};
|
|
}
|
|
|
|
function launch(wrap, W, H, cfg) { return run(wrap, W, H, cfg, won => MGAPI.onResult(won)); }
|
|
function preview(wrap, W, H, cfg) { return run(wrap, W, H, cfg, won => MGAPI.onResult(won)); }
|
|
|
|
return { id: ID, emoji: EMOJI, name: NAME, desc: DESC, controls: CONTROLS, multi: MULTI, launch, preview };
|
|
|
|
})();
|