// ========================================== // 1. ASSETS LADEN // ========================================== async function loadAssets() { const pPromise = new Promise(resolve => { playerSprite.src = "assets/player.png"; playerSprite.onload = resolve; playerSprite.onerror = () => { resolve(); }; }); const bgPromises = gameConfig.backgrounds.map((bgFile, index) => { return new Promise((resolve) => { const img = new Image(); img.src = "assets/" + bgFile; img.onload = () => { bgSprites[index] = img; resolve(); }; img.onerror = () => { resolve(); }; }); }); const obsPromises = gameConfig.obstacles.map(def => { return new Promise((resolve) => { if (!def.image) { resolve(); return; } const img = new Image(); img.src = "assets/" + def.image; img.onload = () => { sprites[def.id] = img; resolve(); }; img.onerror = () => { resolve(); }; }); }); await Promise.all([pPromise, ...bgPromises, ...obsPromises]); } // ========================================== // 2. SPIEL STARTEN // ========================================== window.startGameClick = async function() { if (!isLoaded) return; startScreen.style.display = 'none'; document.body.classList.add('game-active'); // Score Reset visuell score = 0; const scoreEl = document.getElementById('score'); if (scoreEl) scoreEl.innerText = "0"; // WebSocket Start startMusic(); connectGame(); resize(); }; // ========================================== // 3. GAME OVER & HIGHSCORE LOGIK // ========================================== window.gameOver = function(reason) { if (isGameOver) return; isGameOver = true; console.log("Game Over:", reason); const finalScore = Math.floor(score / 10); const currentHighscore = localStorage.getItem('escape_highscore') || 0; if (finalScore > currentHighscore) { localStorage.setItem('escape_highscore', finalScore); } if (gameOverScreen) { gameOverScreen.style.display = 'flex'; document.getElementById('finalScore').innerText = finalScore; // Input wieder anzeigen document.getElementById('inputSection').style.display = 'flex'; document.getElementById('submitBtn').disabled = false; // Liste laden loadLeaderboard(); } }; // Name absenden (Button Click) 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!"); 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 }) // sessionID aus state.js }); if (!res.ok) throw new Error("Fehler beim Senden"); const data = await res.json(); // Code lokal speichern (Claims) 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) { console.error(e); alert("Fehler beim Speichern: " + e.message); btn.disabled = false; } }; // Bestenliste laden (Game Over Screen) async function loadLeaderboard() { try { // sessionID wird mitgesendet, um den eigenen Eintrag zu markieren const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`); const entries = await res.json(); let html = "

BESTENLISTE

"; if(entries.length === 0) html += "
Noch keine Einträge.
"; entries.forEach(e => { const color = e.isMe ? "cyan" : "white"; // Eigener Name in Cyan const bgStyle = 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("Leaderboard Error:", e); } } // ========================================== // 4. GAME LOOP // ========================================== function gameLoop(timestamp) { requestAnimationFrame(gameLoop); if (!isLoaded) return; if (isGameRunning && !isGameOver) { if (!lastTime) lastTime = timestamp; const deltaTime = timestamp - lastTime; lastTime = timestamp; if (deltaTime > 1000) { accumulator = 0; return; } accumulator += deltaTime; while (accumulator >= MS_PER_TICK) { updateGameLogic(); currentTick++; score++; accumulator -= MS_PER_TICK; } const alpha = accumulator / MS_PER_TICK; // Score im HUD const scoreEl = document.getElementById('score'); if (scoreEl) scoreEl.innerText = Math.floor(score / 10); } drawGame(isGameRunning ? accumulator / MS_PER_TICK : 1.0); } // ========================================== // 5. INIT // ========================================== async function initGame() { try { const cRes = await fetch('/api/config'); gameConfig = await cRes.json(); await loadAssets(); await loadStartScreenLeaderboard(); if (typeof getMuteState === 'function') { updateMuteIcon(getMuteState()); } isLoaded = true; if(loadingText) loadingText.style.display = 'none'; if(startBtn) startBtn.style.display = 'inline-block'; const savedHighscore = localStorage.getItem('escape_highscore') || 0; const hsEl = document.getElementById('localHighscore'); if(hsEl) hsEl.innerText = savedHighscore; requestAnimationFrame(gameLoop); drawGame(); } catch(e) { console.error(e); if(loadingText) loadingText.innerText = "Ladefehler (siehe Konsole)"; } } // Helper: Mini-Leaderboard auf Startseite 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.length === 0) { listEl.innerHTML = "
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) {} } // Audio Toggle Funktion für den Button window.toggleAudioClick = function() { // 1. Audio umschalten (in audio.js) const muted = toggleMute(); // 2. Button Icon updaten updateMuteIcon(muted); // 3. Fokus vom Button nehmen (damit Space nicht den Button drückt, sondern springt) 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"; } } // ========================================== // MEINE CODES (LOCAL STORAGE) // ========================================== // 1. Codes anzeigen (Wird vom Button im Startscreen aufgerufen) window.showMyCodes = function() { // Modal öffnen openModal('codes'); const listEl = document.getElementById('codesList'); if(!listEl) return; // Daten aus dem Browser-Speicher holen const rawClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]'); if (rawClaims.length === 0) { listEl.innerHTML = "
Keine Codes gespeichert.
"; return; } // Sortieren nach Score (Höchster zuerst) const sortedClaims = rawClaims .map((item, index) => ({ ...item, originalIndex: index })) .sort((a, b) => b.score - a.score); let html = ""; sortedClaims.forEach(c => { // Icons basierend auf Score let rankIcon = "📄"; if (c.score >= 5000) rankIcon = "⭐"; if (c.score >= 10000) rankIcon = "🔥"; if (c.score >= 20000) rankIcon = "👑"; html += `
${rankIcon} ${c.code} (${c.score} Pkt)
${c.name} • ${c.date}
`; }); listEl.innerHTML = html; }; // 2. Code löschen (Lokal und auf Server) window.deleteClaim = async function(sid, code) { if(!confirm("Eintrag wirklich löschen?")) return; // Versuch, es auf dem Server zu löschen try { await fetch('/api/claim/delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: sid, claimCode: code }) }); } catch(e) { console.warn("Server Delete fehlgeschlagen (vielleicht schon weg), lösche lokal..."); } // Lokal löschen let claims = JSON.parse(localStorage.getItem('escape_claims') || '[]'); // Wir filtern den Eintrag raus, der die gleiche SessionID UND den gleichen Code hat claims = claims.filter(c => c.code !== code); localStorage.setItem('escape_claims', JSON.stringify(claims)); // Liste aktualisieren window.showMyCodes(); // Leaderboard aktualisieren (falls im Hintergrund sichtbar) if(document.getElementById('startLeaderboardList')) { loadStartScreenLeaderboard(); } }; // ========================================== // MODAL LOGIK (Fenster auf/zu) // ========================================== window.openModal = function(id) { const el = document.getElementById('modal-' + id); if(el) el.style.display = 'flex'; } window.closeModal = function() { const modals = document.querySelectorAll('.modal-overlay'); modals.forEach(el => el.style.display = 'none'); } // Klick nebendran schließt Modal window.onclick = function(event) { if (event.target.classList.contains('modal-overlay')) { closeModal(); } } initGame();