// ==========================================
// 1. ASSETS LADEN (PIXI V8)
// ==========================================
// ==========================================
// 1. ASSETS LADEN (PIXI V8 KORREKT)
// ==========================================
async function loadAssets() {
const keysToLoad = [];
// A. Player hinzufügen
PIXI.Assets.add({ alias: 'player', src: 'assets/player.png' });
keysToLoad.push('player');
// B. Hintergründe aus Config
if (gameConfig.backgrounds) {
gameConfig.backgrounds.forEach(bg => {
// Alias = Dateiname (z.B. "school-background.jpg")
PIXI.Assets.add({ alias: bg, src: 'assets/' + bg });
keysToLoad.push(bg);
});
}
// C. Hindernisse aus Config
if (gameConfig.obstacles) {
gameConfig.obstacles.forEach(def => {
if (def.image) {
// Alias = ID (z.B. "teacher")
// Checken ob Alias schon existiert (vermeidet Warnungen)
if (!PIXI.Assets.cache.has(def.id)) {
PIXI.Assets.add({ alias: def.id, src: 'assets/' + def.image });
keysToLoad.push(def.id);
}
}
});
}
try {
console.log("Lade Assets...", keysToLoad);
// Alles auf einmal laden
await PIXI.Assets.load(keysToLoad);
console.log("✅ Alle Texturen geladen!");
} catch (e) {
console.error("❌ Asset Fehler:", e);
}
}
// ... (Rest der Datei: startGameClick, gameLoop etc. BLEIBT GLEICH)
// ...
window.startGameClick = async function() {
if (!isLoaded) return;
startScreen.style.display = 'none';
document.body.classList.add('game-active');
score = 0;
document.getElementById('score').innerText = "0";
if (typeof startMusic === 'function') startMusic();
connectGame();
resize();
};
// ==========================================
// 3. GAME OVER & SCORE
// ==========================================
window.gameOver = function(reason) {
if (isGameOver) return;
isGameOver = true;
console.log("Game Over:", reason);
// Highscore Check (Lokal)
const finalScore = Math.floor(score / 10);
const currentHighscore = localStorage.getItem('escape_highscore') || 0;
if (finalScore > parseInt(currentHighscore)) {
localStorage.setItem('escape_highscore', finalScore);
}
if (gameOverScreen) {
gameOverScreen.style.display = 'flex';
document.getElementById('finalScore').innerText = finalScore;
// Input Reset
document.getElementById('inputSection').style.display = 'flex';
document.getElementById('submitBtn').disabled = false;
document.getElementById('playerNameInput').value = "";
// Liste laden
loadLeaderboard();
}
};
window.submitScore = async function() {
const nameInput = document.getElementById('playerNameInput');
const name = nameInput.value.trim();
const btn = document.getElementById('submitBtn');
if (!name) return alert("Bitte Namen eingeben!");
if (!sessionID) return alert("Fehler: Keine Session ID vom Server erhalten.");
btn.disabled = true;
try {
const res = await fetch('/api/submit-name', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ sessionId: sessionID, name: name })
});
if (!res.ok) throw new Error("Server antwortet nicht");
const data = await res.json();
// Lokal speichern ("Meine Codes")
let myClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
myClaims.push({
name: name,
score: Math.floor(score / 10),
code: data.claimCode,
date: new Date().toLocaleString('de-DE'),
sessionId: sessionID
});
localStorage.setItem('escape_claims', JSON.stringify(myClaims));
// UI Update
document.getElementById('inputSection').style.display = 'none';
loadLeaderboard();
alert(`Gespeichert! Dein Code: ${data.claimCode}`);
} catch (e) {
alert("Fehler: " + e.message);
btn.disabled = false;
}
};
async function loadLeaderboard() {
try {
const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`);
const entries = await res.json();
let html = "
BESTENLISTE
";
if(!entries || entries.length === 0) html += "Leer.
";
else entries.forEach(e => {
const color = e.isMe ? "cyan" : "white";
const bg = e.isMe ? "background:rgba(0,255,255,0.1);" : "";
html += `
#${e.rank} ${e.name}${Math.floor(e.score/10)}
`;
});
document.getElementById('leaderboard').innerHTML = html;
} catch(e) { console.error(e); }
}
// ==========================================
// 4. MEINE CODES (LOGIK)
// ==========================================
window.showMyCodes = function() {
openModal('codes');
const listEl = document.getElementById('codesList');
if(!listEl) return;
const rawClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
if (rawClaims.length === 0) {
listEl.innerHTML = "Keine Codes.
";
return;
}
const sortedClaims = rawClaims.sort((a, b) => b.score - a.score);
let html = "";
sortedClaims.forEach(c => {
let rankIcon = "📄";
if (c.score >= 5000) rankIcon = "⭐";
if (c.score >= 10000) rankIcon = "🔥";
html += `
${rankIcon} ${c.code}
(${c.score} Pkt)
${c.name} • ${c.date}
`;
});
listEl.innerHTML = html;
};
window.deleteClaim = async function(code) {
if(!confirm("Eintrag wirklich löschen?")) return;
// Suchen der SessionID für den Server-Call
let claims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
const item = claims.find(c => c.code === code);
if (item && item.sessionId) {
try {
await fetch('/api/claim/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ sessionId: item.sessionId, claimCode: code })
});
} catch(e) { console.warn("Server delete failed"); }
}
// Lokal löschen
claims = claims.filter(c => c.code !== code);
localStorage.setItem('escape_claims', JSON.stringify(claims));
window.showMyCodes(); // Refresh
};
// ==========================================
// 5. GAME LOOP (PHYSICS + RENDER)
// ==========================================
function gameLoop(timestamp) {
requestAnimationFrame(gameLoop);
if (!isLoaded) return;
// Nur updaten, wenn Spiel läuft
if (isGameRunning && !isGameOver) {
if (!lastTime) lastTime = timestamp;
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
if (deltaTime > 1000) { accumulator = 0; return; }
accumulator += deltaTime;
// --- FIXED TIME STEP (Physik: 20 TPS) ---
while (accumulator >= MS_PER_TICK) {
updateGameLogic(); // logic.js (setzt prevX/prevY)
currentTick++;
// Score lokal hochzählen (damit es flüssig aussieht)
// Server korrigiert, falls Abweichung zu groß
score++;
accumulator -= MS_PER_TICK;
}
// HUD Update
const scoreEl = document.getElementById('score');
if (scoreEl) scoreEl.innerText = Math.floor(score / 10);
}
// --- INTERPOLATION (Rendering: 60+ FPS) ---
// alpha berechnen: Wie viel % ist seit dem letzten Tick vergangen?
const alpha = (isGameRunning && !isGameOver) ? (accumulator / MS_PER_TICK) : 1.0;
// drawGame ist jetzt in render.js und nutzt Pixi
drawGame(alpha);
}
// ==========================================
// 6. INITIALISIERUNG
// ==========================================
async function initGame() {
try {
const cRes = await fetch('/api/config');
gameConfig = await cRes.json();
// Pixi Assets laden
await loadAssets();
// Startscreen Bestenliste
await loadStartScreenLeaderboard();
isLoaded = true;
if(loadingText) loadingText.style.display = 'none';
if(startBtn) startBtn.style.display = 'inline-block';
// Mute Icon setzen (audio.js State)
if (typeof getMuteState === 'function') {
updateMuteIcon(getMuteState());
}
// Lokaler Highscore
const savedHighscore = localStorage.getItem('escape_highscore') || 0;
const hsEl = document.getElementById('localHighscore');
if(hsEl) hsEl.innerText = savedHighscore;
// Loop starten (für Idle Rendering)
requestAnimationFrame(gameLoop);
drawGame(1.0); // Initiale Zeichnung
} catch(e) {
console.error(e);
if(loadingText) loadingText.innerText = "Ladefehler (siehe Konsole)";
}
}
// Helper: Startscreen Leaderboard
async function loadStartScreenLeaderboard() {
try {
const listEl = document.getElementById('startLeaderboardList');
if (!listEl) return;
const res = await fetch('/api/leaderboard');
const entries = await res.json();
if (!entries || entries.length === 0) { listEl.innerHTML = "Noch keine Scores.
"; return; }
let html = "";
entries.forEach(e => {
let icon = "#" + e.rank;
if (e.rank === 1) icon = "🥇"; if (e.rank === 2) icon = "🥈"; if (e.rank === 3) icon = "🥉";
html += `${icon} ${e.name}${Math.floor(e.score / 10)}
`;
});
listEl.innerHTML = html;
} catch (e) {}
}
// Helper: Audio Button Logik
window.toggleAudioClick = function() {
if (typeof toggleMute === 'function') {
const muted = toggleMute();
updateMuteIcon(muted);
document.getElementById('mute-btn').blur();
}
};
function updateMuteIcon(isMuted) {
const btn = document.getElementById('mute-btn');
if (btn) {
btn.innerText = isMuted ? "🔇" : "🔊";
btn.style.color = isMuted ? "#ff4444" : "white";
btn.style.borderColor = isMuted ? "#ff4444" : "#555";
}
}
// Modal Helpers
window.openModal = function(id) { document.getElementById('modal-' + id).style.display = 'flex'; }
window.closeModal = function() { document.querySelectorAll('.modal-overlay').forEach(el => el.style.display = 'none'); }
window.onclick = function(event) { if (event.target.classList.contains('modal-overlay')) closeModal(); }
// Start
initGame();