edu-boardgame-generator/minigames/simon.js

172 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* minigames/simon.js
* 🔴 Simon Says — Merke und wiederhole die Farb-Sequenz!
*/
window.MG_simon = (function() {
const ID = 'simon';
const EMOJI = '🔴';
const NAME = 'Simon Says';
const DESC = 'Merke die Farb-Reihenfolge und tippe sie nach!';
const CONTROLS = 'Mausklick / Tippen';
const MULTI = 3;
const COLORS = [
{ id: 0, fill: '#22c55e', glow: 'rgba(34,197,94,0.6)', label: '🟢' },
{ id: 1, fill: '#ef4444', glow: 'rgba(239,68,68,0.6)', label: '🔴' },
{ id: 2, fill: '#3b82f6', glow: 'rgba(59,130,246,0.6)', label: '🔵' },
{ id: 3, fill: '#f59e0b', glow: 'rgba(245,158,11,0.6)', label: '🟡' },
];
function run(wrap, W, H, cfg, onDone) {
const { canvas, ctx } = MGAPI.makeCanvas(wrap, W, H);
const WIN_ROUNDS = cfg.winRounds || 4;
// Layout: 2×2 Grid
const PAD = 16;
const GW = (W - PAD * 3) / 2;
const GH = (H - PAD * 3 - 32) / 2;
const POS = [
{ x: PAD, y: 32 + PAD },
{ x: PAD*2+GW, y: 32 + PAD },
{ x: PAD, y: 32 + PAD*2 + GH },
{ x: PAD*2+GW, y: 32 + PAD*2 + GH },
];
let sequence = [];
let userSeq = [];
let pressedIdx = -1;
let showIdx = -1; // welcher Schritt der Anzeige läuft
let phase = 'show'; // 'show' | 'input' | 'result'
let litIdx = -1;
let stopped = false;
let raf, stepTimer, endTimer, result = null;
function addStep() { sequence.push(Math.floor(Math.random() * 4)); }
function startShow() {
phase = 'show';
litIdx = -1;
showIdx = 0;
userSeq = [];
stepTimer = setInterval(() => {
litIdx = (litIdx === -1) ? sequence[showIdx] : -1;
if (litIdx === -1) {
showIdx++;
if (showIdx >= sequence.length) {
clearInterval(stepTimer);
litIdx = -1;
phase = 'input';
}
}
}, 500);
}
function checkInput(colorId) {
if (phase !== 'input') return;
pressedIdx = colorId;
setTimeout(() => { pressedIdx = -1; }, 200);
userSeq.push(colorId);
const idx = userSeq.length - 1;
if (userSeq[idx] !== sequence[idx]) {
// Falsch
clearTimeout(endTimer);
result = 'lose';
endTimer = setTimeout(() => onDone(false), 1000);
phase = 'result';
return;
}
if (userSeq.length === sequence.length) {
if (sequence.length >= WIN_ROUNDS) {
clearTimeout(endTimer);
result = 'win';
endTimer = setTimeout(() => onDone(true), 900);
phase = 'result';
} else {
// Nächste Runde
phase = 'wait';
setTimeout(() => {
addStep();
startShow();
}, 800);
}
}
}
// Klick auf Farb-Button
canvas.addEventListener('click', e => {
if (phase !== 'input') return;
const rect = canvas.getBoundingClientRect();
const mx = (e.clientX - rect.left) * (canvas.width / rect.width / (window.devicePixelRatio || 1));
const my = (e.clientY - rect.top) * (canvas.height / rect.height / (window.devicePixelRatio || 1));
POS.forEach((p, i) => {
if (mx >= p.x && mx < p.x + GW && my >= p.y && my < p.y + GH)
checkInput(i);
});
});
// Starten
addStep();
startShow();
function loop() {
if (stopped) return;
raf = requestAnimationFrame(loop);
ctx.fillStyle = '#050508';
ctx.fillRect(0, 0, W, H);
// Felder zeichnen
POS.forEach((p, i) => {
const col = COLORS[i];
const isLit = litIdx === i;
const inUser = pressedIdx === i;
ctx.shadowColor = isLit || inUser ? col.glow : 'transparent';
ctx.shadowBlur = isLit || inUser ? 30 : 0;
MGAPI.roundRect(ctx, p.x, p.y, GW, GH, 12,
isLit || inUser ? col.fill : `${col.fill}44`,
isLit || inUser ? col.fill : `${col.fill}88`);
ctx.shadowBlur = 0;
// Emoji mittig
ctx.font = `${Math.min(GW, GH) * 0.4}px serif`;
ctx.textAlign = 'center';
ctx.fillText(col.label, p.x + GW / 2, p.y + GH / 2 + GH * 0.14);
});
// HUD
const phaseText = phase === 'show' ? '👀 Schau zu...'
: phase === 'input' ? '👆 Deine Runde!'
: phase === 'wait' ? '✅ Richtig!'
: '';
MGAPI.text(ctx, phaseText, W / 2, 18,
{ size: 13, color: phase === 'input' ? '#f59e0b' : '#a7a3c2' });
MGAPI.text(ctx, `Runde ${sequence.length} / ${WIN_ROUNDS}`, W / 2, H - 10,
{ size: 11, color: 'rgba(255,255,255,0.35)' });
if (result)
MGAPI.resultScreen(ctx, W, H, result === 'win',
result === 'win' ? `${sequence.length} Runden gemeistert!` : 'Falsche Farbe!');
}
raf = requestAnimationFrame(loop);
return {
stop() {
stopped = true;
clearInterval(stepTimer);
clearTimeout(endTimer);
cancelAnimationFrame(raf);
},
};
}
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 };
})();