Implementiere persistente Gamification (Level 1-10, XP, 14 Badges, profil.py CLI & Web-HUD für lokale Offline-Nutzung)
This commit is contained in:
parent
478b1d95fe
commit
86532b15bb
8 changed files with 1125 additions and 14 deletions
14
.gamification.json
Normal file
14
.gamification.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"spieler_name": "Python-Entwicklerin",
|
||||
"xp": 0,
|
||||
"level": 1,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I",
|
||||
"geloeste_kapitel": [],
|
||||
"bestandene_tests": 0,
|
||||
"freigeschaltete_badges": [],
|
||||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-08-29",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-08-29T14:51:01.563840"
|
||||
}
|
||||
205
assets/gamification.js
Normal file
205
assets/gamification.js
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* 🎮 GAMIFICATION ENGINE FÜR DAS PYTHON LERNPORTAL 🎮
|
||||
* ====================================================
|
||||
* Funktioniert 100% lokal ohne Server (file://) via gamification_data.js & localStorage!
|
||||
*/
|
||||
|
||||
(function () {
|
||||
// Standard-Kataloge als Fallback, falls gamification_data.js noch nicht existiert
|
||||
const DEFAULT_LEVELS = [
|
||||
{ level: 1, min_xp: 0, max_xp: 150, titel: "Code-Küken 🐣", rang: "Bronze I" },
|
||||
{ level: 2, min_xp: 151, max_xp: 400, titel: "Code-Detektivin 🔍", rang: "Bronze II" },
|
||||
{ level: 3, min_xp: 401, max_xp: 800, titel: "Logik-Bastlerin ⚙️", rang: "Silber I" },
|
||||
{ level: 4, min_xp: 801, max_xp: 1300, titel: "Klassen-Baumeisterin 🏗️", rang: "Silber II" },
|
||||
{ level: 5, min_xp: 1301, max_xp: 2000, titel: "Git-Strategin 🌿", rang: "Gold I" },
|
||||
{ level: 6, min_xp: 2001, max_xp: 2900, titel: "TDD-Qualitätsprüferin 🧪", rang: "Gold II" },
|
||||
{ level: 7, min_xp: 2901, max_xp: 4000, titel: "Vererbungs-Meisterin 🧬", rang: "Platin I" },
|
||||
{ level: 8, min_xp: 4001, max_xp: 5300, titel: "Exception-Wächterin 🛡️", rang: "Platin II" },
|
||||
{ level: 9, min_xp: 5301, max_xp: 6800, titel: "GUI-Entwicklerin 🖥️", rang: "Diamant" },
|
||||
{ level: 10, min_xp: 6801, max_xp: 99999, titel: "Software-Architektin 🏆", rang: "Großmeisterin" }
|
||||
];
|
||||
|
||||
const DEFAULT_BADGES = {
|
||||
rechenkonegin: { icon: "🧮", name: "Rechenkönigin", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
|
||||
dialog_profi: { icon: "💬", name: "Dialog-Profi", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
|
||||
weichenstellerin: { icon: "🚦", name: "Weichenstellerin", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert)." },
|
||||
schleifen_dompteurin: { icon: "🎡", name: "Schleifen-Dompteurin", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt)." },
|
||||
funktions_zauberin: { icon: "🪄", name: "Funktions-Zauberin", desc: "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut)." },
|
||||
listen_archivarin: { icon: "📋", name: "Listen-Archivarin", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert)." },
|
||||
daten_strategin: { icon: "🗃️", name: "Daten-Strategin", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert)." },
|
||||
bug_jaegerin: { icon: "🔍", name: "Bug-Jägerin", desc: "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt)." },
|
||||
erste_architektin: { icon: "🏗️", name: "Erste Architektin", desc: "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden)." },
|
||||
zeit_reisende: { icon: "🌿", name: "Git-Zeitreisende", desc: "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert." },
|
||||
tamagotchi_mama: { icon: "🥚", name: "Tamagotchi-Mama", desc: "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut)." },
|
||||
qualitaets_garantin: { icon: "🧪", name: "TDD-Qualitätsgarantin", desc: "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben)." },
|
||||
oop_grossmeisterin: { icon: "🧬", name: "Vererbungs-Koryphäe", desc: "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
|
||||
software_architektin: { icon: "🏆", name: "Meister-Entwicklerin", desc: "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!" }
|
||||
};
|
||||
|
||||
// Daten abrufen
|
||||
function getGamificationData() {
|
||||
if (window.GAMIFICATION_DATA) {
|
||||
return window.GAMIFICATION_DATA;
|
||||
}
|
||||
const saved = localStorage.getItem("python_gamification_state");
|
||||
if (saved) {
|
||||
try { return JSON.parse(saved); } catch (e) { }
|
||||
}
|
||||
return {
|
||||
spieler_name: "Python-Entwicklerin",
|
||||
xp: 0,
|
||||
level: 1,
|
||||
titel: "Code-Küken 🐣",
|
||||
rang: "Bronze I",
|
||||
geloeste_kapitel: [],
|
||||
bestandene_tests: 0,
|
||||
freigeschaltete_badges: [],
|
||||
streak_tage: 1
|
||||
};
|
||||
}
|
||||
|
||||
function getLevelInfo(xp) {
|
||||
const levels = window.GAMIFICATION_LEVEL_STUFEN || DEFAULT_LEVELS;
|
||||
for (let info of levels) {
|
||||
if (xp >= info.min_xp && xp <= info.max_xp) {
|
||||
const nextXp = info.max_xp + 1;
|
||||
const xpInLevel = xp - info.min_xp;
|
||||
const xpForLevel = nextXp - info.min_xp;
|
||||
const prozent = xpForLevel > 0 ? Math.min(100, Math.round((xpInLevel / xpForLevel) * 100)) : 100;
|
||||
return { ...info, naechste_stufe_xp: nextXp, prozent: prozent };
|
||||
}
|
||||
}
|
||||
return { level: 10, titel: "Software-Architektin 🏆", rang: "Großmeisterin", naechste_stufe_xp: 99999, prozent: 100 };
|
||||
}
|
||||
|
||||
// HUD & Trophäenraum rendern
|
||||
function renderGamificationHUD() {
|
||||
const data = getGamificationData();
|
||||
const lvlInfo = getLevelInfo(data.xp || 0);
|
||||
const badgesKatalog = window.GAMIFICATION_BADGES_KATALOG || DEFAULT_BADGES;
|
||||
const unlockedBadges = new Set(data.freigeschaltete_badges || []);
|
||||
const badgeCount = unlockedBadges.size;
|
||||
const totalBadges = Object.keys(badgesKatalog).length;
|
||||
|
||||
// Finde oder erstelle den HUD-Container
|
||||
let hud = document.getElementById("gamer-hud");
|
||||
if (!hud) {
|
||||
const mainContainer = document.querySelector(".container") || document.body;
|
||||
hud = document.createElement("div");
|
||||
hud.id = "gamer-hud";
|
||||
mainContainer.prepend(hud);
|
||||
}
|
||||
|
||||
hud.innerHTML = `
|
||||
<div class="hud-card">
|
||||
<div class="hud-header">
|
||||
<div class="hud-user">
|
||||
<div class="hud-avatar">🎮</div>
|
||||
<div>
|
||||
<div class="hud-name">${data.spieler_name || "Python-Entwicklerin"}</div>
|
||||
<div class="hud-title">${lvlInfo.titel} • <span class="hud-rank">${lvlInfo.rang}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hud-stats">
|
||||
<div class="hud-stat-box">
|
||||
<span class="hud-stat-value">⭐ Level ${lvlInfo.level}</span>
|
||||
<span class="hud-stat-label">${data.xp || 0} XP</span>
|
||||
</div>
|
||||
<div class="hud-stat-box">
|
||||
<span class="hud-stat-value">🔥 ${data.streak_tage || 1} Tage</span>
|
||||
<span class="hud-stat-label">Lern-Streak</span>
|
||||
</div>
|
||||
<div class="hud-stat-box">
|
||||
<span class="hud-stat-value">🏆 ${badgeCount}/${totalBadges}</span>
|
||||
<span class="hud-stat-label">Trophäen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hud-progress-container">
|
||||
<div class="hud-progress-labels">
|
||||
<span>Fortschritt zu Level ${lvlInfo.level + 1}</span>
|
||||
<span><strong>${data.xp || 0}</strong> / ${lvlInfo.naechste_stufe_xp} XP (${lvlInfo.prozent}%)</span>
|
||||
</div>
|
||||
<div class="hud-progress-bar-bg">
|
||||
<div class="hud-progress-bar-fill" style="width: ${lvlInfo.prozent}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hud-footer">
|
||||
<button id="toggle-trophies-btn" class="hud-btn">
|
||||
🏆 Trophäenraum öffnen (${badgeCount}/${totalBadges} freigeschaltet)
|
||||
</button>
|
||||
<div class="hud-cli-tip">
|
||||
⌨️ Im Terminal: <code>python3 profil.py</code> oder <code>python3 test_all.py</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="trophy-room" class="trophy-room" style="display: none;">
|
||||
<h3 style="margin: 15px 0 10px 0; color: var(--text-main);">🎖️ Dein Trophäenraum</h3>
|
||||
<div class="trophy-grid">
|
||||
${Object.keys(badgesKatalog).map(bId => {
|
||||
const b = badgesKatalog[bId];
|
||||
const isUnlocked = unlockedBadges.has(bId);
|
||||
return `
|
||||
<div class="trophy-card ${isUnlocked ? 'trophy-unlocked' : 'trophy-locked'}">
|
||||
<div class="trophy-icon">${isUnlocked ? b.icon : '🔒'}</div>
|
||||
<div class="trophy-info">
|
||||
<div class="trophy-title">${b.name}</div>
|
||||
<div class="trophy-desc">${b.desc}</div>
|
||||
<div class="trophy-badge-status">${isUnlocked ? '✅ Freigeschaltet' : '🔒 Gesperrt'}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event Listener für Trophäenraum Toggle
|
||||
const toggleBtn = document.getElementById("toggle-trophies-btn");
|
||||
const trophyRoom = document.getElementById("trophy-room");
|
||||
if (toggleBtn && trophyRoom) {
|
||||
toggleBtn.addEventListener("click", function () {
|
||||
const isHidden = trophyRoom.style.display === "none";
|
||||
trophyRoom.style.display = isHidden ? "block" : "none";
|
||||
toggleBtn.innerText = isHidden
|
||||
? `🔼 Trophäenraum einklappen (${badgeCount}/${totalBadges})`
|
||||
: `🏆 Trophäenraum öffnen (${badgeCount}/${totalBadges} freigeschaltet)`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox-Persistenz für alle Aufgaben-Seiten (localStorage)
|
||||
function initTaskCheckboxes() {
|
||||
const checkboxes = document.querySelectorAll(".task-checkbox");
|
||||
if (!checkboxes.length) return;
|
||||
|
||||
const pageKey = "task_progress_" + window.location.pathname;
|
||||
const savedStates = JSON.parse(localStorage.getItem(pageKey) || "{}");
|
||||
|
||||
checkboxes.forEach((cb) => {
|
||||
const id = cb.id;
|
||||
if (id && savedStates[id]) {
|
||||
cb.checked = true;
|
||||
}
|
||||
cb.addEventListener("change", function () {
|
||||
const states = JSON.parse(localStorage.getItem(pageKey) || "{}");
|
||||
states[id] = cb.checked;
|
||||
localStorage.setItem(pageKey, JSON.stringify(states));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialisierung beim Laden
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
renderGamificationHUD();
|
||||
initTaskCheckboxes();
|
||||
});
|
||||
} else {
|
||||
renderGamificationHUD();
|
||||
initTaskCheckboxes();
|
||||
}
|
||||
})();
|
||||
236
assets/gamification_data.js
Normal file
236
assets/gamification_data.js
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// Automatisch generierter lokaler Gamification-Spielstand
|
||||
window.GAMIFICATION_DATA = {
|
||||
"spieler_name": "Python-Entwicklerin",
|
||||
"xp": 0,
|
||||
"level": 1,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I",
|
||||
"geloeste_kapitel": [],
|
||||
"bestandene_tests": 0,
|
||||
"freigeschaltete_badges": [],
|
||||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": "2026-08-29",
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": "2026-08-29T14:51:01.563840"
|
||||
};
|
||||
window.GAMIFICATION_BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
"id": "rechenkonegin",
|
||||
"icon": "🧮",
|
||||
"name": "Rechenkönigin",
|
||||
"desc": "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"g01_erste_schritte_taschenrechner",
|
||||
"g02_variablen_und_datentypen"
|
||||
]
|
||||
},
|
||||
"dialog_profi": {
|
||||
"id": "dialog_profi",
|
||||
"icon": "💬",
|
||||
"name": "Dialog-Profi",
|
||||
"desc": "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
"g03_ein_und_ausgabe"
|
||||
]
|
||||
},
|
||||
"weichenstellerin": {
|
||||
"id": "weichenstellerin",
|
||||
"icon": "🚦",
|
||||
"name": "Weichenstellerin",
|
||||
"desc": "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": [
|
||||
"g04_verzweigungen_und_bedingungen"
|
||||
]
|
||||
},
|
||||
"schleifen_dompteurin": {
|
||||
"id": "schleifen_dompteurin",
|
||||
"icon": "🎡",
|
||||
"name": "Schleifen-Dompteurin",
|
||||
"desc": "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"g05_schleifen_und_wiederholungen"
|
||||
]
|
||||
},
|
||||
"funktions_zauberin": {
|
||||
"id": "funktions_zauberin",
|
||||
"icon": "🪄",
|
||||
"name": "Funktions-Zauberin",
|
||||
"desc": "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"g06_funktionen_und_module"
|
||||
]
|
||||
},
|
||||
"listen_archivarin": {
|
||||
"id": "listen_archivarin",
|
||||
"icon": "📋",
|
||||
"name": "Listen-Archivarin",
|
||||
"desc": "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert).",
|
||||
"xp_bonus": 120,
|
||||
"kriterium": [
|
||||
"g07_listen_und_sequenzen",
|
||||
"g08_textverarbeitung_und_strings"
|
||||
]
|
||||
},
|
||||
"daten_strategin": {
|
||||
"id": "daten_strategin",
|
||||
"icon": "🗃️",
|
||||
"name": "Daten-Strategin",
|
||||
"desc": "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
|
||||
"xp_bonus": 150,
|
||||
"kriterium": [
|
||||
"g09_dictionaries_und_sets",
|
||||
"g10_comprehensions_datum_algorithmen"
|
||||
]
|
||||
},
|
||||
"bug_jaegerin": {
|
||||
"id": "bug_jaegerin",
|
||||
"icon": "🔍",
|
||||
"name": "Bug-Jägerin",
|
||||
"desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"00_fehlersuche_und_grundlagen"
|
||||
]
|
||||
},
|
||||
"erste_architektin": {
|
||||
"id": "erste_architektin",
|
||||
"icon": "🏗️",
|
||||
"name": "Erste Architektin",
|
||||
"desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"01_einstieg_klassen",
|
||||
"02_init_und_self",
|
||||
"03_methoden_und_verhalten"
|
||||
]
|
||||
},
|
||||
"tamagotchi_mama": {
|
||||
"id": "tamagotchi_mama",
|
||||
"icon": "🥚",
|
||||
"name": "Tamagotchi-Mama",
|
||||
"desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
|
||||
"xp_bonus": 250,
|
||||
"kriterium": [
|
||||
"06_abschlussprojekt_tamagotchi"
|
||||
]
|
||||
},
|
||||
"zeit_reisende": {
|
||||
"id": "zeit_reisende",
|
||||
"icon": "🌿",
|
||||
"name": "Git-Zeitreisende",
|
||||
"desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": [
|
||||
"04_str_und_darstellung"
|
||||
]
|
||||
},
|
||||
"qualitaets_garantin": {
|
||||
"id": "qualitaets_garantin",
|
||||
"icon": "🧪",
|
||||
"name": "TDD-Qualitätsgarantin",
|
||||
"desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": [
|
||||
"09_eigene_unit_tests_schreiben"
|
||||
]
|
||||
},
|
||||
"oop_grossmeisterin": {
|
||||
"id": "oop_grossmeisterin",
|
||||
"icon": "🧬",
|
||||
"name": "Vererbungs-Koryphäe",
|
||||
"desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"xp_bonus": 300,
|
||||
"kriterium": [
|
||||
"10_vererbung_und_super",
|
||||
"11_polymorphie_und_interfaces",
|
||||
"12_exceptions_und_fehlerbehandlung"
|
||||
]
|
||||
},
|
||||
"software_architektin": {
|
||||
"id": "software_architektin",
|
||||
"icon": "🏆",
|
||||
"name": "Meister-Entwicklerin",
|
||||
"desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
|
||||
"xp_bonus": 500,
|
||||
"kriterium": [
|
||||
"16_master_abschlussprojekt"
|
||||
]
|
||||
}
|
||||
};
|
||||
window.GAMIFICATION_LEVEL_STUFEN = [
|
||||
{
|
||||
"level": 1,
|
||||
"min_xp": 0,
|
||||
"max_xp": 150,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I"
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"min_xp": 151,
|
||||
"max_xp": 400,
|
||||
"titel": "Code-Detektivin 🔍",
|
||||
"rang": "Bronze II"
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"min_xp": 401,
|
||||
"max_xp": 800,
|
||||
"titel": "Logik-Bastlerin ⚙️",
|
||||
"rang": "Silber I"
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"min_xp": 801,
|
||||
"max_xp": 1300,
|
||||
"titel": "Klassen-Baumeisterin 🏗️",
|
||||
"rang": "Silber II"
|
||||
},
|
||||
{
|
||||
"level": 5,
|
||||
"min_xp": 1301,
|
||||
"max_xp": 2000,
|
||||
"titel": "Git-Strategin 🌿",
|
||||
"rang": "Gold I"
|
||||
},
|
||||
{
|
||||
"level": 6,
|
||||
"min_xp": 2001,
|
||||
"max_xp": 2900,
|
||||
"titel": "TDD-Qualitätsprüferin 🧪",
|
||||
"rang": "Gold II"
|
||||
},
|
||||
{
|
||||
"level": 7,
|
||||
"min_xp": 2901,
|
||||
"max_xp": 4000,
|
||||
"titel": "Vererbungs-Meisterin 🧬",
|
||||
"rang": "Platin I"
|
||||
},
|
||||
{
|
||||
"level": 8,
|
||||
"min_xp": 4001,
|
||||
"max_xp": 5300,
|
||||
"titel": "Exception-Wächterin 🛡️",
|
||||
"rang": "Platin II"
|
||||
},
|
||||
{
|
||||
"level": 9,
|
||||
"min_xp": 5301,
|
||||
"max_xp": 6800,
|
||||
"titel": "GUI-Entwicklerin 🖥️",
|
||||
"rang": "Diamant"
|
||||
},
|
||||
{
|
||||
"level": 10,
|
||||
"min_xp": 6801,
|
||||
"max_xp": 99999,
|
||||
"titel": "Software-Architektin 🏆",
|
||||
"rang": "Großmeisterin"
|
||||
}
|
||||
];
|
||||
231
assets/style.css
231
assets/style.css
|
|
@ -506,6 +506,235 @@ summary:hover {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
GAMIFICATION HUD & TROPHY ROOM STYLES
|
||||
========================================================================== */
|
||||
|
||||
.hud-card {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||
color: #f8fafc;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 24px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: var(--shadow-md);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.hud-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.hud-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.hud-avatar {
|
||||
font-size: 2.2rem;
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
border: 2px solid var(--primary);
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.hud-name {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hud-title {
|
||||
font-size: 0.92rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.hud-rank {
|
||||
color: #fbbf24;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hud-stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hud-stat-box {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.hud-stat-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
.hud-stat-label {
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.hud-progress-container {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.hud-progress-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.hud-progress-bar-bg {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
height: 14px;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hud-progress-bar-fill {
|
||||
background: linear-gradient(90deg, #10b981 0%, #06b6d4 100%);
|
||||
height: 100%;
|
||||
border-radius: 7px;
|
||||
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
|
||||
.hud-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.hud-btn {
|
||||
background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 700;
|
||||
font-size: 0.88rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.hud-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.hud-cli-tip {
|
||||
font-size: 0.85rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.hud-cli-tip code {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
color: #38bdf8;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Trophy Room Grid */
|
||||
.trophy-room {
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px dashed rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.trophy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.trophy-card {
|
||||
padding: 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.trophy-unlocked {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
border: 1px solid #10b981;
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.15);
|
||||
}
|
||||
|
||||
.trophy-locked {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.trophy-icon {
|
||||
font-size: 1.8rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.trophy-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.trophy-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.trophy-desc {
|
||||
font-size: 0.82rem;
|
||||
color: #94a3b8;
|
||||
line-height: 1.35;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.trophy-badge-status {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.trophy-unlocked .trophy-badge-status {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.trophy-locked .trophy-badge-status {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 600px) {
|
||||
header h1 { font-size: 1.6rem; }
|
||||
|
|
@ -513,4 +742,6 @@ summary:hover {
|
|||
.footer-nav { flex-direction: column; }
|
||||
.footer-nav .btn { width: 100%; justify-content: center; }
|
||||
.extension-grid { grid-template-columns: 1fr; }
|
||||
.hud-header { flex-direction: column; align-items: flex-start; }
|
||||
.hud-stats { width: 100%; justify-content: space-between; }
|
||||
}
|
||||
|
|
|
|||
312
gamification.py
Normal file
312
gamification.py
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
"""
|
||||
🎮 PERSISTENTES GAMIFICATION-SYSTEM FÜR PYTHON LERNPORTAL 🎮
|
||||
============================================================
|
||||
Wissenschaftlich fundiert nach der Selbstbestimmungstheorie (Deci & Ryan)
|
||||
und dem Octalysis-Framework (Yu-kai Chou).
|
||||
|
||||
Funktionsweise:
|
||||
- Speichert den Spielstand lokal in '.gamification.json'.
|
||||
- Schreibt zusätzlich 'assets/gamification_data.js', damit index.html auch
|
||||
ohne Webserver (per Doppelklick / file://) ohne CORS-Probleme darauf zugreifen kann!
|
||||
- Verwaltet XP, Level-Aufstiege, Streaks und 14 didaktische Trophäen (Badges).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
ROOT_DIR = Path(__file__).parent.resolve()
|
||||
DATA_JSON_PATH = ROOT_DIR / ".gamification.json"
|
||||
DATA_JS_PATH = ROOT_DIR / "assets" / "gamification_data.js"
|
||||
|
||||
# 10 Entwickler-Level mit XP-Schwellen und Titeln
|
||||
LEVEL_STUFEN = [
|
||||
{"level": 1, "min_xp": 0, "max_xp": 150, "titel": "Code-Küken 🐣", "rang": "Bronze I"},
|
||||
{"level": 2, "min_xp": 151, "max_xp": 400, "titel": "Code-Detektivin 🔍", "rang": "Bronze II"},
|
||||
{"level": 3, "min_xp": 401, "max_xp": 800, "titel": "Logik-Bastlerin ⚙️", "rang": "Silber I"},
|
||||
{"level": 4, "min_xp": 801, "max_xp": 1300, "titel": "Klassen-Baumeisterin 🏗️", "rang": "Silber II"},
|
||||
{"level": 5, "min_xp": 1301, "max_xp": 2000, "titel": "Git-Strategin 🌿", "rang": "Gold I"},
|
||||
{"level": 6, "min_xp": 2001, "max_xp": 2900, "titel": "TDD-Qualitätsprüferin 🧪", "rang": "Gold II"},
|
||||
{"level": 7, "min_xp": 2901, "max_xp": 4000, "titel": "Vererbungs-Meisterin 🧬", "rang": "Platin I"},
|
||||
{"level": 8, "min_xp": 4001, "max_xp": 5300, "titel": "Exception-Wächterin 🛡️", "rang": "Platin II"},
|
||||
{"level": 9, "min_xp": 5301, "max_xp": 6800, "titel": "GUI-Entwicklerin 🖥️", "rang": "Diamant"},
|
||||
{"level": 10, "min_xp": 6801, "max_xp": 99999, "titel": "Software-Architektin 🏆", "rang": "Großmeisterin"},
|
||||
]
|
||||
|
||||
# 14 freischaltbare Trophäen mit didaktischen Kriterien
|
||||
BADGES_KATALOG = {
|
||||
"rechenkonegin": {
|
||||
"id": "rechenkonegin",
|
||||
"icon": "🧮",
|
||||
"name": "Rechenkönigin",
|
||||
"desc": "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["g01_erste_schritte_taschenrechner", "g02_variablen_und_datentypen"]
|
||||
},
|
||||
"dialog_profi": {
|
||||
"id": "dialog_profi",
|
||||
"icon": "💬",
|
||||
"name": "Dialog-Profi",
|
||||
"desc": "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": ["g03_ein_und_ausgabe"]
|
||||
},
|
||||
"weichenstellerin": {
|
||||
"id": "weichenstellerin",
|
||||
"icon": "🚦",
|
||||
"name": "Weichenstellerin",
|
||||
"desc": "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
|
||||
"xp_bonus": 75,
|
||||
"kriterium": ["g04_verzweigungen_und_bedingungen"]
|
||||
},
|
||||
"schleifen_dompteurin": {
|
||||
"id": "schleifen_dompteurin",
|
||||
"icon": "🎡",
|
||||
"name": "Schleifen-Dompteurin",
|
||||
"desc": "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["g05_schleifen_und_wiederholungen"]
|
||||
},
|
||||
"funktions_zauberin": {
|
||||
"id": "funktions_zauberin",
|
||||
"icon": "🪄",
|
||||
"name": "Funktions-Zauberin",
|
||||
"desc": "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["g06_funktionen_und_module"]
|
||||
},
|
||||
"listen_archivarin": {
|
||||
"id": "listen_archivarin",
|
||||
"icon": "📋",
|
||||
"name": "Listen-Archivarin",
|
||||
"desc": "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert).",
|
||||
"xp_bonus": 120,
|
||||
"kriterium": ["g07_listen_und_sequenzen", "g08_textverarbeitung_und_strings"]
|
||||
},
|
||||
"daten_strategin": {
|
||||
"id": "daten_strategin",
|
||||
"icon": "🗃️",
|
||||
"name": "Daten-Strategin",
|
||||
"desc": "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
|
||||
"xp_bonus": 150,
|
||||
"kriterium": ["g09_dictionaries_und_sets", "g10_comprehensions_datum_algorithmen"]
|
||||
},
|
||||
"bug_jaegerin": {
|
||||
"id": "bug_jaegerin",
|
||||
"icon": "🔍",
|
||||
"name": "Bug-Jägerin",
|
||||
"desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["00_fehlersuche_und_grundlagen"]
|
||||
},
|
||||
"erste_architektin": {
|
||||
"id": "erste_architektin",
|
||||
"icon": "🏗️",
|
||||
"name": "Erste Architektin",
|
||||
"desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": ["01_einstieg_klassen", "02_init_und_self", "03_methoden_und_verhalten"]
|
||||
},
|
||||
"tamagotchi_mama": {
|
||||
"id": "tamagotchi_mama",
|
||||
"icon": "🥚",
|
||||
"name": "Tamagotchi-Mama",
|
||||
"desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
|
||||
"xp_bonus": 250,
|
||||
"kriterium": ["06_abschlussprojekt_tamagotchi"]
|
||||
},
|
||||
"zeit_reisende": {
|
||||
"id": "zeit_reisende",
|
||||
"icon": "🌿",
|
||||
"name": "Git-Zeitreisende",
|
||||
"desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
|
||||
"xp_bonus": 100,
|
||||
"kriterium": ["04_str_und_darstellung"]
|
||||
},
|
||||
"qualitaets_garantin": {
|
||||
"id": "qualitaets_garantin",
|
||||
"icon": "🧪",
|
||||
"name": "TDD-Qualitätsgarantin",
|
||||
"desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
|
||||
"xp_bonus": 200,
|
||||
"kriterium": ["09_eigene_unit_tests_schreiben"]
|
||||
},
|
||||
"oop_grossmeisterin": {
|
||||
"id": "oop_grossmeisterin",
|
||||
"icon": "🧬",
|
||||
"name": "Vererbungs-Koryphäe",
|
||||
"desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
|
||||
"xp_bonus": 300,
|
||||
"kriterium": ["10_vererbung_und_super", "11_polymorphie_und_interfaces", "12_exceptions_und_fehlerbehandlung"]
|
||||
},
|
||||
"software_architektin": {
|
||||
"id": "software_architektin",
|
||||
"icon": "🏆",
|
||||
"name": "Meister-Entwicklerin",
|
||||
"desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
|
||||
"xp_bonus": 500,
|
||||
"kriterium": ["16_master_abschlussprojekt"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GamificationManager:
|
||||
"""Verwaltet den persistenten Gamification-Status."""
|
||||
|
||||
def __init__(self):
|
||||
self.state = self._load_state()
|
||||
self.save_state()
|
||||
|
||||
def _default_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"spieler_name": "Python-Entwicklerin",
|
||||
"xp": 0,
|
||||
"level": 1,
|
||||
"titel": "Code-Küken 🐣",
|
||||
"rang": "Bronze I",
|
||||
"geloeste_kapitel": [],
|
||||
"bestandene_tests": 0,
|
||||
"freigeschaltete_badges": [],
|
||||
"streak_tage": 1,
|
||||
"letzter_aktiver_tag": str(date.today()),
|
||||
"historie": [],
|
||||
"zuletzt_aktualisiert": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
def _load_state(self) -> Dict[str, Any]:
|
||||
if DATA_JSON_PATH.exists():
|
||||
try:
|
||||
with open(DATA_JSON_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return {**self._default_state(), **data}
|
||||
except Exception:
|
||||
pass
|
||||
return self._default_state()
|
||||
|
||||
def save_state(self):
|
||||
"""Speichert den Status in JSON und exportiert gleichzeitig gamification_data.js für den Browser."""
|
||||
self.state["zuletzt_aktualisiert"] = datetime.now().isoformat()
|
||||
|
||||
# 1. JSON speichern
|
||||
try:
|
||||
with open(DATA_JSON_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(self.state, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Warnung beim Speichern von .gamification.json: {e}")
|
||||
|
||||
# 2. JS-Export für lokale Browser-Nutzung (CORS-Frei via file://)
|
||||
try:
|
||||
DATA_JS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
js_content = f"""// Automatisch generierter lokaler Gamification-Spielstand
|
||||
window.GAMIFICATION_DATA = {json.dumps(self.state, indent=2, ensure_ascii=False)};
|
||||
window.GAMIFICATION_BADGES_KATALOG = {json.dumps(BADGES_KATALOG, indent=2, ensure_ascii=False)};
|
||||
window.GAMIFICATION_LEVEL_STUFEN = {json.dumps(LEVEL_STUFEN, indent=2, ensure_ascii=False)};
|
||||
"""
|
||||
with open(DATA_JS_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(js_content)
|
||||
except Exception as e:
|
||||
print(f"Warnung beim Exportieren von gamification_data.js: {e}")
|
||||
|
||||
def get_level_info(self, xp: int) -> Dict[str, Any]:
|
||||
"""Berechnet Level, Titel und Fortschritt zur nächsten Stufe."""
|
||||
for info in LEVEL_STUFEN:
|
||||
if info["min_xp"] <= xp <= info["max_xp"]:
|
||||
naechste_xp = info["max_xp"] + 1
|
||||
xp_in_level = xp - info["min_xp"]
|
||||
xp_fuer_level = naechste_xp - info["min_xp"]
|
||||
prozent = min(100, int((xp_in_level / xp_fuer_level) * 100)) if xp_fuer_level > 0 else 100
|
||||
return {
|
||||
"level": info["level"],
|
||||
"titel": info["titel"],
|
||||
"rang": info["rang"],
|
||||
"aktuelle_xp": xp,
|
||||
"min_xp": info["min_xp"],
|
||||
"max_xp": info["max_xp"],
|
||||
"naechste_stufe_xp": naechste_xp,
|
||||
"prozent": prozent
|
||||
}
|
||||
# Fallback höchstes Level
|
||||
return {
|
||||
"level": 10,
|
||||
"titel": "Software-Architektin 🏆",
|
||||
"rang": "Großmeisterin",
|
||||
"aktuelle_xp": xp,
|
||||
"min_xp": 6801,
|
||||
"max_xp": 99999,
|
||||
"naechste_stufe_xp": 99999,
|
||||
"prozent": 100
|
||||
}
|
||||
|
||||
def sync_mit_test_ergebnissen(self, bestandene_kapitel_ordner: List[str], anzahl_bestandene_tests: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Gleicht die aktuellen Testergebnisse ab, vergibt XP und schaltet Badges frei.
|
||||
Gibt ein Event-Dictionary für feierliche Terminal-Meldungen zurück.
|
||||
"""
|
||||
alte_geloeste = set(self.state.get("geloeste_kapitel", []))
|
||||
neue_geloeste = set(bestandene_kapitel_ordner)
|
||||
alte_badges = set(self.state.get("freigeschaltete_badges", []))
|
||||
altes_level = self.state.get("level", 1)
|
||||
|
||||
neu_geloest = neue_geloeste - alte_geloeste
|
||||
|
||||
# Basis-XP Berechnung:
|
||||
# 100 XP pro gelöstem Kapitel + 10 XP pro bestandenem Test
|
||||
berechnete_xp = (len(neue_geloeste) * 100) + (anzahl_bestandene_tests * 10)
|
||||
|
||||
# Badges überprüfen und Bonus-XP addieren
|
||||
neu_freigeschaltet_badges = []
|
||||
for badge_id, badge in BADGES_KATALOG.items():
|
||||
kriterien = badge["kriterium"]
|
||||
# Alle Kriterien-Ordner müssen gelöst sein
|
||||
if all(k in neue_geloeste for k in kriterien):
|
||||
if badge_id not in alte_badges:
|
||||
neu_freigeschaltet_badges.append(badge)
|
||||
berechnete_xp += badge["xp_bonus"]
|
||||
|
||||
# Streak-Berechnung
|
||||
heute = str(date.today())
|
||||
letzter_tag = self.state.get("letzter_aktiver_tag", heute)
|
||||
if letzter_tag != heute:
|
||||
try:
|
||||
diff = (date.today() - date.fromisoformat(letzter_tag)).days
|
||||
if diff == 1:
|
||||
self.state["streak_tage"] = self.state.get("streak_tage", 1) + 1
|
||||
elif diff > 1:
|
||||
self.state["streak_tage"] = 1
|
||||
except Exception:
|
||||
self.state["streak_tage"] = 1
|
||||
self.state["letzter_aktiver_tag"] = heute
|
||||
|
||||
# Level-Update
|
||||
lvl_info = self.get_level_info(berechnete_xp)
|
||||
neues_level = lvl_info["level"]
|
||||
|
||||
# Status aktualisieren
|
||||
self.state["xp"] = berechnete_xp
|
||||
self.state["level"] = neues_level
|
||||
self.state["titel"] = lvl_info["titel"]
|
||||
self.state["rang"] = lvl_info["rang"]
|
||||
self.state["geloeste_kapitel"] = list(neue_geloeste)
|
||||
self.state["bestandene_tests"] = anzahl_bestandene_tests
|
||||
self.state["freigeschaltete_badges"] = [
|
||||
b_id for b_id, b in BADGES_KATALOG.items()
|
||||
if all(k in neue_geloeste for k in b["kriterium"])
|
||||
]
|
||||
|
||||
self.save_state()
|
||||
|
||||
return {
|
||||
"neue_kapitel": list(neu_geloest),
|
||||
"neue_badges": neu_freigeschaltet_badges,
|
||||
"level_up": neues_level > altes_level,
|
||||
"altes_level": altes_level,
|
||||
"neues_level": neues_level,
|
||||
"lvl_info": lvl_info,
|
||||
"gesamt_xp": berechnete_xp
|
||||
}
|
||||
|
||||
|
||||
# Singleton-Instanz
|
||||
manager = GamificationManager()
|
||||
|
|
@ -518,5 +518,8 @@ object has no attribute 'gang'</code></pre>
|
|||
|
||||
</main>
|
||||
|
||||
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
|
||||
<script src="assets/gamification_data.js"></script>
|
||||
<script src="assets/gamification.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
85
profil.py
Executable file
85
profil.py
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
🪪 ENTWICKLER-PROFIL & GAMIFICATION PASS 🪪
|
||||
===========================================
|
||||
Führe diesen Befehl im Terminal aus, um deinen aktuellen Level,
|
||||
deine XP, deinen Fortschrittsbalken und deine Trophäen anzuzeigen:
|
||||
python3 profil.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from gamification import GamificationManager, BADGES_KATALOG
|
||||
|
||||
# ANSI Farbcodes für ansprechende Terminal-Optik
|
||||
C_RESET = "\033[0m"
|
||||
C_BOLD = "\033[1m"
|
||||
C_CYAN = "\033[36m"
|
||||
C_GREEN = "\033[32m"
|
||||
C_YELLOW = "\033[33m"
|
||||
C_BLUE = "\033[34m"
|
||||
C_MAGENTA = "\033[35m"
|
||||
C_RED = "\033[31m"
|
||||
|
||||
|
||||
def erstelle_balken(prozent: int, breite: int = 30) -> str:
|
||||
"""Erzeugt einen visuellen Unicode-Fortschrittsbalken."""
|
||||
gefuellt = int((prozent / 100) * breite)
|
||||
leer = breite - gefuellt
|
||||
return f"{C_GREEN}{'█' * gefuellt}{C_RESET}{'░' * leer}"
|
||||
|
||||
|
||||
def main():
|
||||
manager = GamificationManager()
|
||||
state = manager.state
|
||||
xp = state.get("xp", 0)
|
||||
lvl_info = manager.get_level_info(xp)
|
||||
geloest = state.get("geloeste_kapitel", [])
|
||||
unlocked_badges = set(state.get("freigeschaltete_badges", []))
|
||||
streak = state.get("streak_tage", 1)
|
||||
name = state.get("spieler_name", "Python-Entwicklerin")
|
||||
titel = lvl_info["titel"]
|
||||
rang = lvl_info["rang"]
|
||||
tests_count = state.get("bestandene_tests", 0)
|
||||
kapitel_str = f"{len(geloest)} / 27 Kapiteln gelöst ({tests_count} Tests)"
|
||||
streak_str = f"{streak} Tag(e) in Folge aktiv!"
|
||||
|
||||
print()
|
||||
print(f"{C_CYAN}╔══════════════════════════════════════════════════════════════════════╗{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} {C_BOLD}🎮 PYTHON ENTWICKLER-PROFIL & MEISTERPASS{C_RESET} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}╠══════════════════════════════════════════════════════════════════════╣{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 👤 {C_BOLD}Entwicklerin:{C_RESET} {name:<48} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 🏅 {C_BOLD}Titel:{C_RESET} {titel:<48} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} ⭐ {C_BOLD}Rang:{C_RESET} {rang:<48} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 🔥 {C_BOLD}Lern-Streak:{C_RESET} {streak_str:<48} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 📚 {C_BOLD}Fortschritt:{C_RESET} {kapitel_str:<48} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}╠══════════════════════════════════════════════════════════════════════╣{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 📈 {C_BOLD}LEVEL {lvl_info['level']:2d} FORTSCHRITT:{C_RESET} {C_CYAN}║{C_RESET}")
|
||||
|
||||
balken = erstelle_balken(lvl_info["prozent"], breite=35)
|
||||
xp_text = f"{xp} / {lvl_info['naechste_stufe_xp']} XP ({lvl_info['prozent']}%)"
|
||||
print(f"{C_CYAN}║{C_RESET} [{balken}] {xp_text:<17} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}╠══════════════════════════════════════════════════════════════════════╣{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} 🏆 {C_BOLD}TROPHÄENRAUM ({len(unlocked_badges)} von {len(BADGES_KATALOG)} freigeschaltet):{C_RESET} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}╟──────────────────────────────────────────────────────────────────────╢{C_RESET}")
|
||||
|
||||
# Badges auflisten
|
||||
for b_id, badge in BADGES_KATALOG.items():
|
||||
if b_id in unlocked_badges:
|
||||
status = f"{C_GREEN}[FREIGESCHALTET]{C_RESET}"
|
||||
icon = badge["icon"]
|
||||
name_colored = f"{C_YELLOW}{badge['name']}{C_RESET}"
|
||||
else:
|
||||
status = f"{C_RESET}[GESPERRT 🔒]{C_RESET}"
|
||||
icon = "🔒"
|
||||
name_colored = f"{badge['name']}"
|
||||
|
||||
print(f"{C_CYAN}║{C_RESET} {icon} {name_colored:<36} {status:>24} {C_CYAN}║{C_RESET}")
|
||||
print(f"{C_CYAN}║{C_RESET} ↳ {badge['desc']:<63} {C_CYAN}║{C_RESET}")
|
||||
|
||||
print(f"{C_CYAN}╚══════════════════════════════════════════════════════════════════════╝{C_RESET}")
|
||||
print(f"💡 {C_BOLD}Tipp:{C_RESET} Löse weitere Aufgaben in 'aufgabe.py' und teste sie mit 'python3 test_all.py'!")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
53
test_all.py
53
test_all.py
|
|
@ -1,14 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
🧪 Gesamttest-Runner für alle Kapitel
|
||||
===================================
|
||||
Führe dieses Skript aus, um zu sehen, welche Kapitel du bereits erfolgreich gelöst hast:
|
||||
🧪 Gesamttest-Runner & Gamification-Synchronisation
|
||||
===================================================
|
||||
Führe dieses Skript aus, um zu sehen, welche Kapitel du bereits erfolgreich gelöst hast
|
||||
und um deine XP, dein Level und deine Trophäen zu aktualisieren:
|
||||
python3 test_all.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from gamification import GamificationManager
|
||||
|
||||
KAPITEL = [
|
||||
("Grundlagen 01: Python als Taschenrechner", "g01_erste_schritte_taschenrechner"),
|
||||
|
|
@ -42,11 +44,13 @@ KAPITEL = [
|
|||
|
||||
def main():
|
||||
root = Path(__file__).parent.resolve()
|
||||
print("=" * 65)
|
||||
print("🎓 PYTHON OOP ÜBUNGSREIHE – GESAMTFORTSCHRITT")
|
||||
print("=" * 65)
|
||||
print("=" * 68)
|
||||
print("🎓 PYTHON LERNPORTAL – GESAMTFORTSCHRITT & TESTS")
|
||||
print("=" * 68)
|
||||
|
||||
gesamt_bestanden = 0
|
||||
bestandene_ordner = []
|
||||
gesamt_tests_bestanden = 0
|
||||
|
||||
for titel, ordner in KAPITEL:
|
||||
kapitel_pfad = root / ordner
|
||||
|
|
@ -74,19 +78,40 @@ def main():
|
|||
erfolgreich = result.wasSuccessful() and anzahl_tests > 0
|
||||
|
||||
if erfolgreich:
|
||||
print(f" ✅ {titel:45s} ({anzahl_tests}/{anzahl_tests} Tests OK)")
|
||||
print(f" ✅ {titel:48s} ({anzahl_tests}/{anzahl_tests} Tests OK)")
|
||||
gesamt_bestanden += 1
|
||||
bestandene_ordner.append(ordner)
|
||||
gesamt_tests_bestanden += anzahl_tests
|
||||
else:
|
||||
fehlgeschlagen = len(result.failures) + len(result.errors)
|
||||
print(f" ⏳ {titel:45s} (Noch offen / {fehlgeschlagen} Fehler)")
|
||||
print(f" ⏳ {titel:48s} (Noch offen / {fehlgeschlagen} Fehler)")
|
||||
|
||||
print("=" * 65)
|
||||
print("=" * 68)
|
||||
print(f"Ergebnis: {gesamt_bestanden} von {len(KAPITEL)} Kapiteln vollständig gelöst!")
|
||||
if gesamt_bestanden == len(KAPITEL):
|
||||
print("🏆 PERFEKT! Du hast alle Aufgaben mit Bravour gemeistert! 🎉")
|
||||
else:
|
||||
print("💡 Öffne das nächste offene Kapitel und starte mit aufgabe.py!")
|
||||
print("=" * 65)
|
||||
|
||||
# Gamification-Status aktualisieren
|
||||
gm = GamificationManager()
|
||||
events = gm.sync_mit_test_ergebnissen(bestandene_ordner, gesamt_tests_bestanden)
|
||||
lvl = events["lvl_info"]
|
||||
|
||||
print()
|
||||
print("🎮 GAMIFICATION STATUS:")
|
||||
print(f" ⭐ Level {lvl['level']} ({lvl['titel']}) | Rang: {lvl['rang']}")
|
||||
print(f" 💎 XP: {events['gesamt_xp']} XP (Nächste Stufe bei {lvl['naechste_stufe_xp']} XP)")
|
||||
print(f" 🏆 Trophäen: {len(gm.state.get('freigeschaltete_badges', []))} / 14 freigeschaltet")
|
||||
|
||||
if events.get("level_up"):
|
||||
print(f"\n🎉🎉🎉 LEVEL UP! Du bist jetzt Level {lvl['level']} ({lvl['titel']})! 🎉🎉🎉")
|
||||
|
||||
if events.get("neue_badges"):
|
||||
print("\n🎖️ NEU FREIGESCHALTETE TROPHÄEN:")
|
||||
for b in events["neue_badges"]:
|
||||
print(f" ✨ {b['icon']} {b['name']} (+{b['xp_bonus']} XP Bonus!)")
|
||||
print(f" ↳ {b['desc']}")
|
||||
|
||||
print("-" * 68)
|
||||
print("💡 Zeige deinen vollständigen Pass mit: python3 profil.py")
|
||||
print("=" * 68)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Reference in a new issue