172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
/**
|
||
* 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 };
|
||
|
||
})();
|