142 lines
5.1 KiB
JavaScript
142 lines
5.1 KiB
JavaScript
/**
|
|
* 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 };
|
|
|
|
})();
|