125 lines
3.7 KiB
JavaScript
125 lines
3.7 KiB
JavaScript
function normalizeText(text) {
|
|
if (!text) return '';
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/[äÄ]/g, 'ae')
|
|
.replace(/[öÖ]/g, 'oe')
|
|
.replace(/[üÜ]/g, 'ue')
|
|
.replace(/ß/g, 'ss')
|
|
.replace(/[^a-z0-9]/g, '')
|
|
.trim();
|
|
}
|
|
|
|
function extractFieldValue(field, fallback) {
|
|
if (!field) return fallback || '';
|
|
if (typeof field === 'string') return field;
|
|
if (typeof field === 'object' && field.value !== undefined) return field.value;
|
|
return String(field);
|
|
}
|
|
|
|
function getTyp(question) {
|
|
return extractFieldValue(question['Typ'], 'MC').toLowerCase();
|
|
}
|
|
|
|
function checkFreitext(userAnswer, correctAnswer) {
|
|
return normalizeText(userAnswer) === normalizeText(correctAnswer);
|
|
}
|
|
|
|
function parseCorrectAnswers(question) {
|
|
const raw = (question['Richtige Antwort'] || '').trim();
|
|
const typ = getTyp(question);
|
|
if (typ === 'freitext') return [raw];
|
|
return raw
|
|
.split(',')
|
|
.map((s) => s.trim().toUpperCase())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function countOptions(question) {
|
|
let n = 0;
|
|
if (question['Antwort A']) n++;
|
|
if (question['Antwort B']) n++;
|
|
if (question['Antwort C']) n++;
|
|
if (question['Antwort D']) n++;
|
|
return n;
|
|
}
|
|
|
|
function shuffleArray(arr) {
|
|
const a = [...arr];
|
|
for (let i = a.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[a[i], a[j]] = [a[j], a[i]];
|
|
}
|
|
return a;
|
|
}
|
|
|
|
function calculateScore(question, selected, timeRemaining, totalTime, scoringMode) {
|
|
if (!selected || selected.length === 0) return { points: 0, correct: false };
|
|
|
|
let basis;
|
|
if (scoringMode === 'time' && totalTime > 0) {
|
|
const ratio = Math.max(0, Math.min(1, timeRemaining / totalTime));
|
|
basis = 500 + Math.round(ratio * 500);
|
|
} else {
|
|
basis = 1000;
|
|
}
|
|
|
|
const typ = getTyp(question);
|
|
const correctAnswers = parseCorrectAnswers(question);
|
|
|
|
if (typ === 'freitext') {
|
|
const ok = checkFreitext(selected[0], correctAnswers[0]);
|
|
return { points: ok ? basis : 0, correct: ok };
|
|
}
|
|
|
|
const correctSet = new Set(correctAnswers);
|
|
const totalCorrect = correctSet.size;
|
|
const totalIncorrect = countOptions(question) - totalCorrect;
|
|
|
|
let rightPicked = 0;
|
|
let wrongPicked = 0;
|
|
for (const s of selected) {
|
|
if (correctSet.has(s.toUpperCase())) rightPicked++;
|
|
else wrongPicked++;
|
|
}
|
|
|
|
let points = (rightPicked / Math.max(totalCorrect, 1)) * basis;
|
|
if (totalIncorrect > 0) {
|
|
points -= (wrongPicked / totalIncorrect) * (basis / 2);
|
|
}
|
|
points = Math.max(0, Math.round(points));
|
|
|
|
const fullyCorrect = rightPicked === totalCorrect && wrongPicked === 0;
|
|
return { points, correct: fullyCorrect };
|
|
}
|
|
|
|
function sanitizeQuestion(question) {
|
|
const typ = getTyp(question);
|
|
const sanitized = {
|
|
id: question.id,
|
|
frage: question['Frage'] || '',
|
|
typ: extractFieldValue(question['Typ'], 'MC'),
|
|
bild: null,
|
|
antworten: [],
|
|
kategorie: extractFieldValue(question['Kategorie'], ''),
|
|
};
|
|
|
|
// Rewrite image URL to use proxy
|
|
const bild = question['Bild'];
|
|
if (bild && Array.isArray(bild) && bild.length > 0 && bild[0].url) {
|
|
sanitized.bild = `/api/img?url=${encodeURIComponent(bild[0].url)}`;
|
|
}
|
|
|
|
if (typ !== 'freitext') {
|
|
const keys = ['A', 'B', 'C', 'D'];
|
|
for (const k of keys) {
|
|
const val = question[`Antwort ${k}`];
|
|
if (val) sanitized.antworten.push({ key: k, text: val });
|
|
}
|
|
sanitized.antworten = shuffleArray(sanitized.antworten);
|
|
}
|
|
|
|
return sanitized;
|
|
}
|
|
|
|
module.exports = { calculateScore, sanitizeQuestion, parseCorrectAnswers, normalizeText, shuffleArray, extractFieldValue }; |