edu-boardgame-generator/minigames/_api.js
Spiel-Generator Workshop 016be6ea90 feat: initial project structure with modular mini-game system
- editor.html: 5-step game editor with live Python code split-screen
- game.html: playable board game engine
- codegen.js: Python live-code generator (extracted from editor)
- logo.png: PH Weingarten logo (extracted from Base64)
- minigames/_api.js: shared Mini-Game API (makeCanvas, onResult, helpers)
- 12 Mini-Games as individual modules (launch + preview API):
  snake, flappy, memory, quiz, reaction,
  basketball, catch, maze, simon, typing, puzzle, spotdiff
- README.md with architecture docs and deployment guide

Refactoring highlights:
  - editor.html: 251 KB → 102 KB (-59%)
  - Mini-games fully decoupled, each ~100-200 lines
  - All 12 games now have working launch() + preview()
  - Maze uses recursive backtracker algorithm
  - spotdiff uses canvas-drawn scene with 5 differences
2026-03-14 22:12:25 +00:00

93 lines
4 KiB
JavaScript

/**
* minigames/_api.js
* Gemeinsame API und Hilfsfunktionen für alle Mini-Games
*
* Jedes Mini-Game muss folgendes exportieren (als globale Variable window.MG_<NAME>):
*
* 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 };
})();