Files
bootle/app/static/js/pixies.js
T

216 lines
7.5 KiB
JavaScript
Executable File

// Wichtel-Panik / Cornish Pixies Arcade Engine (Mobile First)
(function() {
let score = 0;
const targetScore = 10;
let timeLeft = 35;
let gameRunning = false;
let timerInterval = null;
let spawnTimeout = null;
// DOM Elements
const scoreEl = document.getElementById('pixieScore');
const timerEl = document.getElementById('pixieTimer');
const startOverlay = document.getElementById('startOverlay');
const startBtn = document.getElementById('startPixieBtn');
const successPanel = document.getElementById('pixieSuccess');
const holes = document.querySelectorAll('.pixie-hole');
const activeTargets = new Map(); // holeIndex -> { isNeville: bool, timer: id, clicked: bool }
window.startPixieHunt = function() {
if (gameRunning) return;
gameRunning = true;
score = 0;
timeLeft = 35;
scoreEl.innerText = score;
timerEl.innerText = timeLeft;
if (startOverlay) startOverlay.style.display = 'none';
if (successPanel) successPanel.style.display = 'none';
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate(30);
// Start countdown
timerInterval = setInterval(() => {
timeLeft--;
timerEl.innerText = timeLeft;
if (timeLeft <= 0) {
endGame(false);
}
}, 1000);
scheduleNextSpawn();
};
function scheduleNextSpawn() {
if (!gameRunning) return;
const delay = Math.random() * 350 + 400; // 400ms - 750ms
spawnTimeout = setTimeout(() => {
spawnTarget();
scheduleNextSpawn();
}, delay);
}
function spawnTarget() {
if (!gameRunning) return;
// Find available holes
const available = [];
for (let i = 0; i < 9; i++) {
if (!activeTargets.has(i)) available.push(i);
}
if (available.length === 0) return;
const holeIdx = available[Math.floor(Math.random() * available.length)];
const targetEl = document.getElementById(`target${holeIdx}`);
if (!targetEl) return;
// 12% chance Neville, 88% chance Pixie
const isNeville = Math.random() < 0.12;
targetEl.className = 'pixie-target active';
targetEl.innerText = isNeville ? '👦' : '🧚';
if (isNeville) targetEl.classList.add('neville');
const stayTime = Math.random() * 450 + 900; // 900ms - 1350ms (forgiving for mobile)
const hideTimer = setTimeout(() => {
hideTarget(holeIdx);
}, stayTime);
activeTargets.set(holeIdx, {
isNeville: isNeville,
timer: hideTimer,
clicked: false
});
}
function hideTarget(holeIdx) {
const targetEl = document.getElementById(`target${holeIdx}`);
if (targetEl) {
targetEl.classList.remove('active');
targetEl.classList.remove('frozen');
targetEl.classList.remove('neville');
targetEl.innerText = '';
}
activeTargets.delete(holeIdx);
}
// Pointerdown handler on holes (zero delay on touch devices!)
holes.forEach(hole => {
const idx = parseInt(hole.dataset.idx, 10);
function onHoleTrigger(e) {
if (!gameRunning) return;
e.preventDefault();
if (!activeTargets.has(idx)) return;
const targetData = activeTargets.get(idx);
if (targetData.clicked) return;
targetData.clicked = true;
const targetEl = document.getElementById(`target${idx}`);
if (targetData.isNeville) {
// Clicked Neville -> Penalty!
if (window.soundFX) window.soundFX.playWhoosh();
if (window.navigator && window.navigator.vibrate) window.navigator.vibrate([40, 40]);
score = Math.max(0, score - 2);
scoreEl.innerText = score;
targetEl.style.transform = 'scale(1.25) rotate(15deg)';
if (window.showFloatingToast) {
window.showFloatingToast("🤕 <strong>Neville:</strong> »Aua! Ich bin doch kein Wichtel!« (-2 Pkt)");
}
setTimeout(() => hideTarget(idx), 350);
} else {
// Clicked Pixie -> Immobilus!
clearTimeout(targetData.timer);
targetEl.classList.add('frozen');
targetEl.innerText = '❄️';
if (window.soundFX) {
window.soundFX.playKeyChime();
}
if (window.navigator && window.navigator.vibrate) {
window.navigator.vibrate(35);
}
score++;
scoreEl.innerText = score;
if (score >= targetScore) {
endGame(true);
} else {
setTimeout(() => hideTarget(idx), 380);
}
}
}
// Use pointerdown for instant touch & mouse response without delay
hole.addEventListener('pointerdown', onHoleTrigger);
});
async function endGame(won) {
gameRunning = false;
clearInterval(timerInterval);
clearTimeout(spawnTimeout);
// Hide all active
activeTargets.forEach((val, idx) => hideTarget(idx));
activeTargets.clear();
if (won) {
let newItem = null;
try {
const res = await fetch('/api/pixies/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ caught: score })
});
const data = await res.json();
if (data.new_item) newItem = data.new_item;
} catch (e) {
console.error("Pixies complete error:", e);
}
if (!newItem) {
newItem = {
id: "pixie_jar",
name: "Wichtelglas",
icon: "🧚",
desc: "Ein dickbauchiges Glas mit einem gebannten Wichtel."
};
}
if (successPanel) successPanel.style.display = 'block';
if (window.showChamberVictory) {
window.showChamberVictory({
chamberBadge: "Kammer V: Wichtel-Panik",
title: "🧚 Wichtel Gebannt!",
statsHtml: `<span>❄️ Gebannt: <strong>${score} Wichtel</strong></span><span>👦 Neville: <strong>Gerettet</strong></span>`,
item: newItem,
storyText: "Mit blitzschnellen <em>Immobilus!</em>-Zaubern hast du alle 10 Wichtel eingefroren! Ein zappelnder Wichtel landet sicher im <strong>Wichtelglas</strong>. Neville bedankt sich erleichtert. Der Weg zur berüchtigten <strong>Flammenkammer von Severus Snape</strong> liegt nun offen!",
nextUrl: "/bottles",
nextBtnText: "➔ Weiter zu Kammer VI: Snapes Flaschen 🧪"
});
}
} else {
if (window.soundFX) window.soundFX.playWhoosh();
if (startOverlay) {
startOverlay.style.display = 'flex';
if (startBtn) startBtn.innerText = "Erneut versuchen 🔄";
}
if (window.showFloatingToast) {
window.showFloatingToast("⏰ Die Zeit ist abgelaufen! Versuch es noch einmal.");
}
}
}
})();