/** * minigames/catch.js * 🍎 Äpfel fangen — Bewege den Korb und fange fallende Früchte! */ window.MG_catch = (function() { const ID = 'catch'; const EMOJI = '🍎'; const NAME = 'Äpfel fangen'; const DESC = 'Bewege den Korb mit ← → oder der Maus und fange 5 Äpfel!'; const CONTROLS = '← → / A D / Maus'; const MULTI = 3; function run(wrap, W, H, cfg, onDone) { const { canvas, ctx } = MGAPI.makeCanvas(wrap, W, H); const theme = cfg.theme || { primary: '#22c55e' }; const WIN = cfg.winCatch || 20; const FRUITS = ['🍎','🍊','🍋','🍇','🍓','🍑']; const BASKET_W = 70, BASKET_H = 24; let basket = { x: W / 2 - BASKET_W / 2 }; let items = []; let score = 0; let missed = 0; let frame = 0; let stopped = false; let raf, endTimer, result = null; const keys = {}; let mouseX = null; const onKey = e => { keys[e.code] = e.type === 'keydown'; }; const onMouse = e => { const rect = canvas.getBoundingClientRect(); mouseX = (e.clientX - rect.left) * (canvas.width / rect.width / (window.devicePixelRatio || 1)) - BASKET_W / 2; }; const onTouch = e => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); mouseX = (e.touches[0].clientX - rect.left) * (canvas.width / rect.width / (window.devicePixelRatio || 1)) - BASKET_W / 2; }; document.addEventListener('keydown', onKey); document.addEventListener('keyup', onKey); canvas.addEventListener('mousemove', onMouse); canvas.addEventListener('touchmove', onTouch, { passive: false }); function spawnItem() { items.push({ x: Math.random() * (W - 40) + 20, y: -30, vy: 3.2 + Math.random() * 1.2 + score * 0.22, // schneller + mehr Zuwachs emoji: FRUITS[Math.floor(Math.random() * FRUITS.length)], size: 26 + Math.random() * 10, }); } function loop() { if (stopped) return; raf = requestAnimationFrame(loop); frame++; if (mouseX !== null) { basket.x += (mouseX - basket.x) * 0.18; } else { if (keys['ArrowLeft'] || keys['KeyA']) basket.x -= 5.5; if (keys['ArrowRight'] || keys['KeyD']) basket.x += 5.5; } basket.x = Math.max(0, Math.min(W - BASKET_W, basket.x)); const spawnRate = Math.max(22, 55 - score * 3); // häufiger spawnen if (frame % spawnRate === 0 && !result) spawnItem(); items.forEach(it => { it.y += it.vy; }); items = items.filter(it => { const basketTop = H - 50 - BASKET_H; if (it.y + it.size / 2 > basketTop && it.x > basket.x && it.x < basket.x + BASKET_W) { score++; if (score >= WIN && !endTimer) endTimer = setTimeout(() => { result = 'win'; setTimeout(() => onDone(true), 900); }, 200); return false; } if (it.y > H + 20) { missed++; if (missed >= 4 && score < WIN && !endTimer) endTimer = setTimeout(() => { result = 'lose'; setTimeout(() => onDone(false), 900); }, 200); return false; } return true; }); ctx.fillStyle = '#050508'; ctx.fillRect(0, 0, W, H); ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, H - 50); ctx.lineTo(W, H - 50); ctx.stroke(); ctx.font = '28px serif'; ctx.textAlign = 'center'; items.forEach(it => ctx.fillText(it.emoji, it.x, it.y + it.size)); const bx = basket.x, by = H - 50 - BASKET_H; MGAPI.roundRect(ctx, bx, by, BASKET_W, BASKET_H, 6, `${theme.primary}33`, theme.primary); for (let i = 1; i < 4; i++) { ctx.strokeStyle = `${theme.primary}66`; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(bx+(BASKET_W/4)*i, by); ctx.lineTo(bx+(BASKET_W/4)*i, by+BASKET_H); ctx.stroke(); } ctx.font = '22px serif'; ctx.fillText('🧺', bx + BASKET_W / 2, by + BASKET_H + 20); MGAPI.text(ctx, `🍎 ${score} / ${WIN} 💔 ${missed} / 4`, W / 2, 18, { size: 13, color: theme.primary }); if (!result) MGAPI.text(ctx, '← → oder Maus bewegen', W / 2, H - 10, { size: 10, color: 'rgba(255,255,255,0.3)' }); if (result) MGAPI.resultScreen(ctx, W, H, result === 'win', result === 'win' ? `${score} Früchte gefangen!` : `${missed} verpasst — nächstes Mal!`); } raf = requestAnimationFrame(loop); return { stop() { stopped = true; cancelAnimationFrame(raf); clearTimeout(endTimer); document.removeEventListener('keydown', onKey); document.removeEventListener('keyup', onKey); canvas.removeEventListener('mousemove', onMouse); canvas.removeEventListener('touchmove', onTouch); }, }; } 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 }; })();