add prevention of ctrl+c ctrl + v

This commit is contained in:
Adrian Joost 2026-05-18 14:30:41 +02:00
parent ad64bae67b
commit 82f8e46cde

View File

@ -12,7 +12,7 @@
"Der mutige Browser", "Der mutige Browser",
"Eine schlaue Funktion", "Eine schlaue Funktion",
"Der vergessliche Server", "Der vergessliche Server",
"Die kreative Gruppe" "Die kreative Gruppe",
], ],
actions: [ actions: [
"sortiert leise", "sortiert leise",
@ -22,7 +22,7 @@
"rendert ploetzlich", "rendert ploetzlich",
"zaehlt konzentriert", "zaehlt konzentriert",
"testet neugierig", "testet neugierig",
"kompiliert langsam" "kompiliert langsam",
], ],
objects: [ objects: [
"sieben blaue Buttons", "sieben blaue Buttons",
@ -32,7 +32,7 @@
"acht schnelle Requests", "acht schnelle Requests",
"zwei leuchtende Karten", "zwei leuchtende Karten",
"fuenf stille Fehlermeldungen", "fuenf stille Fehlermeldungen",
"sechs winzige Icons" "sechs winzige Icons",
], ],
places: [ places: [
"im hellen Dashboard", "im hellen Dashboard",
@ -42,7 +42,7 @@
"vor dem ersten Kaffee", "vor dem ersten Kaffee",
"waehrend der Lernphase", "waehrend der Lernphase",
"hinter dem lokalen Server", "hinter dem lokalen Server",
"mitten im Semesterprojekt" "mitten im Semesterprojekt",
], ],
endings: [ endings: [
"Danach lacht der Code, weil alles endlich funktioniert.", "Danach lacht der Code, weil alles endlich funktioniert.",
@ -50,8 +50,8 @@
"Kurz darauf blinkt die Konsole und behauptet, sie sei unschuldig.", "Kurz darauf blinkt die Konsole und behauptet, sie sei unschuldig.",
"Spaeter landet der Score im Ranking und wartet auf Applaus.", "Spaeter landet der Score im Ranking und wartet auf Applaus.",
"Dabei bleibt die Seite ruhig, obwohl der Timer dramatisch tickt.", "Dabei bleibt die Seite ruhig, obwohl der Timer dramatisch tickt.",
"Zum Schluss gewinnt, wer die Woerter sauber in Reihenfolge bringt." "Zum Schluss gewinnt, wer die Woerter sauber in Reihenfolge bringt.",
] ],
}; };
let timerInterval; let timerInterval;
@ -87,21 +87,28 @@
let generatedText = ""; let generatedText = "";
do { do {
const firstSentence = [ const firstSentence =
[
getRandomItem(TEXT_PARTS.subjects), getRandomItem(TEXT_PARTS.subjects),
getRandomItem(TEXT_PARTS.actions), getRandomItem(TEXT_PARTS.actions),
getRandomItem(TEXT_PARTS.objects), getRandomItem(TEXT_PARTS.objects),
getRandomItem(TEXT_PARTS.places) getRandomItem(TEXT_PARTS.places),
].join(" ") + "."; ].join(" ") + ".";
const secondSentence = [ const secondSentence =
[
getRandomItem(TEXT_PARTS.subjects), getRandomItem(TEXT_PARTS.subjects),
getRandomItem(TEXT_PARTS.actions), getRandomItem(TEXT_PARTS.actions),
getRandomItem(TEXT_PARTS.objects), getRandomItem(TEXT_PARTS.objects),
getRandomItem(TEXT_PARTS.places) getRandomItem(TEXT_PARTS.places),
].join(" ") + "."; ].join(" ") + ".";
generatedText = firstSentence + " " + secondSentence + " " + getRandomItem(TEXT_PARTS.endings); generatedText =
firstSentence +
" " +
secondSentence +
" " +
getRandomItem(TEXT_PARTS.endings);
} while (generatedText === lastGeneratedText); } while (generatedText === lastGeneratedText);
lastGeneratedText = generatedText; lastGeneratedText = generatedText;
@ -112,8 +119,8 @@
if (!phaseStart || !phaseMemorize) return; if (!phaseStart || !phaseMemorize) return;
// Startansicht ausblenden und den neu generierten Text fuer die Lernphase anzeigen. // Startansicht ausblenden und den neu generierten Text fuer die Lernphase anzeigen.
phaseStart.classList.add('d-none'); phaseStart.classList.add("d-none");
phaseMemorize.classList.remove('d-none'); phaseMemorize.classList.remove("d-none");
if (gameStatus) { if (gameStatus) {
gameStatus.textContent = "Lernphase"; gameStatus.textContent = "Lernphase";
@ -145,8 +152,8 @@
if (!phaseMemorize || !phaseInput) return; if (!phaseMemorize || !phaseInput) return;
// Text verschwindet, Eingabefeld erscheint: ab hier zaehlt nur noch das Gedaechtnis. // Text verschwindet, Eingabefeld erscheint: ab hier zaehlt nur noch das Gedaechtnis.
phaseMemorize.classList.add('d-none'); phaseMemorize.classList.add("d-none");
phaseInput.classList.remove('d-none'); phaseInput.classList.remove("d-none");
if (gameStatus) { if (gameStatus) {
gameStatus.textContent = "Eingabe"; gameStatus.textContent = "Eingabe";
@ -168,15 +175,19 @@
// Behält die sichtbaren Woerter separat, damit Satzzeichen in der Ergebnisanzeige erhalten bleiben. // Behält die sichtbaren Woerter separat, damit Satzzeichen in der Ergebnisanzeige erhalten bleiben.
function getWords(text) { function getWords(text) {
return text.split(/\s+/).filter(word => word.length > 0); return text.split(/\s+/).filter((word) => word.length > 0);
} }
function calculateScore(original, input) { function calculateScore(original, input) {
if (!original || !input) return 0; if (!original || !input) return 0;
// Score-Regel: gleiche Woerter an gleicher Position, Satzzeichen und Grossschreibung ignoriert. // Score-Regel: gleiche Woerter an gleicher Position, Satzzeichen und Grossschreibung ignoriert.
const cleanOriginal = getWords(original).map(normalizeWord).filter(word => word.length > 0); const cleanOriginal = getWords(original)
const cleanInput = getWords(input).map(normalizeWord).filter(word => word.length > 0); .map(normalizeWord)
.filter((word) => word.length > 0);
const cleanInput = getWords(input)
.map(normalizeWord)
.filter((word) => word.length > 0);
let correctWords = 0; let correctWords = 0;
const limit = Math.min(cleanOriginal.length, cleanInput.length); const limit = Math.min(cleanOriginal.length, cleanInput.length);
@ -193,7 +204,8 @@
// Baut ein einzelnes farbiges Wort-Label fuer den Ergebnisvergleich. // Baut ein einzelnes farbiges Wort-Label fuer den Ergebnisvergleich.
function createWordBadge(word, isCorrect) { function createWordBadge(word, isCorrect) {
const badge = document.createElement("span"); const badge = document.createElement("span");
badge.className = "badge me-1 mb-1 " + (isCorrect ? "text-bg-success" : "text-bg-danger"); badge.className =
"badge me-1 mb-1 " + (isCorrect ? "text-bg-success" : "text-bg-danger");
badge.textContent = word; badge.textContent = word;
return badge; return badge;
} }
@ -210,13 +222,15 @@
// Original: rot, wenn das eingegebene Wort an dieser Position fehlt oder falsch ist. // Original: rot, wenn das eingegebene Wort an dieser Position fehlt oder falsch ist.
originalWords.forEach((word, index) => { originalWords.forEach((word, index) => {
const isCorrect = normalizeWord(word) === normalizeWord(inputWords[index] || ""); const isCorrect =
normalizeWord(word) === normalizeWord(inputWords[index] || "");
resultOriginal.appendChild(createWordBadge(word, isCorrect)); resultOriginal.appendChild(createWordBadge(word, isCorrect));
}); });
// Eingabe: rot, wenn das Wort nicht zum Originalwort an derselben Position passt. // Eingabe: rot, wenn das Wort nicht zum Originalwort an derselben Position passt.
inputWords.forEach((word, index) => { inputWords.forEach((word, index) => {
const isCorrect = normalizeWord(word) === normalizeWord(originalWords[index] || ""); const isCorrect =
normalizeWord(word) === normalizeWord(originalWords[index] || "");
resultInput.appendChild(createWordBadge(word, isCorrect)); resultInput.appendChild(createWordBadge(word, isCorrect));
}); });
} }
@ -250,7 +264,7 @@
if (!auth || !auth.username || !auth.password) { if (!auth || !auth.username || !auth.password) {
showScoreSaveFeedback( showScoreSaveFeedback(
"Score wurde nur lokal berechnet. Bitte einloggen, damit er im Leaderboard gespeichert wird.", "Score wurde nur lokal berechnet. Bitte einloggen, damit er im Leaderboard gespeichert wird.",
"warning" "warning",
); );
return; return;
} }
@ -258,7 +272,10 @@
// Auth-Daten kommen aus login.js; der ScoreService setzt daraus die Backend-Header. // Auth-Daten kommen aus login.js; der ScoreService setzt daraus die Backend-Header.
const scoreService = getScoreService(); const scoreService = getScoreService();
if (!scoreService) { if (!scoreService) {
showScoreSaveFeedback("Score-Service konnte nicht geladen werden.", "danger"); showScoreSaveFeedback(
"Score-Service konnte nicht geladen werden.",
"danger",
);
return; return;
} }
@ -270,21 +287,33 @@
scoreData.score, scoreData.score,
scoreData.time, scoreData.time,
scoreData.text, scoreData.text,
scoreData.userWrittenText scoreData.userWrittenText,
); );
if (result.ok) { if (result.ok) {
const place = result.body && result.body.place ? " Platz " + result.body.place + "." : ""; const place =
showScoreSaveFeedback("Score erfolgreich gespeichert." + place, "success"); result.body && result.body.place
? " Platz " + result.body.place + "."
: "";
showScoreSaveFeedback(
"Score erfolgreich gespeichert." + place,
"success",
);
return; return;
} }
if (result.status === 401) { if (result.status === 401) {
showScoreSaveFeedback("Score konnte nicht gespeichert werden: Login ist nicht gültig.", "danger"); showScoreSaveFeedback(
"Score konnte nicht gespeichert werden: Login ist nicht gültig.",
"danger",
);
return; return;
} }
showScoreSaveFeedback("Score konnte nicht gespeichert werden (Status " + result.status + ").", "danger"); showScoreSaveFeedback(
"Score konnte nicht gespeichert werden (Status " + result.status + ").",
"danger",
);
} }
async function submitScore() { async function submitScore() {
@ -304,8 +333,8 @@
const score = calculateScore(currentGameText, userInput); const score = calculateScore(currentGameText, userInput);
// Ergebnis sofort anzeigen; das Speichern im Backend passiert danach asynchron. // Ergebnis sofort anzeigen; das Speichern im Backend passiert danach asynchron.
if (phaseInput) phaseInput.classList.add('d-none'); if (phaseInput) phaseInput.classList.add("d-none");
if (phaseResult) phaseResult.classList.remove('d-none'); if (phaseResult) phaseResult.classList.remove("d-none");
if (gameStatus) { if (gameStatus) {
gameStatus.textContent = "Abgeschlossen"; gameStatus.textContent = "Abgeschlossen";
@ -322,7 +351,7 @@
score: score, score: score,
time: MEMORIZE_TIME_SECONDS, time: MEMORIZE_TIME_SECONDS,
text: currentGameText, text: currentGameText,
userWrittenText: userInput userWrittenText: userInput,
}; };
console.log("Score bereit zum Senden:", scoreData); console.log("Score bereit zum Senden:", scoreData);
@ -331,7 +360,10 @@
await saveScore(scoreData); await saveScore(scoreData);
} catch (error) { } catch (error) {
console.error("Fehler beim Speichern des Scores:", error); console.error("Fehler beim Speichern des Scores:", error);
showScoreSaveFeedback("Score konnte wegen eines technischen Fehlers nicht gespeichert werden.", "danger"); showScoreSaveFeedback(
"Score konnte wegen eines technischen Fehlers nicht gespeichert werden.",
"danger",
);
} finally { } finally {
if (btnSubmitScore) { if (btnSubmitScore) {
btnSubmitScore.disabled = false; btnSubmitScore.disabled = false;
@ -344,36 +376,61 @@
clearInterval(timerInterval); clearInterval(timerInterval);
// Die Navigation laedt play.html per fetch; deshalb werden die Elemente erst hier gesucht. // Die Navigation laedt play.html per fetch; deshalb werden die Elemente erst hier gesucht.
phaseStart = document.getElementById('phaseStart'); phaseStart = document.getElementById("phaseStart");
phaseMemorize = document.getElementById('phaseMemorize'); phaseMemorize = document.getElementById("phaseMemorize");
phaseInput = document.getElementById('phaseInput'); phaseInput = document.getElementById("phaseInput");
phaseResult = document.getElementById('phaseResult'); phaseResult = document.getElementById("phaseResult");
targetTextDisplay = document.getElementById('targetTextDisplay'); targetTextDisplay = document.getElementById("targetTextDisplay");
timerDisplay = document.getElementById('timerDisplay'); timerDisplay = document.getElementById("timerDisplay");
userTextInput = document.getElementById('userTextInput'); userTextInput = document.getElementById("userTextInput");
resultScore = document.getElementById('resultScore'); resultScore = document.getElementById("resultScore");
resultOriginal = document.getElementById('resultOriginal'); resultOriginal = document.getElementById("resultOriginal");
resultInput = document.getElementById('resultInput'); resultInput = document.getElementById("resultInput");
gameStatus = document.getElementById('gameStatus'); gameStatus = document.getElementById("gameStatus");
scoreSaveFeedback = document.getElementById('scoreSaveFeedback'); scoreSaveFeedback = document.getElementById("scoreSaveFeedback");
const btnStart = document.getElementById('btnStartGame'); const btnStart = document.getElementById("btnStartGame");
btnSubmitScore = document.getElementById('btnSubmitScore'); btnSubmitScore = document.getElementById("btnSubmitScore");
const btnRestart = document.getElementById('btnRestart'); const btnRestart = document.getElementById("btnRestart");
const btnLeaderboard = document.getElementById('btnLeaderboard'); const btnLeaderboard = document.getElementById("btnLeaderboard");
if (btnStart) btnStart.addEventListener('click', startGame); if (btnStart) btnStart.addEventListener("click", startGame);
if (btnSubmitScore) btnSubmitScore.addEventListener('click', submitScore); if (btnSubmitScore) btnSubmitScore.addEventListener("click", submitScore);
if (btnRestart) btnRestart.addEventListener('click', () => window.loadPage("play", "nav-play")); if (btnRestart)
if (btnLeaderboard) btnLeaderboard.addEventListener('click', () => { btnRestart.addEventListener("click", () =>
const navLink = document.getElementById('nav-leaderboard'); window.loadPage("play", "nav-play"),
);
if (btnLeaderboard)
btnLeaderboard.addEventListener("click", () => {
const navLink = document.getElementById("nav-leaderboard");
if (navLink) { if (navLink) {
navLink.click(); // Nutzt die bestehende Navigation inklusive Active-State. navLink.click(); // Nutzt die bestehende Navigation inklusive Active-State.
} else { } else {
console.warn("Sidebar Link #nav-leaderboard nicht gefunden."); console.warn("Sidebar Link #nav-leaderboard nicht gefunden.");
} }
}); });
};
if (userTextInput) {
userTextInput.addEventListener("keydown", (e) => {
const key = e.key.toLowerCase();
if (
(e.ctrlKey || e.metaKey) &&
(key === "c" || key === "v" || key === "x" || key === "a")
) {
e.preventDefault();
alert("Yeah, cheating is not sooo nice...");
}
});
userTextInput.addEventListener("paste", (e) => e.preventDefault());
userTextInput.addEventListener("copy", (e) => e.preventDefault());
userTextInput.addEventListener("cut", (e) => e.preventDefault());
}
if (targetTextDisplay) {
targetTextDisplay.addEventListener("copy", (e) => e.preventDefault());
targetTextDisplay.style.userSelect = "none";
}
};
})(); })();