/** * Eigene Ergebnisse (Scores) verwalten und anzeigen. * Dieses Modul lädt nach der Initialisierung die bisherigen Spielergebnisse des * eingeloggten Benutzers und stellt diese sortiert in einer Tabelle dar. */ (function () { /** * Formatiert Sekunden in ein lesbares MM:SS Format (z.B. 75 Sekunden -> "1:15"). * @param {number} seconds - Anzahl Sekunden. * @returns {string} Formatiertes Zeit-String oder "-" bei ungültigem Wert. */ function formatTime(seconds) { if (typeof seconds !== "number" || Number.isNaN(seconds)) { return "-"; } const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); // padStart stellt sicher, dass Sekunden immer zweistellig ausgegeben werden (z.B. "05" statt "5") return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`; } /** * Normalisiert einen Benutzernamen (Trimming und Kleinschreibung) für robuste Vergleiche. * @param {string} username - Der Benutzername. * @returns {string} Der bereinigte Benutzername. */ function normalizeUsername(username) { return String(username ?? "").trim().toLowerCase(); } /** * Holt die aktuellen Authentifizierungsdaten aus dem globalen Auth-Modul. * @returns {Object|null} Auth-Objekt (mit username/password) oder null, falls nicht angemeldet. */ function getLoggedInAuth() { if (!window.AppAuth || typeof window.AppAuth.getAuth !== "function") { return null; } const auth = window.AppAuth.getAuth(); if (!auth || !auth.username || !auth.password) { return null; } return auth; } /** * Initialisiert und liefert eine Instanz des ScoreService. * @returns {ScoreService|null} Eine ScoreService-Instanz oder null, falls Konfiguration fehlt. */ function getScoreService() { if (!window.config || !window.ScoreService) { return null; } return new window.ScoreService(window.config); } /** * Ermittelt den anzuzeigenden Rang eines Score-Eintrags. * Falls das Backend keinen Platz/Rang liefert, wird die Position in der Liste (+1) als Fallback genutzt. * @param {Object} entry - Der Score-Eintrag. * @param {number} index - Der Listenindex des Eintrags. * @returns {number} Der anzuzeigende Rang (1-basiert). */ function getDisplayedRank(entry, index) { const place = Number(entry?.place); if (!Number.isNaN(place) && place > 0) { return place; } return index + 1; } /** * Blendet eine Feedback-Nachmeldung (Erfolg/Fehler/Info) auf der Oberfläche ein. * @param {string} message - Der anzuzeigende Text. * @param {string} type - Der Bootstrap-Alert-Typ (danger, warning, info, success). */ function setFeedback(message, type) { const feedback = document.getElementById("scores-feedback"); if (!feedback) { return; } feedback.className = "alert alert-" + type + " mb-4"; feedback.textContent = message; feedback.classList.remove("d-none"); } /** * Generiert die HTML-Zeilen für die Score-Tabelle aus den geladenen Einträgen. * Sortiert die Einträge: 1. nach Platzierung (falls vorhanden), 2. nach Score (absteigend), 3. nach Zeit (aufsteigend). * @param {Array} entries - Liste von Score-Einträgen vom Backend. */ function renderScores(entries) { const tableBody = document.getElementById("scores-body"); if (!tableBody) { return; } tableBody.innerHTML = ""; // Falls keine Einträge vorhanden sind, Hinweistext ausgeben if (!Array.isArray(entries) || entries.length === 0) { tableBody.innerHTML = 'Keine eigenen Scores gefunden.'; return; } // Kopie des Arrays anlegen und sortieren const sortedEntries = entries.slice().sort((a, b) => { // 1. Priorität: Offizielle Platzierung (falls vorhanden) const placeA = Number(a?.place); const placeB = Number(b?.place); if (!Number.isNaN(placeA) && !Number.isNaN(placeB) && placeA !== placeB) { return placeA - placeB; } // 2. Priorität: Höhere Punktzahl gewinnt const scoreA = Number(a?.score ?? 0); const scoreB = Number(b?.score ?? 0); if (scoreB !== scoreA) { return scoreB - scoreA; } // 3. Priorität: Bei Punktgleichheit gewinnt die schnellere Zeit const timeA = Number(a?.time ?? Number.MAX_SAFE_INTEGER); const timeB = Number(b?.time ?? Number.MAX_SAFE_INTEGER); return timeA - timeB; }); // Für jeden sortierten Eintrag eine Tabellenzeile erstellen und anhängen sortedEntries.forEach((entry, index) => { const row = document.createElement("tr"); row.innerHTML = ` ${getDisplayedRank(entry, index)} ${entry.username ?? "-"} ${formatTime(entry.time)} min ${entry.score ?? "-"} `; tableBody.appendChild(row); }); } /** * Fordert die Scores des Benutzers vom Backend an und filtert sie. */ async function loadMyScores() { const auth = getLoggedInAuth(); // Prüfen, ob der Benutzer überhaupt eingeloggt ist if (!auth || !auth.username) { setFeedback("Bitte logge dich ein, um deine Scores zu sehen.", "warning"); renderScores([]); return; } const scoreService = getScoreService(); if (!scoreService) { setFeedback("Score-Service konnte nicht geladen werden.", "danger"); renderScores([]); return; } try { // Scores für diesen Namen vom Backend abrufen const result = await scoreService.getScoreByName(auth.username); if (!result.ok || !Array.isArray(result.body)) { setFeedback("Deine Scores konnten nicht geladen werden.", "danger"); renderScores([]); return; } // Sicherheits-Filterung: Nur Scores behalten, die exakt zum aktuell eingeloggten User passen const ownScores = result.body.filter((entry) => { if (!entry || !entry.username) { return false; } return normalizeUsername(entry.username) === normalizeUsername(auth.username); }); if (ownScores.length === 0) { setFeedback("Für deinen Account wurden noch keine Scores gefunden.", "info"); } // Scores rendern renderScores(ownScores); } catch (error) { console.error("Fehler beim Laden der eigenen Scores:", error); setFeedback("Deine Scores konnten nicht geladen werden.", "danger"); renderScores([]); } } /** * Globale Initialisierungsfunktion, die von navigation.js aufgerufen wird, * sobald die scores.html-Teilseite erfolgreich geladen wurde. */ window.initScoresPage = function initScoresPage() { loadMyScores().catch((error) => { console.error("Fehler beim Initialisieren der Scores-Seite:", error); setFeedback("Deine Scores konnten nicht geladen werden.", "danger"); renderScores([]); }); }; })();