Aufgaben-Python/assets/parsons.js

182 lines
7.2 KiB
JavaScript

/**
* 🧩 INTERAKTIVES PARSONS-PUZZLE & PRIMM WIDGET 🧩
* =================================================
* Basiert auf der Forschung von Dale Parsons & Barbara Ericson.
* Ermöglicht Schülerinnen und Schülern das Sortieren von Code-Blöcken
* mit sofortigem Feedback und Gamification-XP.
*/
(function () {
class ParsonsWidget {
constructor(container) {
this.container = container;
this.puzzleId = container.dataset.puzzleId || "puzzle_" + Math.random().toString(36).substr(2, 9);
this.initialLines = JSON.parse(container.dataset.lines || "[]");
this.solution = JSON.parse(container.dataset.solution || "[]");
this.explanation = container.dataset.explanation || "Sehr gut! Du hast die Logik fehlerfrei aufgebaut.";
this.xpReward = parseInt(container.dataset.xp || "25", 10);
this.currentLines = [...this.initialLines];
this.isSolved = this.loadSolvedState();
this.render();
}
loadSolvedState() {
try {
const solved = JSON.parse(localStorage.getItem("parsons_solved") || "{}");
return !!solved[this.puzzleId];
} catch (e) {
return false;
}
}
saveSolvedState() {
try {
const solved = JSON.parse(localStorage.getItem("parsons_solved") || "{}");
solved[this.puzzleId] = true;
localStorage.setItem("parsons_solved", JSON.stringify(solved));
// Gamification XP Bonus gutschreiben
if (window.GAMIFICATION_DATA) {
window.GAMIFICATION_DATA.xp = (window.GAMIFICATION_DATA.xp || 0) + this.xpReward;
if (typeof renderGamificationHUD === "function") {
renderGamificationHUD();
}
}
} catch (e) {
console.error("Fehler beim Speichern des Puzzle-Status:", e);
}
}
render() {
this.container.classList.add("parsons-box");
this.container.innerHTML = `
<div class="parsons-header">
<div class="parsons-title">
<span>🧩 Code-Puzzle (Parsons Problem)</span>
<span class="parsons-badge">${this.isSolved ? '✅ Gelöst (+'+this.xpReward+' XP)' : '⭐ +'+this.xpReward+' XP'}</span>
</div>
<p class="parsons-instruction">
Bringe die Code-Blöcke in die richtige logische Reihenfolge, bevor du selbst programmierst:
</p>
</div>
<div class="parsons-board" id="board-${this.puzzleId}">
${this.currentLines.map((line, idx) => `
<div class="parsons-line" draggable="true" data-index="${idx}">
<div class="parsons-handle">☰</div>
<div class="parsons-code"><code>${this.escapeHtml(line)}</code></div>
<div class="parsons-actions">
<button type="button" class="btn-parsons-move btn-up" data-dir="-1" title="Nach oben" ${idx === 0 ? 'disabled' : ''}>▲</button>
<button type="button" class="btn-parsons-move btn-down" data-dir="1" title="Nach unten" ${idx === this.currentLines.length - 1 ? 'disabled' : ''}>▼</button>
</div>
</div>
`).join("")}
</div>
<div class="parsons-controls">
<button type="button" class="btn btn-parsons-check" id="check-${this.puzzleId}">
${this.isSolved ? '🔄 Erneut prüfen' : '✨ Lösung prüfen'}
</button>
<button type="button" class="btn btn-secondary btn-parsons-hint" id="hint-${this.puzzleId}">
💡 Ersten Block verraten
</button>
</div>
<div class="parsons-feedback" id="feedback-${this.puzzleId}" style="${this.isSolved ? 'display: block;' : 'display: none;'}">
${this.isSolved ? `<div class="box box-success"><div class="box-title">🎉 Perfekt gelöst!</div>${this.explanation}</div>` : ''}
</div>
`;
this.attachEvents();
}
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
attachEvents() {
const board = document.getElementById(`board-${this.puzzleId}`);
const checkBtn = document.getElementById(`check-${this.puzzleId}`);
const hintBtn = document.getElementById(`hint-${this.puzzleId}`);
// Up / Down Buttons
board.querySelectorAll(".btn-parsons-move").forEach(btn => {
btn.addEventListener("click", (e) => {
const lineEl = e.target.closest(".parsons-line");
const idx = parseInt(lineEl.dataset.index, 10);
const dir = parseInt(e.target.dataset.dir, 10);
const targetIdx = idx + dir;
if (targetIdx >= 0 && targetIdx < this.currentLines.length) {
const temp = this.currentLines[idx];
this.currentLines[idx] = this.currentLines[targetIdx];
this.currentLines[targetIdx] = temp;
this.render();
}
});
});
// Check Button
checkBtn.addEventListener("click", () => this.checkSolution());
// Hint Button
hintBtn.addEventListener("click", () => {
const correctFirst = this.solution[0];
const currentFirstIdx = this.currentLines.indexOf(correctFirst);
if (currentFirstIdx > 0) {
this.currentLines.splice(currentFirstIdx, 1);
this.currentLines.unshift(correctFirst);
this.render();
const feedback = document.getElementById(`feedback-${this.puzzleId}`);
feedback.innerHTML = `<div class="box box-tipp"><div class="box-title">💡 Tipp:</div>Der erste Block <code>${this.escapeHtml(correctFirst)}</code> wurde an den Anfang gesetzt!</div>`;
feedback.style.display = "block";
}
});
}
checkSolution() {
const feedback = document.getElementById(`feedback-${this.puzzleId}`);
let correct = true;
for (let i = 0; i < this.solution.length; i++) {
if (this.currentLines[i] !== this.solution[i]) {
correct = false;
break;
}
}
if (correct) {
feedback.innerHTML = `
<div class="box box-success" style="animation: fadeIn 0.3s ease;">
<div class="box-title">🎉 Bravo! Alle Blöcke sind in der perfekten Reihenfolge!</div>
<p>${this.explanation}</p>
<p style="margin-top: 8px; font-weight: bold; color: var(--success);">
⭐ Du hast <strong>+${this.xpReward} XP</strong> für deinen Entwickler-Pass freigeschaltet!
</p>
</div>
`;
feedback.style.display = "block";
this.saveSolvedState();
this.isSolved = true;
} else {
feedback.innerHTML = `
<div class="box box-warning" style="animation: fadeIn 0.3s ease;">
<div class="box-title">🤔 Noch nicht ganz...</div>
<p>Einige Zeilen stehen noch an der falschen Stelle. Überlege Schritt für Schritt: Was muss zuerst initialisiert oder berechnet werden, bevor der nächste Schritt ausgeführt werden kann?</p>
</div>
`;
feedback.style.display = "block";
}
}
}
// Initialisiere alle Puzzle-Widgets auf der Seite
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".parsons-puzzle").forEach(el => {
new ParsonsWidget(el);
});
});
})();