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)

+ +