// ========================================== // 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();