Add Spiellogik in play.html und play.js

This commit is contained in:
DST81 2026-05-16 17:26:49 +02:00
parent 66cee7e5e2
commit d91d42f759
7 changed files with 319 additions and 73 deletions

View File

@ -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 {

View File

@ -74,7 +74,7 @@
<!-- Content -->
<main class="container mt-4" id="main-content">
<!-- Hier wird play.html geladen -->
<!-- Hier werden die pages.html geladen -->
</main>
<!-- Footer: Jetzt INSIDE main-area, damit Flexbox funktioniert -->
@ -82,8 +82,8 @@
<div class="container">
<!-- Oberer Bereich -->
<div class="row mb-3">
<div class="col-12">
<h5 class="text-warning">Made with <span class="text-danger"></span> und zu viel Kaffee für das Modul Frontend. Bootstrap 5.3.8 powered.</h5>
<div class="col-12">Made with <span class="text-danger"></span> für das Modul Frontend
<h5 class="text-warning">Made with Bootstrap 5.3.8.</h5>
<p class="text-black small mb-0 fst-italic">
<em>„Unser Impressum ist länger als der Text, den Sie sich merken müssen.“</em>
</p>
@ -132,11 +132,8 @@
<script src="assets/src/service/score-service.js"></script>
<script src="assets/src/service/leaderboard-service.js"></script>
<script src="js/login.js"></script>
<<<<<<< Updated upstream
<script src="js/leaderboard.js"></script>
<!--Navigation Script -->
=======
>>>>>>> Stashed changes
<script src="js/play.js"></script>
<script src="js/navigation.js"></script>
</body>

View File

@ -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");

View File

@ -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");
});
});

View File

@ -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";
}
}
}
})();
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.");
}
});
};
})();

View File

@ -2,7 +2,6 @@
<h4 class="card-title mb-3">Willkommen beim Lorem Ipsum Game</h2>
<p class="card-text text-uted fs-6 mb-4">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!</p>
<p class="card-text fs-6"> Wähle eine Option aus der Navigation, um zu starten.</p>
<img id="logo_img" src="image/Logo_loremIpsum.png" alt="Lorem Ipsum Game" class="img-fluid w-50 mt-3 d-block mx-auto">
<img id="logo_img" src="image/Logo_loremIpsum.png" alt="Lorem Ipsum Game"class="img-fluid mt-3 d-block mx-auto">
</div>

View File

@ -1,10 +1,10 @@
<!-- Spiel-Wrapper -->
<!-- Spielseite: Die vier Phasen werden per play.js ein- und ausgeblendet. -->
<div class="game-container">
<!-- Header Bereich -->
<!-- Status-Badge zeigt die aktuelle Spielphase: Bereit, Lernphase, Eingabe, Abgeschlossen. -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1">Memorize Challenge</h2>
<h2 class="fw-bold mb-0">Lorem Ipsum - Challenge you brain</h2>
<p class="text-muted mb-0">Merken Sie sich den Text so gut wie möglich.</p>
</div>
<div id="gameStatus" class="badge bg-secondary fs-6 px-3 py-2">
@ -12,7 +12,7 @@
</div>
</div>
<!-- Phase 1: Start -->
<!-- Phase 1: Startbildschirm vor der Textanzeige. -->
<div id="phaseStart" class="card">
<div class="card-body text-center py-5">
<h3 class="mb-3">Sind Sie bereit?</h3>
@ -26,7 +26,7 @@
</div>
</div>
<!-- Phase 2: Lernphase -->
<!-- Phase 2: Merken. Der zufaellige Rundentext wird in #targetTextDisplay eingesetzt. -->
<div id="phaseMemorize" class="d-none">
<div class="card border-warning" style="border-left: 5px solid #ffd166; background-color: #fffbf0;">
<div class="card-body text-center py-4">
@ -43,7 +43,7 @@
</div>
</div>
<!-- Phase 3: Eingabe -->
<!-- Phase 3: Eingabe aus dem Gedaechtnis. Der Originaltext ist hier bewusst ausgeblendet. -->
<div id="phaseInput" class="d-none">
<div class="card">
<div class="card-body">
@ -58,13 +58,15 @@
</div>
</div>
<!-- Phase 4: Ergebnis -->
<!-- Phase 4: Auswertung inklusive Backend-Feedback und Vergleich Original/Eingabe. -->
<div id="phaseResult" class="d-none">
<div class="card text-center" style="border-top: 5px solid #28a745;">
<div class="card-body py-5">
<h3 class="text-success mb-2">Ergebnis</h3>
<div class="display-1 fw-bold my-4" id="resultScore" style="color: #4a6fa5;">0</div>
<p class="text-muted mb-5">Punkte (korrekte Wörter an der richtigen Position)</p>
<!-- Wird von saveScore() befuellt: gespeichert, nur lokal berechnet oder Fehler. -->
<div id="scoreSaveFeedback" class="alert d-none mb-4" role="alert"></div>
<div class="row g-4 text-start mb-5">
<div class="col-md-6">