Compare commits

...

2 Commits

Author SHA1 Message Date
57ae0ccda5 Merge pull request 'add prevention of ctrl+c ctrl + v' (#8) from prohibit-copy into main
Reviewed-on: #8
2026-05-18 14:32:00 +02:00
Adrian Joost
82f8e46cde add prevention of ctrl+c ctrl + v 2026-05-18 14:30:41 +02:00

View File

@ -12,7 +12,7 @@
"Der mutige Browser",
"Eine schlaue Funktion",
"Der vergessliche Server",
"Die kreative Gruppe"
"Die kreative Gruppe",
],
actions: [
"sortiert leise",
@ -22,7 +22,7 @@
"rendert ploetzlich",
"zaehlt konzentriert",
"testet neugierig",
"kompiliert langsam"
"kompiliert langsam",
],
objects: [
"sieben blaue Buttons",
@ -32,7 +32,7 @@
"acht schnelle Requests",
"zwei leuchtende Karten",
"fuenf stille Fehlermeldungen",
"sechs winzige Icons"
"sechs winzige Icons",
],
places: [
"im hellen Dashboard",
@ -42,7 +42,7 @@
"vor dem ersten Kaffee",
"waehrend der Lernphase",
"hinter dem lokalen Server",
"mitten im Semesterprojekt"
"mitten im Semesterprojekt",
],
endings: [
"Danach lacht der Code, weil alles endlich funktioniert.",
@ -50,8 +50,8 @@
"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."
]
"Zum Schluss gewinnt, wer die Woerter sauber in Reihenfolge bringt.",
],
};
let timerInterval;
@ -87,21 +87,28 @@
let generatedText = "";
do {
const firstSentence = [
const firstSentence =
[
getRandomItem(TEXT_PARTS.subjects),
getRandomItem(TEXT_PARTS.actions),
getRandomItem(TEXT_PARTS.objects),
getRandomItem(TEXT_PARTS.places)
getRandomItem(TEXT_PARTS.places),
].join(" ") + ".";
const secondSentence = [
const secondSentence =
[
getRandomItem(TEXT_PARTS.subjects),
getRandomItem(TEXT_PARTS.actions),
getRandomItem(TEXT_PARTS.objects),
getRandomItem(TEXT_PARTS.places)
getRandomItem(TEXT_PARTS.places),
].join(" ") + ".";
generatedText = firstSentence + " " + secondSentence + " " + getRandomItem(TEXT_PARTS.endings);
generatedText =
firstSentence +
" " +
secondSentence +
" " +
getRandomItem(TEXT_PARTS.endings);
} while (generatedText === lastGeneratedText);
lastGeneratedText = generatedText;
@ -112,8 +119,8 @@
if (!phaseStart || !phaseMemorize) return;
// Startansicht ausblenden und den neu generierten Text fuer die Lernphase anzeigen.
phaseStart.classList.add('d-none');
phaseMemorize.classList.remove('d-none');
phaseStart.classList.add("d-none");
phaseMemorize.classList.remove("d-none");
if (gameStatus) {
gameStatus.textContent = "Lernphase";
@ -145,8 +152,8 @@
if (!phaseMemorize || !phaseInput) return;
// Text verschwindet, Eingabefeld erscheint: ab hier zaehlt nur noch das Gedaechtnis.
phaseMemorize.classList.add('d-none');
phaseInput.classList.remove('d-none');
phaseMemorize.classList.add("d-none");
phaseInput.classList.remove("d-none");
if (gameStatus) {
gameStatus.textContent = "Eingabe";
@ -168,15 +175,19 @@
// 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);
return text.split(/\s+/).filter((word) => word.length > 0);
}
function calculateScore(original, input) {
if (!original || !input) return 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);
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);
@ -193,7 +204,8 @@
// 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.className =
"badge me-1 mb-1 " + (isCorrect ? "text-bg-success" : "text-bg-danger");
badge.textContent = word;
return badge;
}
@ -210,13 +222,15 @@
// Original: rot, wenn das eingegebene Wort an dieser Position fehlt oder falsch ist.
originalWords.forEach((word, index) => {
const isCorrect = normalizeWord(word) === normalizeWord(inputWords[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] || "");
const isCorrect =
normalizeWord(word) === normalizeWord(originalWords[index] || "");
resultInput.appendChild(createWordBadge(word, isCorrect));
});
}
@ -250,7 +264,7 @@
if (!auth || !auth.username || !auth.password) {
showScoreSaveFeedback(
"Score wurde nur lokal berechnet. Bitte einloggen, damit er im Leaderboard gespeichert wird.",
"warning"
"warning",
);
return;
}
@ -258,7 +272,10 @@
// 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");
showScoreSaveFeedback(
"Score-Service konnte nicht geladen werden.",
"danger",
);
return;
}
@ -270,21 +287,33 @@
scoreData.score,
scoreData.time,
scoreData.text,
scoreData.userWrittenText
scoreData.userWrittenText,
);
if (result.ok) {
const place = result.body && result.body.place ? " Platz " + result.body.place + "." : "";
showScoreSaveFeedback("Score erfolgreich gespeichert." + place, "success");
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");
showScoreSaveFeedback(
"Score konnte nicht gespeichert werden: Login ist nicht gültig.",
"danger",
);
return;
}
showScoreSaveFeedback("Score konnte nicht gespeichert werden (Status " + result.status + ").", "danger");
showScoreSaveFeedback(
"Score konnte nicht gespeichert werden (Status " + result.status + ").",
"danger",
);
}
async function submitScore() {
@ -304,8 +333,8 @@
const score = calculateScore(currentGameText, userInput);
// Ergebnis sofort anzeigen; das Speichern im Backend passiert danach asynchron.
if (phaseInput) phaseInput.classList.add('d-none');
if (phaseResult) phaseResult.classList.remove('d-none');
if (phaseInput) phaseInput.classList.add("d-none");
if (phaseResult) phaseResult.classList.remove("d-none");
if (gameStatus) {
gameStatus.textContent = "Abgeschlossen";
@ -322,7 +351,7 @@
score: score,
time: MEMORIZE_TIME_SECONDS,
text: currentGameText,
userWrittenText: userInput
userWrittenText: userInput,
};
console.log("Score bereit zum Senden:", scoreData);
@ -331,7 +360,10 @@
await saveScore(scoreData);
} catch (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 {
if (btnSubmitScore) {
btnSubmitScore.disabled = false;
@ -344,36 +376,61 @@
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');
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');
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');
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 (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.");
}
});
};
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";
}
};
})();