/** * minigames/snake.js * 🐍 Snake β€” Steuere die Schlange, friss Γ„pfel ohne gegen die Wand zu fahren */ window.MG_snake = (function() { const ID = 'snake'; const EMOJI = '🐍'; const NAME = 'Snake'; const DESC = 'Steuere die Schlange! Friss 3 Γ„pfel ohne gegen die Wand zu fahren.'; const CONTROLS = 'Pfeiltasten oder WASD'; const MULTI = 1; // einmalig pro Spiel // ── Gemeinsame Spiel-Logik ──────────────────────────────────────────────── function run(wrap, W, H, cfg, onDone) { const { canvas, ctx } = MGAPI.makeCanvas(wrap, W, H); const SZ = 20; const COLS = Math.floor(W / SZ); const ROWS = Math.floor(H / SZ); const WIN_SCORE = cfg.winScore || 3; const theme = cfg.theme || { primary: '#10b981', glow: 'rgba(16,185,129,0.4)' }; function rndFood() { return { x: Math.floor(Math.random() * COLS), y: Math.floor(Math.random() * ROWS), }; } let snake = [{ x: 5, y: 5 }, { x: 4, y: 5 }, { x: 3, y: 5 }]; let dir = { x: 1, y: 0 }; let nextDir = { x: 1, y: 0 }; let food = rndFood(); let score = 0; let dead = false; let stopped = false; let raf, endTimer, last = 0; // Steuerung function onKey(e) { const map = { ArrowUp: { x: 0, y: -1 }, w: { x: 0, y: -1 }, ArrowDown: { x: 0, y: 1 }, s: { x: 0, y: 1 }, ArrowLeft: { x: -1, y: 0 }, a: { x: -1, y: 0 }, ArrowRight: { x: 1, y: 0 }, d: { x: 1, y: 0 }, }; const d = map[e.key]; if (d && (d.x !== -dir.x || d.y !== -dir.y)) { e.preventDefault(); nextDir = d; } } document.addEventListener('keydown', onKey); // Touch-Steuerung (Swipe) let touchStart = null; canvas.addEventListener('touchstart', e => { touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY }; }, { passive: true }); canvas.addEventListener('touchend', e => { if (!touchStart) return; const dx = e.changedTouches[0].clientX - touchStart.x; const dy = e.changedTouches[0].clientY - touchStart.y; if (Math.abs(dx) > Math.abs(dy)) { nextDir = dx > 0 ? { x: 1, y: 0 } : { x: -1, y: 0 }; } else { nextDir = dy > 0 ? { x: 0, y: 1 } : { x: 0, y: -1 }; } }, { passive: true }); function drawGrid() { ctx.strokeStyle = 'rgba(255,255,255,0.04)'; ctx.lineWidth = 0.5; for (let x = 0; x < COLS; x++) for (let y = 0; y < ROWS; y++) ctx.strokeRect(x * SZ, y * SZ, SZ, SZ); } function loop(ts) { if (stopped) return; raf = requestAnimationFrame(loop); if (ts - last < 140) return; last = ts; dir = nextDir; if (!dead) { const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y }; if ( head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS || snake.some(s => s.x === head.x && s.y === head.y) ) { dead = true; if (!endTimer) endTimer = setTimeout(() => onDone(score >= WIN_SCORE), 1200); } else { snake.unshift(head); if (head.x === food.x && head.y === food.y) { score++; food = rndFood(); if (score >= WIN_SCORE && !endTimer) { endTimer = setTimeout(() => onDone(true), 1200); } } else { snake.pop(); } } } // Zeichnen ctx.fillStyle = '#050508'; ctx.fillRect(0, 0, W, H); drawGrid(); // Futter ctx.shadowColor = theme.primary; ctx.shadowBlur = 12; ctx.font = `${SZ - 2}px serif`; ctx.textAlign = 'center'; ctx.fillText('🍎', food.x * SZ + SZ / 2, food.y * SZ + SZ / 1.2); ctx.shadowBlur = 0; // Schlange snake.forEach((s, i) => { ctx.fillStyle = i === 0 ? theme.primary : `${theme.primary}88`; if (i === 0) { ctx.shadowColor = theme.primary; ctx.shadowBlur = 8; } MGAPI.roundRect(ctx, s.x * SZ + 2, s.y * SZ + 2, SZ - 4, SZ - 4, 4, ctx.fillStyle, null); ctx.shadowBlur = 0; }); // HUD MGAPI.text(ctx, `🍎 ${score} / ${WIN_SCORE}`, 8, 14, { align: 'left', size: 13, color: theme.primary }); // Ergebnis-Screen if (dead) { MGAPI.resultScreen(ctx, W, H, score >= WIN_SCORE, score >= WIN_SCORE ? `${score} Γ„pfel gefressen!` : `Nur ${score} Γ„pfel β€” nΓ€chstes Mal!`); } } raf = requestAnimationFrame(loop); return { stop() { stopped = true; cancelAnimationFrame(raf); clearTimeout(endTimer); document.removeEventListener('keydown', onKey); }, }; } // ── VollstΓ€ndiges Spiel (game.html) ────────────────────────────────────── function launch(wrap, W, H, cfg) { return run(wrap, W, H, cfg, won => MGAPI.onResult(won)); } // ── Editor-Vorschau (Test-Popup) ───────────────────────────────────────── function preview(wrap, W, H, cfg) { return run(wrap, W, H, { ...cfg, winScore: 3 }, won => MGAPI.onResult(won)); } return { id: ID, emoji: EMOJI, name: NAME, desc: DESC, controls: CONTROLS, multi: MULTI, launch, preview }; })();