Private
Public Access
1
0
Files
it232Abschied/static/js/main.js
Sebastian Unterschütz cf2e6e1c94 bug fixes
2025-11-26 18:56:59 +01:00

299 lines
11 KiB
JavaScript

// ==========================================
// INIT & ASSETS
// ==========================================
async function loadAssets() {
playerSprite.src = "assets/player.png";
// Hintergründe laden
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 = () => {
console.warn("BG fehlt:", bgFile);
bgSprites[index] = null;
resolve();
};
});
});
// Hindernisse laden
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(); };
});
});
// Player laden (kleiner Promise Wrapper)
const pPromise = new Promise(r => {
playerSprite.onload = r;
playerSprite.onerror = r;
});
await Promise.all([pPromise, ...bgPromises, ...obsPromises]);
}
// ==========================================
// START LOGIK
// ==========================================
window.startGameClick = async function() {
if (!isLoaded) return;
startScreen.style.display = 'none';
document.body.classList.add('game-active');
try {
const sRes = await fetch('/api/start', {method:'POST'});
const sData = await sRes.json();
sessionID = sData.sessionId;
rng = new PseudoRNG(sData.seed);
isGameRunning = true;
maxRawBgIndex = 0;
lastTime = performance.now();
resize();
} catch(e) {
alert("Start Fehler: " + e.message);
location.reload();
}
};
// ==========================================
// SCORE EINTRAGEN
// ==========================================
window.submitScore = async function() {
const nameInput = document.getElementById('playerNameInput');
const name = nameInput.value;
const btn = document.getElementById('submitBtn');
if (!name) return alert("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 })
});
if (!res.ok) throw new Error("Server Error");
const data = await res.json();
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));
document.getElementById('inputSection').style.display = 'none';
loadLeaderboard();
alert(`Gespeichert! Code: ${data.claimCode}`);
} catch (e) {
alert("Fehler: " + e.message);
btn.disabled = false;
}
};
// ==========================================
// MEINE CODES & LÖSCHEN
// ==========================================
window.showMyCodes = function() {
if(window.openModal) window.openModal('codes');
const listEl = document.getElementById('codesList');
if(!listEl) return;
const rawClaims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
if (rawClaims.length === 0) {
listEl.innerHTML = "<div style='padding:10px; text-align:center; color:#666;'>Keine Codes gespeichert.</div>";
return;
}
const sortedClaims = rawClaims
.map((item, index) => ({ ...item, originalIndex: index }))
.sort((a, b) => b.score - a.score);
let html = "";
sortedClaims.forEach(c => {
const canDelete = c.sessionId ? true : false;
const btnStyle = canDelete ? "cursor:pointer; color:#ff4444; border-color:#ff4444;" : "cursor:not-allowed; color:gray; border-color:gray;";
const btnAttr = canDelete ? `onclick="deleteClaim(${c.originalIndex}, '${c.sessionId}', '${c.code}')"` : "disabled";
let rankIcon = "📄";
if (c.score >= 10000) rankIcon = "🔥";
if (c.score >= 5000) rankIcon = "⭐";
html += `
<div style="border-bottom:1px solid #444; padding:8px 0; display:flex; justify-content:space-between; align-items:center;">
<div style="text-align:left;">
<span style="color:#00e5ff; font-weight:bold; font-size:12px;">${rankIcon} ${c.code}</span>
<span style="color:#ffcc00; font-weight:bold;">(${c.score} Pkt)</span><br>
<span style="color:#aaa; font-size:9px;">${c.name}${c.date}</span>
</div>
<button ${btnAttr}
style="background:transparent; border:1px solid; padding:5px; font-size:9px; margin:0; ${btnStyle}">
LÖSCHEN
</button>
</div>`;
});
listEl.innerHTML = html;
};
window.deleteClaim = async function(index, sid, code) {
if(!confirm("Wirklich löschen?")) return;
try {
const res = await fetch('/api/claim/delete', {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ sessionId: sid, claimCode: code })
});
if (!res.ok) {
if(!confirm("Server Fehler (evtl. schon weg). Lokal löschen?")) return;
}
let claims = JSON.parse(localStorage.getItem('escape_claims') || '[]');
claims.splice(index, 1);
localStorage.setItem('escape_claims', JSON.stringify(claims));
window.showMyCodes();
loadLeaderboard();
} catch(e) { alert("Verbindungsfehler!"); }
};
async function loadLeaderboard() {
try {
const res = await fetch(`/api/leaderboard?sessionId=${sessionID}`);
const entries = await res.json();
let html = "<h3 style='margin-bottom:5px'>BESTENLISTE</h3>";
entries.forEach(e => {
const color = e.isMe ? "yellow" : "white";
const bgStyle = e.isMe ? "background:rgba(255,255,0,0.1);" : "";
const betterThanMe = e.rank - 1;
let infoText = "";
if (e.isMe && betterThanMe > 0) {
infoText = `<div style='font-size:8px; color:#aaa;'>(${betterThanMe} waren besser)</div>`;
} else if (e.isMe && betterThanMe === 0) {
infoText = `<div style='font-size:8px; color:#ffcc00;'>👑 NIEMAND ist besser!</div>`;
}
html += `
<div style="border-bottom:1px dotted #444; padding:5px; ${bgStyle} margin-bottom:2px;">
<div style="display:flex; justify-content:space-between; color:${color};">
<span>#${e.rank} ${e.name.toUpperCase()}</span>
<span>${Math.floor(e.score/10)}</span>
</div>
${infoText}
</div>`;
if(e.rank === 3 && entries.length > 3 && !entries[3].isMe) {
html += "<div style='text-align:center; color:gray; font-size:8px;'>...</div>";
}
});
document.getElementById('leaderboard').innerHTML = html;
} catch(e) { console.error(e); }
}
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 = "<div style='padding:20px'>Noch keine Scores.</div>"; 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 += `<div class="hof-entry"><span><span class="hof-rank">${icon}</span> ${e.name}</span><span class="hof-score">${Math.floor(e.score / 10)}</span></div>`;
});
listEl.innerHTML = html;
} catch (e) {}
}
function gameOver(reason) {
if (isGameOver) return;
isGameOver = true;
const finalScoreVal = Math.floor(score / 10);
const currentHighscore = localStorage.getItem('escape_highscore') || 0;
if (finalScoreVal > currentHighscore) localStorage.setItem('escape_highscore', finalScoreVal);
gameOverScreen.style.display = 'flex';
document.getElementById('finalScore').innerText = finalScoreVal;
loadLeaderboard();
drawGame();
}
// ==========================================
// DER FIXIERTE GAME LOOP
// ==========================================
function gameLoop(timestamp) {
requestAnimationFrame(gameLoop);
// 1. Wenn Assets noch nicht da sind, machen wir gar nichts
if (!isLoaded) return;
// 2. PHYSIK-LOGIK (Nur wenn Spiel läuft und nicht Game Over)
// Das hier sorgt dafür, dass der Dino stehen bleibt, wenn wir im Menü sind
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++;
if (currentTick - lastSentTick >= CHUNK_SIZE) sendChunk();
accumulator -= MS_PER_TICK;
}
const scoreEl = document.getElementById('score');
if (scoreEl) scoreEl.innerText = Math.floor(score / 10);
}
// 3. RENDERING (IMMER!)
// Das hier war das Problem. Früher stand hier "return" wenn !isGameRunning.
// Jetzt malen wir immer. Wenn isGameRunning false ist, malt er einfach den Start-Zustand.
drawGame();
}
async function initGame() {
try {
const cRes = await fetch('/api/config'); gameConfig = await cRes.json();
// Erst alles laden
await loadAssets();
await loadStartScreenLeaderboard();
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;
// Loop starten (mit dummy timestamp start)
requestAnimationFrame(gameLoop);
// Initiales Zeichnen erzwingen (damit Hintergrund sofort da ist)
drawGame();
} catch(e) {
console.error(e);
if(loadingText) loadingText.innerText = "Ladefehler (siehe Konsole)";
}
}
initGame();