/** * minigames/_api.js * Gemeinsame API und Hilfsfunktionen fΓΌr alle Mini-Games * * Jedes Mini-Game muss folgendes exportieren (als globale Variable window.MG_): * * window.MG_snake = { * id: 'snake', * emoji: '🐍', * name: 'Snake', * desc: 'Steuere die Schlange...', * controls: 'Pfeiltasten oder WASD', * multi: 1, // 1 = einmalig, 3 = max 3x, 99 = unbegrenzt * launch: function(wrap, W, H, cfg) { ... return { stop() {} }; }, * preview: function(wrap, W, H, cfg) { ... return { stop() {} }; }, * }; * * launch() β†’ vollstΓ€ndiges Spiel, wird in game.html verwendet * preview() β†’ kompakte Demo, wird im Editor-Test-Popup verwendet * Beide geben ein Objekt { stop() } zurΓΌck zum AufrΓ€umen. * * cfg = { quizData, theme, rules, devName, gameName } */ window.MGAPI = (function() { // ── Canvas-Setup-Helfer ────────────────────────────────────────────────── function makeCanvas(wrap, W, H) { const canvas = document.createElement('canvas'); canvas.width = W; canvas.height = H; canvas.style.display = 'block'; canvas.style.margin = '0 auto'; canvas.style.borderRadius = '12px'; canvas.style.background = '#0f0e17'; wrap.appendChild(canvas); return { canvas, ctx: canvas.getContext('2d') }; } // ── Ergebnis-Anzeige (wird von game.html ΓΌberschrieben) ────────────────── // game.html setzt window.MGAPI.onResult = finishMinigame // editor.html setzt window.MGAPI.onResult = previewResult function onResult(won) { if (typeof window._mgOnResult === 'function') { window._mgOnResult(won); } } // ── Schrift-Helfer ─────────────────────────────────────────────────────── function text(ctx, str, x, y, opts = {}) { ctx.save(); ctx.font = `${opts.weight || 'bold'} ${opts.size || 16}px ${opts.family || 'Nunito,sans-serif'}`; ctx.fillStyle = opts.color || '#fff'; ctx.textAlign = opts.align || 'center'; ctx.textBaseline = opts.baseline || 'middle'; if (opts.shadow) { ctx.shadowColor = opts.shadow; ctx.shadowBlur = opts.shadowBlur || 10; } ctx.fillText(str, x, y); ctx.restore(); } // ── Runde Rechtecke ────────────────────────────────────────────────────── function roundRect(ctx, x, y, w, h, r, fill, stroke) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); if (fill) { ctx.fillStyle = fill; ctx.fill(); } if (stroke) { ctx.strokeStyle = stroke; ctx.stroke(); } } // ── Game-Over / Win Screen ─────────────────────────────────────────────── function resultScreen(ctx, W, H, won, msg) { ctx.fillStyle = won ? 'rgba(16,185,129,0.85)' : 'rgba(239,68,68,0.85)'; roundRect(ctx, W/2-120, H/2-50, 240, 100, 16, ctx.fillStyle, null); text(ctx, won ? 'πŸŽ‰ Gewonnen!' : 'πŸ’€ Verloren!', W/2, H/2-16, { size: 22, weight: 'bold', family: "'Fredoka One',cursive", color: '#fff' }); if (msg) text(ctx, msg, W/2, H/2+16, { size: 13, color: 'rgba(255,255,255,0.85)' }); } // ── Γ–ffentliche API ────────────────────────────────────────────────────── return { makeCanvas, onResult, text, roundRect, resultScreen }; })();