/** * minigames/spotdiff.js * πŸ” Fehler finden β€” Finde alle Unterschiede zwischen den zwei Bildern! */ window.MG_spotdiff = (function() { const ID = 'spotdiff'; const EMOJI = 'πŸ”'; const NAME = 'Fehler finden'; const DESC = 'Finde alle Unterschiede zwischen den zwei Bildern!'; const CONTROLS = 'Mausklick'; const MULTI = 1; // ── Bild-Sets (canvas-gezeichnete Szenen) ──────────────────────────────── // Jede Szene besteht aus drawA(ctx,W,H) und drawB(ctx,W,H), // plus einer Liste von Unterschied-Hotspots { x, y, r } (relativ, 0–1) const SCENES = [ { name: 'Bauernhof', drawA(ctx, W, H) { // Himmel ctx.fillStyle = '#7dd3fc'; ctx.fillRect(0, 0, W, H * 0.55); // Gras ctx.fillStyle = '#4ade80'; ctx.fillRect(0, H * 0.55, W, H * 0.45); // Sonne ctx.fillStyle = '#fbbf24'; ctx.beginPath(); ctx.arc(W*0.15, H*0.18, H*0.1, 0, Math.PI*2); ctx.fill(); // Haus ctx.fillStyle = '#f87171'; ctx.fillRect(W*0.3, H*0.3, W*0.25, H*0.28); ctx.fillStyle = '#7f1d1d'; ctx.beginPath(); ctx.moveTo(W*0.27,H*0.3); ctx.lineTo(W*0.425,H*0.12); ctx.lineTo(W*0.58,H*0.3); ctx.fill(); // Fenster (2) ctx.fillStyle = '#bae6fd'; ctx.fillRect(W*0.34, H*0.38, W*0.06, H*0.07); ctx.fillStyle = '#bae6fd'; ctx.fillRect(W*0.45, H*0.38, W*0.06, H*0.07); // TΓΌr ctx.fillStyle = '#78350f'; ctx.fillRect(W*0.39, H*0.46, W*0.05, H*0.12); // Baum (3 Kreise) ctx.fillStyle = '#16a34a'; ctx.beginPath(); ctx.arc(W*0.75, H*0.38, W*0.06, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#15803d'; ctx.beginPath(); ctx.arc(W*0.72, H*0.46, W*0.05, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#14532d'; ctx.beginPath(); ctx.arc(W*0.79, H*0.44, W*0.05, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#78350f'; ctx.fillRect(W*0.74, H*0.5, W*0.02, H*0.08); // Wolke ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(W*0.55, H*0.14, W*0.05, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(W*0.62, H*0.11, W*0.06, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(W*0.69, H*0.14, W*0.05, 0, Math.PI*2); ctx.fill(); }, drawB(ctx, W, H) { // Himmel (UNTERSCHIED 1: dunkleres Blau) ctx.fillStyle = '#38bdf8'; ctx.fillRect(0, 0, W, H * 0.55); // Gras ctx.fillStyle = '#4ade80'; ctx.fillRect(0, H * 0.55, W, H * 0.45); // Sonne (UNTERSCHIED 2: weiter oben) ctx.fillStyle = '#fbbf24'; ctx.beginPath(); ctx.arc(W*0.15, H*0.10, H*0.1, 0, Math.PI*2); ctx.fill(); // Haus ctx.fillStyle = '#f87171'; ctx.fillRect(W*0.3, H*0.3, W*0.25, H*0.28); ctx.fillStyle = '#7f1d1d'; ctx.beginPath(); ctx.moveTo(W*0.27,H*0.3); ctx.lineTo(W*0.425,H*0.12); ctx.lineTo(W*0.58,H*0.3); ctx.fill(); // Fenster β€” nur EINES (UNTERSCHIED 3) ctx.fillStyle = '#bae6fd'; ctx.fillRect(W*0.34, H*0.38, W*0.06, H*0.07); ctx.fillStyle = '#f87171'; ctx.fillRect(W*0.45, H*0.38, W*0.06, H*0.07); // rotes Fenster // TΓΌr ctx.fillStyle = '#78350f'; ctx.fillRect(W*0.39, H*0.46, W*0.05, H*0.12); // Baum (kleiner β€” UNTERSCHIED 4) ctx.fillStyle = '#16a34a'; ctx.beginPath(); ctx.arc(W*0.75, H*0.42, W*0.04, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#15803d'; ctx.beginPath(); ctx.arc(W*0.72, H*0.48, W*0.035, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#14532d'; ctx.beginPath(); ctx.arc(W*0.79, H*0.46, W*0.035, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#78350f'; ctx.fillRect(W*0.74, H*0.5, W*0.02, H*0.08); // Wolke fehlt (UNTERSCHIED 5) }, // Hotspots in Bild B (relativ 0-1) spots: [ { x: 0.42, y: 0.12, r: 0.07, label: 'Himmelfarbe' }, { x: 0.15, y: 0.10, r: 0.08, label: 'Sonne' }, { x: 0.48, y: 0.41, r: 0.06, label: 'Rotes Fenster' }, { x: 0.75, y: 0.44, r: 0.07, label: 'Kleinerer Baum' }, { x: 0.62, y: 0.13, r: 0.08, label: 'Fehlende Wolke' }, ], }, ]; function run(wrap, W, H, cfg, onDone) { const scene = SCENES[0]; const theme = cfg.theme || { primary: '#84cc16' }; // Zwei Canvas nebeneinander const halfW = Math.floor((W - 8) / 2); const imgH = H - 64; const container = document.createElement('div'); container.style.cssText = 'position:relative;width:100%;'; wrap.appendChild(container); const canvasA = document.createElement('canvas'); canvasA.width = halfW; canvasA.height = imgH; canvasA.style.cssText = `display:inline-block;border-radius:8px;cursor:default;`; const canvasB = document.createElement('canvas'); canvasB.width = halfW; canvasB.height = imgH; canvasB.style.cssText = `display:inline-block;border-radius:8px;cursor:crosshair;margin-left:8px;`; container.appendChild(canvasA); container.appendChild(canvasB); // HUD-Canvas const hud = document.createElement('canvas'); hud.width = W; hud.height = 48; hud.style.display = 'block'; container.appendChild(hud); const ctxA = canvasA.getContext('2d'); const ctxB = canvasB.getContext('2d'); const ctxH = hud.getContext('2d'); scene.drawA(ctxA, halfW, imgH); scene.drawB(ctxB, halfW, imgH); let found = new Set(); let marks = []; // { x, y, ok } let stopped = false; let endTimer; function drawMarks() { scene.drawB(ctxB, halfW, imgH); // neu zeichnen marks.forEach(m => { ctxB.strokeStyle = m.ok ? '#22c55e' : '#ef4444'; ctxB.lineWidth = 3; ctxB.beginPath(); ctxB.arc(m.x, m.y, 18, 0, Math.PI * 2); ctxB.stroke(); if (m.ok) { ctxB.fillStyle = 'rgba(34,197,94,0.25)'; ctxB.fill(); } }); } function drawHUD() { ctxH.clearRect(0, 0, W, 48); MGAPI.text(ctxH, `πŸ” ${found.size} / ${scene.spots.length} Unterschiede gefunden`, W/2, 16, { size: 13, color: theme.primary }); MGAPI.text(ctxH, 'Klicke auf die Unterschiede im rechten Bild', W/2, 36, { size: 11, color: 'rgba(255,255,255,0.4)' }); } canvasB.addEventListener('click', e => { if (found.size >= scene.spots.length) return; const rect = canvasB.getBoundingClientRect(); const mx = (e.clientX - rect.left) * (canvasB.width / rect.width); const my = (e.clientY - rect.top) * (canvasB.height / rect.height); // NΓ€chsten Spot suchen let hit = null; scene.spots.forEach((s, i) => { if (found.has(i)) return; const sx = s.x * halfW, sy = s.y * imgH; const d = Math.sqrt((mx - sx) ** 2 + (my - sy) ** 2); if (d < s.r * Math.min(halfW, imgH)) hit = i; }); if (hit !== null) { found.add(hit); const sx = scene.spots[hit].x * halfW; const sy = scene.spots[hit].y * imgH; marks.push({ x: sx, y: sy, ok: true }); // Auch in Bild A markieren ctxA.strokeStyle = '#22c55e'; ctxA.lineWidth = 3; ctxA.beginPath(); ctxA.arc(sx, sy, 18, 0, Math.PI * 2); ctxA.stroke(); ctxA.fillStyle = 'rgba(34,197,94,0.2)'; ctxA.fill(); } else { marks.push({ x: mx, y: my, ok: false }); setTimeout(() => { marks = marks.filter(m => m.ok); drawMarks(); drawHUD(); }, 600); } drawMarks(); drawHUD(); if (found.size >= scene.spots.length && !endTimer) { endTimer = setTimeout(() => onDone(true), 900); // Overlay ctxH.fillStyle = 'rgba(16,185,129,0.9)'; MGAPI.roundRect(ctxH, W/2-120, 4, 240, 38, 8, 'rgba(16,185,129,0.9)', null); MGAPI.text(ctxH, 'πŸŽ‰ Alle Unterschiede gefunden!', W/2, 24, { size: 14, family: "'Fredoka One',cursive", color: '#fff' }); } }); drawHUD(); return { stop() { stopped = true; clearTimeout(endTimer); }, }; } 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 }; })();