From 9f995e213ac8d7a611dd6abf751148bfebb460f1 Mon Sep 17 00:00:00 2001 From: Trompi001 Date: Wed, 22 Apr 2026 10:23:35 +0200 Subject: [PATCH 1/5] creating leaderboard --- assets/css/custom.css | 64 ++++++++++++++++ index.html | 1 + js/leaderboard.js | 164 +++++++++++++++++++++++++++++++++++++++++ js/navigation.js | 3 + pages/leaderboard.html | 16 +++- 5 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 js/leaderboard.js diff --git a/assets/css/custom.css b/assets/css/custom.css index a9b6183..9536f2c 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -213,3 +213,67 @@ p { } +/* Table styles leaderboard */ +.leaderboard-table { + border-collapse: collapse; + width: 100%; + background: #ffffff; + border-radius: 10px; + overflow: hidden; +} + +.leaderboard-table th, +.leaderboard-table td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.leaderboard-table thead th { + background: #1b1b2f; + color: #ffffff; + font-weight: 600; + letter-spacing: 0.02em; +} + +.leaderboard-table tbody tr { + transition: background-color 0.2s ease; +} + +.leaderboard-table tbody tr:nth-child(even) { + background-color: #f7f9fc; +} + +.leaderboard-table tbody tr:hover { + background-color: #dbe6f7; +} + +.leaderboard-table tbody tr.leaderboard-row-current-user, +.leaderboard-table tbody tr.leaderboard-row-current-user:nth-child(even), +.leaderboard-table tbody tr.leaderboard-row-current-user:hover { + background-color: #dff3df; +} + +.leaderboard-table tbody tr.leaderboard-row-current-user td { + font-weight: 600; +} + +.leaderboard-table tbody tr.leaderboard-row-gap td { + border-bottom: none; + padding: 0; + height: 16px; + background: transparent; +} + +.leaderboard-table tbody tr.leaderboard-row-gap:hover, +.leaderboard-table tbody tr.leaderboard-row-gap:hover td { + background: transparent; +} + +.leaderboard-table tbody tr.leaderboard-row-current-user-extra td { + border-top: 2px solid #b8dfb8; +} + +.leaderboard-table tbody tr:last-child td { + border-bottom: none; +} \ No newline at end of file diff --git a/index.html b/index.html index d9b72dd..66319c0 100644 --- a/index.html +++ b/index.html @@ -58,6 +58,7 @@ + diff --git a/js/leaderboard.js b/js/leaderboard.js new file mode 100644 index 0000000..3595811 --- /dev/null +++ b/js/leaderboard.js @@ -0,0 +1,164 @@ +// Formatiert Sekunden als m:ss. Bei ungültigem Wert wird ein Platzhalter angezeigt. +function formatTime(seconds) { + if (typeof seconds !== "number" || Number.isNaN(seconds)) { + return "-"; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`; +} + +// Liefert den aktuellen Login-Kontext, falls Auth global verfügbar ist. +function getLoggedInAuth() { + if (!window.AppAuth || typeof window.AppAuth.getAuth !== "function") { + return null; + } + + const auth = window.AppAuth.getAuth(); + if (!auth || !auth.username) { + return null; + } + + return auth; +} + +// Vereinheitlicht Benutzernamen für robuste Vergleiche (z. B. Groß-/Kleinschreibung). +function normalizeUsername(username) { + return String(username ?? "").trim().toLowerCase(); +} + +function getLoggedInUsername() { + const auth = getLoggedInAuth(); + if (!auth) { + return null; + } + + return normalizeUsername(auth.username); +} + +// Nutzt den vom Backend gelieferten Rang, fallback auf die aktuelle Listenposition. +function getDisplayedRank(entry, index) { + const place = Number(entry?.place); + if (!Number.isNaN(place) && place > 0) { + return place; + } + + return index + 1; +} + +// Bestes Ergebnis: höchste Punktzahl, bei Gleichstand die geringere Zeit. +function getBestScoreEntry(entries) { + return entries + .slice() + .sort((a, b) => { + const scoreA = Number(a.score ?? 0); + const scoreB = Number(b.score ?? 0); + if (scoreB !== scoreA) { + return scoreB - scoreA; + } + + const timeA = Number(a.time ?? Number.MAX_SAFE_INTEGER); + const timeB = Number(b.time ?? Number.MAX_SAFE_INTEGER); + return timeA - timeB; + })[0] ?? null; +} + +async function getCurrentUserLeaderboardEntry(username) { + if (!window.ScoreService || !username) { + return null; + } + + const scoreService = new window.ScoreService(window.config); + const result = await scoreService.getScoreByName(username); + + if (!result.ok || !Array.isArray(result.body) || result.body.length === 0) { + return null; + } + + return getBestScoreEntry(result.body); +} + +// Rendert die Top-Liste und markiert den eingeloggten Nutzer visuell. +function renderLeaderboard(entries, extraUserEntry = null) { + const tableBody = document.getElementById("leaderboard-body"); + if (!tableBody) { + return; + } + + const loggedInUsername = getLoggedInUsername(); + + tableBody.innerHTML = ""; + + entries.forEach((entry, index) => { + const row = document.createElement("tr"); + const rowUsername = normalizeUsername(entry.username); + + if (loggedInUsername && rowUsername === loggedInUsername) { + row.classList.add("leaderboard-row-current-user"); + } + + row.innerHTML = ` + ${getDisplayedRank(entry, index)} + ${entry.username ?? "-"} + ${formatTime(entry.time)} min + ${entry.score ?? "-"} + `; + + tableBody.appendChild(row); + }); + + if (extraUserEntry) { + // Trennt Top-10 und eigenen Eintrag optisch, wenn der Nutzer nicht in den Top-10 ist. + const spacerRow = document.createElement("tr"); + spacerRow.classList.add("leaderboard-row-gap"); + spacerRow.innerHTML = ''; + tableBody.appendChild(spacerRow); + + const userRow = document.createElement("tr"); + userRow.classList.add("leaderboard-row-current-user"); + userRow.classList.add("leaderboard-row-current-user-extra"); + userRow.innerHTML = ` + ${getDisplayedRank(extraUserEntry, 0)} + ${extraUserEntry.username ?? "-"} + ${formatTime(extraUserEntry.time)} min + ${extraUserEntry.score ?? "-"} + `; + + tableBody.appendChild(userRow); + } +} + +async function loadTopTenLeaderboard() { + const leaderboardService = new window.LeaderboardService(window.config); + const result = await leaderboardService.getLeaderboard(0, 10); + + if (!result.ok || !Array.isArray(result.body)) { + renderLeaderboard([]); + return; + } + + const auth = getLoggedInAuth(); + let extraUserEntry = null; + + if (auth && auth.username) { + const loggedInUsername = normalizeUsername(auth.username); + // Falls der Nutzer nicht in den Top-10 erscheint, wird sein bestes Ergebnis separat gezeigt. + const isInTopTen = result.body.some( + (entry) => normalizeUsername(entry.username) === loggedInUsername, + ); + + if (!isInTopTen) { + extraUserEntry = await getCurrentUserLeaderboardEntry(auth.username); + } + } + + renderLeaderboard(result.body, extraUserEntry); +} + +window.initLeaderboardPage = function initLeaderboardPage() { + loadTopTenLeaderboard().catch((error) => { + console.error("Fehler beim Laden des Leaderboards:", error); + renderLeaderboard([]); + }); +}; diff --git a/js/navigation.js b/js/navigation.js index 6111ac0..169c63a 100644 --- a/js/navigation.js +++ b/js/navigation.js @@ -23,6 +23,9 @@ document.addEventListener("DOMContentLoaded", () => { if (page === "login" && typeof window.initLoginPage === "function") { window.initLoginPage(); } + if (page === "leaderboard" && typeof window.initLeaderboardPage === "function") { + window.initLeaderboardPage(); + } }) .catch(error => { console.error("Fehler beim Laden von " + page + ":", error); diff --git a/pages/leaderboard.html b/pages/leaderboard.html index 7fbb136..99e3973 100644 --- a/pages/leaderboard.html +++ b/pages/leaderboard.html @@ -1,5 +1,19 @@ +

Leaderboard

-

Hier kannst du die besten Spieler und ihre Scores sehen.

+ + + + + + + + + + + + + +
RankUsertimeScore
From 40e61c16a2cad6885eb61849b4243c0105d9c3a1 Mon Sep 17 00:00:00 2001 From: DST81 Date: Sun, 10 May 2026 23:54:07 +0200 Subject: [PATCH 2/5] Add Impressum --- index.html | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/index.html b/index.html index d9b72dd..0a3b273 100644 --- a/index.html +++ b/index.html @@ -51,6 +51,68 @@ + +
+
+ +
+
+
Made with und zu viel Kaffee für das Modul Frontend. Bootstrap 5.3.8 powered.
+

+ „Unser Impressum ist länger als der Text, den Sie sich merken müssen.“ +

+
+
+ + +
+
+ + Impressum + + +
+
+ + +
+ Betreiber: Das Team FAD (Florin, Adi, Daniela) +
+ + +
+ Adresse: Irgendwo im Internet, Schweiz +
+ + +
+ Kontakt: schreibuns@lorem-ipsum-spiel.ch +
+ + +
+ Inhalt: Der letzte Committer +
+ + +
+ Datenschutz: Wir speichern nur Scores. +
+ +
+
+ + +
+

+ © 2026 Modul Frontend Projekt. Alle Rechte vorbehalten. +

+
+
+
+
+
+ From 90ab402ca6abd3a1ff5ae452ee54c498e73dabd6 Mon Sep 17 00:00:00 2001 From: DST81 Date: Wed, 13 May 2026 10:22:06 +0200 Subject: [PATCH 3/5] Add play.js --- index.html | 140 ++++++++++++++++++++++-------------------- js/play.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ pages/home.html | 8 +-- pages/play.html | 102 +++++++++++++++++++++++++++++-- 4 files changed, 333 insertions(+), 74 deletions(-) create mode 100644 js/play.js diff --git a/index.html b/index.html index eba6f66..8378417 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,41 @@ - Lorem Ipsum - Das Spiel + Lorem Ipsum - Das Spiel + + + - + + +
- +
- +

Dashboard

-
-
-
+
+ +
- -
-
- -
-
-
Made with und zu viel Kaffee für das Modul Frontend. Bootstrap 5.3.8 powered.
-

- „Unser Impressum ist länger als der Text, den Sie sich merken müssen.“ -

-
-
- - -
-
- - Impressum - - -
-
- - -
- Betreiber: Das Team FAD (Florin, Adi, Daniela) + +
+
+ +
+
+
Made with und zu viel Kaffee für das Modul Frontend. Bootstrap 5.3.8 powered.
+

+ „Unser Impressum ist länger als der Text, den Sie sich merken müssen.“ +

+
- -
- Adresse: Irgendwo im Internet, Schweiz + +
+
+ Impressum +
+
+
+ Betreiber: Das Team FAD (Florin, Adi, Daniela) +
+
+ Adresse: Irgendwo im Internet, Schweiz +
+
+ Kontakt: schreibuns@lorem-ipsum-spiel.ch +
+
+ Inhalt: Der letzte Committer +
+
+ Datenschutz: Wir speichern nur Scores. +
+
+
+
+

+ © 2026 Modul Frontend Projekt. Alle Rechte vorbehalten. +

+
- - -
- Kontakt: schreibuns@lorem-ipsum-spiel.ch -
- - -
- Inhalt: Der letzte Committer -
- - -
- Datenschutz: Wir speichern nur Scores. -
-
- - -
-

- © 2026 Modul Frontend Projekt. Alle Rechte vorbehalten. -

-
-
+
-
@@ -122,7 +134,7 @@ + - \ No newline at end of file diff --git a/js/play.js b/js/play.js new file mode 100644 index 0000000..2e4d54e --- /dev/null +++ b/js/play.js @@ -0,0 +1,157 @@ +(function() { + // --- Konfiguration --- + const GAME_TEXT = "Der schnelle braune Fuchs springt über den faulen Hund. Programmieren ist wie Zaubern, nur mit mehr Kaffee und weniger Magie. Wer rastet, der rostet, aber wer codet, der lebt."; + const MEMORIZE_TIME_SECONDS = 15; + + let timerInterval; + let currentTime = 0; + + // --- DOM Elemente holen --- + const phaseStart = document.getElementById('phaseStart'); + const phaseMemorize = document.getElementById('phaseMemorize'); + const phaseInput = document.getElementById('phaseInput'); + const phaseResult = document.getElementById('phaseResult'); + + const targetTextDisplay = document.getElementById('targetTextDisplay'); + const timerDisplay = document.getElementById('timerDisplay'); + const userTextInput = document.getElementById('userTextInput'); + const resultScore = document.getElementById('resultScore'); + const resultOriginal = document.getElementById('resultOriginal'); + const resultInput = document.getElementById('resultInput'); + const gameStatus = document.getElementById('gameStatus'); + + // Buttons + const btnStart = document.getElementById('btnStartGame'); + const btnSubmit = document.getElementById('btnSubmitScore'); + const btnRestart = document.getElementById('btnRestart'); + const btnLeaderboard = document.getElementById('btnLeaderboard'); + + // --- Event Listener registrieren --- + if (btnStart) btnStart.addEventListener('click', startGame); + if (btnSubmit) btnSubmit.addEventListener('click', submitScore); + if (btnRestart) btnRestart.addEventListener('click', () => location.reload()); + if (btnLeaderboard) btnLeaderboard.addEventListener('click', () => { + const navLink = document.getElementById('nav-leaderboard'); + if (navLink) { + navLink.click(); // Simuliert Klick auf Sidebar-Link + } else { + console.warn("Sidebar Link #nav-leaderboard nicht gefunden."); + } + }); + + // --- Funktionen --- + + function startGame() { + if (!phaseStart || !phaseMemorize) return; + + // UI umschalten + phaseStart.classList.add('d-none'); + phaseMemorize.classList.remove('d-none'); + + if(gameStatus) { + gameStatus.textContent = "Lernphase"; + gameStatus.className = "badge fs-6 px-3 py-2"; + gameStatus.style.backgroundColor = "#ffd166"; + gameStatus.style.color = "#1b1b2f"; + } + + if(targetTextDisplay) targetTextDisplay.textContent = GAME_TEXT; + + // Timer starten + currentTime = MEMORIZE_TIME_SECONDS; + if(timerDisplay) timerDisplay.textContent = currentTime; + + timerInterval = setInterval(() => { + currentTime--; + if(timerDisplay) timerDisplay.textContent = currentTime; + + if (currentTime <= 0) { + endMemorizePhase(); + } + }, 1000); + } + + function endMemorizePhase() { + clearInterval(timerInterval); + + if (!phaseMemorize || !phaseInput) return; + + // UI umschalten + phaseMemorize.classList.add('d-none'); + phaseInput.classList.remove('d-none'); + + if(gameStatus) { + gameStatus.textContent = "Eingabe"; + gameStatus.className = "badge fs-6 px-3 py-2"; + gameStatus.style.backgroundColor = "#4a6fa5"; + gameStatus.style.color = "#fff"; + } + + if(userTextInput) { + userTextInput.value = ""; + userTextInput.focus(); + } + } + + function calculateScore(original, input) { + if (!original || !input) return 0; + + // Bereinigung: Kleinbuchstaben, Satzzeichen entfernen, in Arrays aufteilen + const cleanOriginal = original.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").split(/\s+/).filter(w => w.length > 0); + const cleanInput = input.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").split(/\s+/).filter(w => w.length > 0); + + let correctWords = 0; + const limit = Math.min(cleanOriginal.length, cleanInput.length); + + for (let i = 0; i < limit; i++) { + if (cleanInput[i] === cleanOriginal[i]) { + correctWords++; + } + } + + return correctWords; + } + + function submitScore() { + if (!userTextInput) return; + + const userInput = userTextInput.value.trim(); + if (!userInput) { + alert("Bitte geben Sie einen Text ein."); + return; + } + + const score = calculateScore(GAME_TEXT, userInput); + + // Ergebnis anzeigen + if (phaseInput) phaseInput.classList.add('d-none'); + if (phaseResult) phaseResult.classList.remove('d-none'); + + if(gameStatus) { + gameStatus.textContent = "Abgeschlossen"; + gameStatus.className = "badge fs-6 px-3 py-2"; + gameStatus.style.backgroundColor = "#28a745"; + gameStatus.style.color = "#fff"; + } + + if (resultScore) resultScore.textContent = score; + if (resultOriginal) resultOriginal.textContent = GAME_TEXT; + if (resultInput) resultInput.textContent = userInput; + + // --- API Aufruf vorbereiten --- + const scoreData = { + score: score, + time: MEMORIZE_TIME_SECONDS, + text: GAME_TEXT, + userWrittenText: userInput + }; + + console.log("Score bereit zum Senden:", scoreData); + + // HIER Ihren Service Aufruf einfügen, z.B.: + // if (typeof window.ScoreService !== 'undefined') { + // window.ScoreService.post(scoreData); + // } + } + +})(); \ No newline at end of file diff --git a/pages/home.html b/pages/home.html index 731056c..a21a272 100644 --- a/pages/home.html +++ b/pages/home.html @@ -1,8 +1,8 @@
-

Willkommen beim Lorem Ipsum Game

-

Teste deine Fähigkeiten im Umgang mit Lorem Ipsum Texten! Je schneller und genauer du bist, desto höher ist dein Score. +

Willkommen beim Lorem Ipsum Game

+

Teste deine Fähigkeiten im Umgang mit Lorem Ipsum Texten! Je schneller und genauer du bist, desto höher ist dein Score. Viel Spaß beim Spielen!

-

Wähle eine Option aus der Navigation, um zu starten.

- Lorem Ipsum Game +

Wähle eine Option aus der Navigation, um zu starten.

+ Lorem Ipsum Game
\ No newline at end of file diff --git a/pages/play.html b/pages/play.html index a28877a..4e32687 100644 --- a/pages/play.html +++ b/pages/play.html @@ -1,7 +1,97 @@ -
-

Spiel Starten

-

Hier kannst du das Spiel starten. Viel Erfolg!

+ +
+ + +
+
+

Memorize Challenge

+

Merken Sie sich den Text so gut wie möglich.

+
+
+ Bereit +
+
+ + +
+
+

Sind Sie bereit?

+

+ Sie sehen gleich einen Text für 15 Sekunden.
+ Versuchen Sie, sich so viele Wörter wie möglich zu merken! +

+ +
+
+ + +
+
+
+

Lernphase läuft...

+

+ +

+
+ + Verbleibende Zeit: 15s + +
+
+
+
+ + +
+
+
+ + +
+ +
+
+
+
+ + +
+
+
+

Ergebnis

+
0
+

Punkte (korrekte Wörter an der richtigen Position)

+ +
+
+
+ Originaltext +

+
+
+
+
+ Ihre Eingabe +

+
+
+
+ +
+ + +
+
+
+
+ +
- - -
\ No newline at end of file From 66cee7e5e25c0cb08aa67cf512e6833862cfdb3f Mon Sep 17 00:00:00 2001 From: DST81 Date: Wed, 13 May 2026 10:26:59 +0200 Subject: [PATCH 4/5] Add branch leaderboard to impressum --- index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.html b/index.html index 8378417..f0ff6d1 100644 --- a/index.html +++ b/index.html @@ -132,8 +132,11 @@ +<<<<<<< Updated upstream +======= +>>>>>>> Stashed changes From d91d42f7594e11d9b1336ba3023fbc216a9a914c Mon Sep 17 00:00:00 2001 From: DST81 Date: Sat, 16 May 2026 17:26:49 +0200 Subject: [PATCH 5/5] Add Spiellogik in play.html und play.js --- assets/css/custom.css | 6 +- index.html | 9 +- js/login.js | 27 +++- js/navigation.js | 5 +- js/play.js | 326 +++++++++++++++++++++++++++++++++++------- pages/home.html | 3 +- pages/play.html | 16 ++- 7 files changed, 319 insertions(+), 73 deletions(-) diff --git a/assets/css/custom.css b/assets/css/custom.css index 9536f2c..0dbb321 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -137,9 +137,9 @@ p { border-radius: 6px; padding: 5px 10px; } -#logo-img { - max-width: 500px; - width: 100%; +#logo_img { + max-width: 300px; + width: 50%; height: auto; } #main-area { diff --git a/index.html b/index.html index f0ff6d1..9c84e94 100644 --- a/index.html +++ b/index.html @@ -74,7 +74,7 @@
- +
@@ -82,8 +82,8 @@
-
-
Made with und zu viel Kaffee für das Modul Frontend. Bootstrap 5.3.8 powered.
+
Made with für das Modul Frontend +
Made with Bootstrap 5.3.8.

„Unser Impressum ist länger als der Text, den Sie sich merken müssen.“

@@ -132,11 +132,8 @@ -<<<<<<< Updated upstream -======= ->>>>>>> Stashed changes diff --git a/js/login.js b/js/login.js index 73aaf38..03d97ed 100644 --- a/js/login.js +++ b/js/login.js @@ -71,7 +71,7 @@ logoutButton.disabled = false; deleteAccountButton.disabled = false; currentSessionBox.classList.remove("d-none"); - authFormsRow.classList.add("d-none"); + authFormsRow.classList.remove("d-none"); } else { sessionText.textContent = "Nicht eingeloggt."; logoutButton.disabled = true; @@ -100,6 +100,7 @@ const usernameInput = document.getElementById("login-username"); const passwordInput = document.getElementById("login-password"); + const submitButton = event.submitter; const username = usernameInput.value.trim(); const password = passwordInput.value.trim(); @@ -108,7 +109,29 @@ return; } - const result = await userService.getUser(username, password); + if (submitButton) { + submitButton.disabled = true; + submitButton.textContent = "Einloggen..."; + } + + let result; + try { + result = await userService.getUser(username, password); + } catch (error) { + console.error("Login fehlgeschlagen:", error); + setFeedback("Login fehlgeschlagen: Backend ist nicht erreichbar.", "danger"); + if (submitButton) { + submitButton.disabled = false; + submitButton.textContent = "Einloggen"; + } + return; + } + + if (submitButton) { + submitButton.disabled = false; + submitButton.textContent = "Einloggen"; + } + if (result.ok) { saveAuth(username, password); setFeedback("Login erfolgreich.", "success"); diff --git a/js/navigation.js b/js/navigation.js index 169c63a..622579e 100644 --- a/js/navigation.js +++ b/js/navigation.js @@ -26,6 +26,9 @@ document.addEventListener("DOMContentLoaded", () => { if (page === "leaderboard" && typeof window.initLeaderboardPage === "function") { window.initLeaderboardPage(); } + if (page === "play" && typeof window.initPlayPage === "function") { + window.initPlayPage(); + } }) .catch(error => { console.error("Fehler beim Laden von " + page + ":", error); @@ -49,4 +52,4 @@ document.addEventListener("DOMContentLoaded", () => { //Startseite laden loadPage("home", "nav-home"); -}); \ No newline at end of file +}); diff --git a/js/play.js b/js/play.js index 2e4d54e..a73dd06 100644 --- a/js/play.js +++ b/js/play.js @@ -1,50 +1,117 @@ (function() { // --- Konfiguration --- - const GAME_TEXT = "Der schnelle braune Fuchs springt über den faulen Hund. Programmieren ist wie Zaubern, nur mit mehr Kaffee und weniger Magie. Wer rastet, der rostet, aber wer codet, der lebt."; const MEMORIZE_TIME_SECONDS = 15; + // Bausteine fuer den zufaelligen Rundentext. Alles bleibt lokal, damit das Spiel ohne Backend starten kann. + const TEXT_PARTS = { + subjects: [ + "Der flinke Entwickler", + "Die neugierige Studentin", + "Ein mueder Professor", + "Das kleine Frontend", + "Der mutige Browser", + "Eine schlaue Funktion", + "Der vergessliche Server", + "Die kreative Gruppe" + ], + actions: [ + "sortiert leise", + "debuggt geduldig", + "vergleicht heimlich", + "speichert vorsichtig", + "rendert ploetzlich", + "zaehlt konzentriert", + "testet neugierig", + "kompiliert langsam" + ], + objects: [ + "sieben blaue Buttons", + "drei lange Variablen", + "neun goldene Woerter", + "vier kaputte Formulare", + "acht schnelle Requests", + "zwei leuchtende Karten", + "fuenf stille Fehlermeldungen", + "sechs winzige Icons" + ], + places: [ + "im hellen Dashboard", + "unter dem dunklen Navbar", + "neben dem alten Footer", + "zwischen Login und Leaderboard", + "vor dem ersten Kaffee", + "waehrend der Lernphase", + "hinter dem lokalen Server", + "mitten im Semesterprojekt" + ], + endings: [ + "Danach lacht der Code, weil alles endlich funktioniert.", + "Am Ende merkt sich niemand die Semikolons, aber alle die Punkte.", + "Kurz darauf blinkt die Konsole und behauptet, sie sei unschuldig.", + "Spaeter landet der Score im Ranking und wartet auf Applaus.", + "Dabei bleibt die Seite ruhig, obwohl der Timer dramatisch tickt.", + "Zum Schluss gewinnt, wer die Woerter sauber in Reihenfolge bringt." + ] + }; + let timerInterval; let currentTime = 0; - // --- DOM Elemente holen --- - const phaseStart = document.getElementById('phaseStart'); - const phaseMemorize = document.getElementById('phaseMemorize'); - const phaseInput = document.getElementById('phaseInput'); - const phaseResult = document.getElementById('phaseResult'); - - const targetTextDisplay = document.getElementById('targetTextDisplay'); - const timerDisplay = document.getElementById('timerDisplay'); - const userTextInput = document.getElementById('userTextInput'); - const resultScore = document.getElementById('resultScore'); - const resultOriginal = document.getElementById('resultOriginal'); - const resultInput = document.getElementById('resultInput'); - const gameStatus = document.getElementById('gameStatus'); + // Der aktuell angezeigte Text muss bis zur Auswertung stabil bleiben. + let currentGameText = ""; + let lastGeneratedText = ""; - // Buttons - const btnStart = document.getElementById('btnStartGame'); - const btnSubmit = document.getElementById('btnSubmitScore'); - const btnRestart = document.getElementById('btnRestart'); - const btnLeaderboard = document.getElementById('btnLeaderboard'); - - // --- Event Listener registrieren --- - if (btnStart) btnStart.addEventListener('click', startGame); - if (btnSubmit) btnSubmit.addEventListener('click', submitScore); - if (btnRestart) btnRestart.addEventListener('click', () => location.reload()); - if (btnLeaderboard) btnLeaderboard.addEventListener('click', () => { - const navLink = document.getElementById('nav-leaderboard'); - if (navLink) { - navLink.click(); // Simuliert Klick auf Sidebar-Link - } else { - console.warn("Sidebar Link #nav-leaderboard nicht gefunden."); - } - }); + // DOM-Referenzen werden erst gesetzt, nachdem pages/play.html dynamisch geladen wurde. + let phaseStart; + let phaseMemorize; + let phaseInput; + let phaseResult; + let targetTextDisplay; + let timerDisplay; + let userTextInput; + let resultScore; + let resultOriginal; + let resultInput; + let gameStatus; + let scoreSaveFeedback; + let btnSubmitScore; // --- Funktionen --- + function getRandomItem(items) { + return items[Math.floor(Math.random() * items.length)]; + } + + // Erstellt pro Runde zwei zufaellige Saetze plus Schluss-Satz und vermeidet direkte Wiederholungen. + function generateGameText() { + let generatedText = ""; + + do { + const firstSentence = [ + getRandomItem(TEXT_PARTS.subjects), + getRandomItem(TEXT_PARTS.actions), + getRandomItem(TEXT_PARTS.objects), + getRandomItem(TEXT_PARTS.places) + ].join(" ") + "."; + + const secondSentence = [ + getRandomItem(TEXT_PARTS.subjects), + getRandomItem(TEXT_PARTS.actions), + getRandomItem(TEXT_PARTS.objects), + getRandomItem(TEXT_PARTS.places) + ].join(" ") + "."; + + generatedText = firstSentence + " " + secondSentence + " " + getRandomItem(TEXT_PARTS.endings); + } while (generatedText === lastGeneratedText); + + lastGeneratedText = generatedText; + return generatedText; + } + function startGame() { if (!phaseStart || !phaseMemorize) return; - // UI umschalten + // Startansicht ausblenden und den neu generierten Text fuer die Lernphase anzeigen. phaseStart.classList.add('d-none'); phaseMemorize.classList.remove('d-none'); @@ -55,9 +122,10 @@ gameStatus.style.color = "#1b1b2f"; } - if(targetTextDisplay) targetTextDisplay.textContent = GAME_TEXT; + currentGameText = generateGameText(); + if(targetTextDisplay) targetTextDisplay.textContent = currentGameText; - // Timer starten + // Nach Ablauf des Timers wird automatisch zur Eingabe gewechselt. currentTime = MEMORIZE_TIME_SECONDS; if(timerDisplay) timerDisplay.textContent = currentTime; @@ -76,7 +144,7 @@ if (!phaseMemorize || !phaseInput) return; - // UI umschalten + // Text verschwindet, Eingabefeld erscheint: ab hier zaehlt nur noch das Gedaechtnis. phaseMemorize.classList.add('d-none'); phaseInput.classList.remove('d-none'); @@ -93,12 +161,22 @@ } } + // Entfernt alles, was beim Vergleichen nicht zaehlen soll. + function normalizeWord(word) { + return word.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, ""); + } + + // Behält die sichtbaren Woerter separat, damit Satzzeichen in der Ergebnisanzeige erhalten bleiben. + function getWords(text) { + return text.split(/\s+/).filter(word => word.length > 0); + } + function calculateScore(original, input) { if (!original || !input) return 0; - // Bereinigung: Kleinbuchstaben, Satzzeichen entfernen, in Arrays aufteilen - const cleanOriginal = original.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").split(/\s+/).filter(w => w.length > 0); - const cleanInput = input.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").split(/\s+/).filter(w => w.length > 0); + // Score-Regel: gleiche Woerter an gleicher Position, Satzzeichen und Grossschreibung ignoriert. + const cleanOriginal = getWords(original).map(normalizeWord).filter(word => word.length > 0); + const cleanInput = getWords(input).map(normalizeWord).filter(word => word.length > 0); let correctWords = 0; const limit = Math.min(cleanOriginal.length, cleanInput.length); @@ -112,7 +190,104 @@ return correctWords; } - function submitScore() { + // Baut ein einzelnes farbiges Wort-Label fuer den Ergebnisvergleich. + function createWordBadge(word, isCorrect) { + const badge = document.createElement("span"); + badge.className = "badge me-1 mb-1 " + (isCorrect ? "text-bg-success" : "text-bg-danger"); + badge.textContent = word; + return badge; + } + + // Markiert Original und Eingabe nach derselben Positionslogik wie calculateScore(). + function renderWordComparison(original, input) { + if (!resultOriginal || !resultInput) return; + + const originalWords = getWords(original); + const inputWords = getWords(input); + + resultOriginal.innerHTML = ""; + resultInput.innerHTML = ""; + + // Original: rot, wenn das eingegebene Wort an dieser Position fehlt oder falsch ist. + originalWords.forEach((word, index) => { + const isCorrect = normalizeWord(word) === normalizeWord(inputWords[index] || ""); + resultOriginal.appendChild(createWordBadge(word, isCorrect)); + }); + + // Eingabe: rot, wenn das Wort nicht zum Originalwort an derselben Position passt. + inputWords.forEach((word, index) => { + const isCorrect = normalizeWord(word) === normalizeWord(originalWords[index] || ""); + resultInput.appendChild(createWordBadge(word, isCorrect)); + }); + } + + function getAuth() { + if (!window.AppAuth || typeof window.AppAuth.getAuth !== "function") { + return null; + } + + return window.AppAuth.getAuth(); + } + + function getScoreService() { + if (!window.config || !window.ScoreService) { + return null; + } + + return new window.ScoreService(window.config); + } + + function showScoreSaveFeedback(message, type) { + if (!scoreSaveFeedback) return; + + scoreSaveFeedback.className = "alert alert-" + type + " mb-4"; + scoreSaveFeedback.textContent = message; + scoreSaveFeedback.classList.remove("d-none"); + } + + async function saveScore(scoreData) { + const auth = getAuth(); + if (!auth || !auth.username || !auth.password) { + showScoreSaveFeedback( + "Score wurde nur lokal berechnet. Bitte einloggen, damit er im Leaderboard gespeichert wird.", + "warning" + ); + return; + } + + // Auth-Daten kommen aus login.js; der ScoreService setzt daraus die Backend-Header. + const scoreService = getScoreService(); + if (!scoreService) { + showScoreSaveFeedback("Score-Service konnte nicht geladen werden.", "danger"); + return; + } + + showScoreSaveFeedback("Score wird gespeichert...", "info"); + + const result = await scoreService.postScore( + auth.username, + auth.password, + scoreData.score, + scoreData.time, + scoreData.text, + scoreData.userWrittenText + ); + + if (result.ok) { + const place = result.body && result.body.place ? " Platz " + result.body.place + "." : ""; + showScoreSaveFeedback("Score erfolgreich gespeichert." + place, "success"); + return; + } + + if (result.status === 401) { + showScoreSaveFeedback("Score konnte nicht gespeichert werden: Login ist nicht gültig.", "danger"); + return; + } + + showScoreSaveFeedback("Score konnte nicht gespeichert werden (Status " + result.status + ").", "danger"); + } + + async function submitScore() { if (!userTextInput) return; const userInput = userTextInput.value.trim(); @@ -121,9 +296,14 @@ return; } - const score = calculateScore(GAME_TEXT, userInput); + if (btnSubmitScore) { + btnSubmitScore.disabled = true; + btnSubmitScore.textContent = "Wird ausgewertet..."; + } + + const score = calculateScore(currentGameText, userInput); - // Ergebnis anzeigen + // Ergebnis sofort anzeigen; das Speichern im Backend passiert danach asynchron. if (phaseInput) phaseInput.classList.add('d-none'); if (phaseResult) phaseResult.classList.remove('d-none'); @@ -135,23 +315,65 @@ } if (resultScore) resultScore.textContent = score; - if (resultOriginal) resultOriginal.textContent = GAME_TEXT; - if (resultInput) resultInput.textContent = userInput; + renderWordComparison(currentGameText, userInput); - // --- API Aufruf vorbereiten --- + // Genau dieser Rundentext wird gespeichert, damit Leaderboard/Score-Details nachvollziehbar bleiben. const scoreData = { score: score, time: MEMORIZE_TIME_SECONDS, - text: GAME_TEXT, + text: currentGameText, userWrittenText: userInput }; console.log("Score bereit zum Senden:", scoreData); - - // HIER Ihren Service Aufruf einfügen, z.B.: - // if (typeof window.ScoreService !== 'undefined') { - // window.ScoreService.post(scoreData); - // } + + try { + await saveScore(scoreData); + } catch (error) { + console.error("Fehler beim Speichern des Scores:", error); + showScoreSaveFeedback("Score konnte wegen eines technischen Fehlers nicht gespeichert werden.", "danger"); + } finally { + if (btnSubmitScore) { + btnSubmitScore.disabled = false; + btnSubmitScore.textContent = "Auswerten & Absenden"; + } + } } -})(); \ No newline at end of file + window.initPlayPage = function initPlayPage() { + clearInterval(timerInterval); + + // Die Navigation laedt play.html per fetch; deshalb werden die Elemente erst hier gesucht. + phaseStart = document.getElementById('phaseStart'); + phaseMemorize = document.getElementById('phaseMemorize'); + phaseInput = document.getElementById('phaseInput'); + phaseResult = document.getElementById('phaseResult'); + + targetTextDisplay = document.getElementById('targetTextDisplay'); + timerDisplay = document.getElementById('timerDisplay'); + userTextInput = document.getElementById('userTextInput'); + resultScore = document.getElementById('resultScore'); + resultOriginal = document.getElementById('resultOriginal'); + resultInput = document.getElementById('resultInput'); + gameStatus = document.getElementById('gameStatus'); + scoreSaveFeedback = document.getElementById('scoreSaveFeedback'); + + const btnStart = document.getElementById('btnStartGame'); + btnSubmitScore = document.getElementById('btnSubmitScore'); + const btnRestart = document.getElementById('btnRestart'); + const btnLeaderboard = document.getElementById('btnLeaderboard'); + + if (btnStart) btnStart.addEventListener('click', startGame); + if (btnSubmitScore) btnSubmitScore.addEventListener('click', submitScore); + if (btnRestart) btnRestart.addEventListener('click', () => window.loadPage("play", "nav-play")); + if (btnLeaderboard) btnLeaderboard.addEventListener('click', () => { + const navLink = document.getElementById('nav-leaderboard'); + if (navLink) { + navLink.click(); // Nutzt die bestehende Navigation inklusive Active-State. + } else { + console.warn("Sidebar Link #nav-leaderboard nicht gefunden."); + } + }); + }; + +})(); diff --git a/pages/home.html b/pages/home.html index a21a272..585c591 100644 --- a/pages/home.html +++ b/pages/home.html @@ -2,7 +2,6 @@

Willkommen beim Lorem Ipsum Game

Teste deine Fähigkeiten im Umgang mit Lorem Ipsum Texten! Je schneller und genauer du bist, desto höher ist dein Score. Viel Spaß beim Spielen!

-

Wähle eine Option aus der Navigation, um zu starten.

- Lorem Ipsum Game + Lorem Ipsum Game
\ No newline at end of file diff --git a/pages/play.html b/pages/play.html index 4e32687..6d7f4d8 100644 --- a/pages/play.html +++ b/pages/play.html @@ -1,10 +1,10 @@ - +
- +
-

Memorize Challenge

+

Lorem Ipsum - Challenge you brain

Merken Sie sich den Text so gut wie möglich.

@@ -12,7 +12,7 @@
- +

Sind Sie bereit?

@@ -26,7 +26,7 @@
- +
@@ -43,7 +43,7 @@
- +
@@ -58,13 +58,15 @@
- +

Ergebnis

0

Punkte (korrekte Wörter an der richtigen Position)

+ +