Files
quiz/app/static/js/quiz.js
T

300 lines
11 KiB
JavaScript
Executable File

let currentSessionId = null;
let currentQuestionNumber = 1;
let totalQuestions = 15;
let currentScore = 0;
let attemptsUsed = 0;
const maxAttempts = 3;
// Web Audio API Sound Generator for magic chimes and buzzers
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
if (type === 'magic') {
// Magical bright arpeggio chime
const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
notes.forEach((freq, index) => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime + index * 0.08);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + index * 0.08 + 0.35);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(audioCtx.currentTime + index * 0.08);
osc.stop(audioCtx.currentTime + index * 0.08 + 0.35);
});
} else if (type === 'wrong') {
// Soft error buzz
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.25);
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.25);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.25);
}
}
async function startGame() {
const envelope = document.getElementById("envelope");
const startBtn = document.getElementById("startBtn");
startBtn.disabled = true;
startBtn.innerText = "✨ Zauber wird gewirkt...";
envelope.classList.add("envelope-opened");
// Start background music if enabled or first click
const bgm = document.getElementById("bgm");
if (bgm && bgm.paused) {
bgm.play().then(() => {
const icon = document.getElementById("audioIcon");
const text = document.getElementById("audioText");
if (icon && text) {
icon.innerText = "🔊";
text.innerText = "Musik: An";
}
localStorage.setItem("bgm_enabled", "true");
}).catch(() => {});
}
try {
const res = await fetch("/api/game/start", {
method: "POST",
headers: { "Content-Type": "application/json" }
});
if (!res.ok) {
throw new Error("Konnte Spiel nicht starten.");
}
const data = await res.json();
currentSessionId = data.session_id;
currentQuestionNumber = data.question_number;
totalQuestions = data.total_questions;
currentScore = data.score;
attemptsUsed = data.attempts_used;
setTimeout(() => {
document.getElementById("introCard").classList.add("hidden");
document.getElementById("gameCard").classList.remove("hidden");
renderQuestion(data);
}, 1100);
} catch (err) {
alert("Fehler beim Starten des Quiz: " + err.message);
startBtn.disabled = false;
startBtn.innerText = "📜 Siegel brechen & Brief öffnen";
envelope.classList.remove("envelope-opened");
}
}
function renderQuestion(qData) {
document.getElementById("questionCounter").innerText = `Frage ${qData.question_number} von ${totalQuestions}`;
document.getElementById("pointsCounter").innerText = `🏆 Hauspunkte: ${currentScore}`;
document.getElementById("badgeCategory").innerText = `⚡ ${qData.category.replace("_", " ").toUpperCase()}`;
document.getElementById("attemptBadge").innerText = `Versuch ${attemptsUsed + 1} von ${maxAttempts}`;
document.getElementById("frageText").innerText = qData.question;
const textGroup = document.getElementById("textAnswerGroup");
const choiceGroup = document.getElementById("choiceAnswerGroup");
const choiceContainer = document.getElementById("choiceOptionsContainer");
const input = document.getElementById("answerInput");
document.getElementById("hintBox").classList.add("hidden");
document.getElementById("feedbackBox").innerHTML = "";
if (qData.question_type === "multiple_choice" && qData.options && qData.options.length > 0) {
textGroup.classList.add("hidden");
choiceGroup.classList.remove("hidden");
choiceContainer.innerHTML = "";
const letters = ["A", "B", "C", "D", "E", "F"];
qData.options.forEach((opt, idx) => {
const btn = document.createElement("button");
btn.className = "choice-btn";
btn.type = "button";
btn.innerHTML = `<span class="choice-letter">${letters[idx] || "•"}</span> <span class="choice-text">${opt}</span>`;
btn.onclick = () => submitAnswer(opt, btn);
choiceContainer.appendChild(btn);
});
} else {
choiceGroup.classList.add("hidden");
textGroup.classList.remove("hidden");
input.value = "";
input.disabled = false;
document.getElementById("checkBtn").disabled = false;
input.focus();
}
const hintBtn = document.getElementById("hintBtn");
if (hintBtn) hintBtn.disabled = false;
const hintBtnMC = document.getElementById("hintBtnMC");
if (hintBtnMC) hintBtnMC.disabled = false;
updateProgress();
}
function updateProgress() {
const percent = ((currentQuestionNumber - 1) / totalQuestions) * 100;
document.getElementById("progressBar").style.width = percent + "%";
}
function checkAnswer() {
const input = document.getElementById("answerInput");
const answer = input.value.trim();
if (!answer) {
input.focus();
return;
}
submitAnswer(answer);
}
async function submitAnswer(answer, clickedBtn = null) {
const checkBtn = document.getElementById("checkBtn");
const input = document.getElementById("answerInput");
if (checkBtn) checkBtn.disabled = true;
if (input) input.disabled = true;
// Disable all choice buttons
const choiceBtns = document.querySelectorAll(".choice-btn");
choiceBtns.forEach(b => b.disabled = true);
if (clickedBtn) {
clickedBtn.classList.add("choice-selected");
}
try {
const res = await fetch("/api/game/check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
session_id: currentSessionId,
answer: answer
})
});
if (!res.ok) {
throw new Error("Fehler bei der Antwortprüfung.");
}
const result = await res.json();
currentScore = result.score;
document.getElementById("pointsCounter").innerText = `🏆 Hauspunkte: ${currentScore}`;
const frageBox = document.getElementById("frageBox");
const feedbackBox = document.getElementById("feedbackBox");
if (result.correct) {
// Success!
playSound('magic');
if (clickedBtn) {
clickedBtn.classList.remove("choice-selected");
clickedBtn.classList.add("choice-correct");
}
frageBox.classList.add("sparkle-effect");
feedbackBox.innerHTML = `<span class="feedback-correct">✨ Richtig! 1 Hauspunkt für dein Haus! ✨</span>`;
setTimeout(() => {
frageBox.classList.remove("sparkle-effect");
if (result.is_finished) {
finishGame();
} else if (result.next_question) {
currentQuestionNumber = result.next_question.question_number;
attemptsUsed = 0;
renderQuestion(result.next_question);
}
}, 1200);
} else {
// Incorrect
playSound('wrong');
if (clickedBtn) {
clickedBtn.classList.remove("choice-selected");
clickedBtn.classList.add("choice-wrong");
}
frageBox.classList.add("shake-effect");
setTimeout(() => frageBox.classList.remove("shake-effect"), 500);
attemptsUsed = result.attempts_used;
document.getElementById("attemptBadge").innerText = `Versuch ${Math.min(attemptsUsed + 1, maxAttempts)} von ${maxAttempts}`;
if (result.attempts_left === 0) {
// Exhausted all attempts
feedbackBox.innerHTML = `
<div class="feedback-wrong">Leider nicht richtig (3 Fehlversuche).</div>
<div class="feedback-revealed">Richtige Lösung: <strong>${result.revealed_answer}</strong></div>
`;
setTimeout(() => {
if (result.is_finished) {
finishGame();
} else if (result.next_question) {
currentQuestionNumber = result.next_question.question_number;
attemptsUsed = 0;
renderQuestion(result.next_question);
}
}, 2200);
} else {
// Still attempts remaining
feedbackBox.innerHTML = `<span class="feedback-wrong">Leider falsch! Noch ${result.attempts_left} Versuch(e).</span>`;
if (checkBtn) checkBtn.disabled = false;
if (input) {
input.disabled = false;
input.select();
}
// Re-enable choice buttons
choiceBtns.forEach(b => b.disabled = false);
}
}
} catch (err) {
alert("Netzwerkfehler: " + err.message);
if (checkBtn) checkBtn.disabled = false;
if (input) input.disabled = false;
choiceBtns.forEach(b => b.disabled = false);
}
}
async function requestHint() {
if (!currentSessionId) return;
const hintBox = document.getElementById("hintBox");
const hintText = document.getElementById("hintText");
try {
const res = await fetch(`/api/game/hint?session_id=${encodeURIComponent(currentSessionId)}`);
const data = await res.json();
hintText.innerText = data.hint || "Kein Tipp vorhanden.";
hintBox.classList.remove("hidden");
} catch (err) {
hintText.innerText = "Tipp konnte nicht geladen werden.";
hintBox.classList.remove("hidden");
}
}
function finishGame() {
// 100% progress
document.getElementById("progressBar").style.width = "100%";
window.location.href = `/finale?score=${currentScore}&total=${totalQuestions}`;
}
// Enter key to submit
document.addEventListener("DOMContentLoaded", () => {
const input = document.getElementById("answerInput");
if (input) {
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
checkAnswer();
}
});
}
});