diff --git a/.gamification.json b/.gamification.json new file mode 100644 index 0000000..15de29d --- /dev/null +++ b/.gamification.json @@ -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" +} \ No newline at end of file diff --git a/assets/gamification.js b/assets/gamification.js new file mode 100644 index 0000000..f45ed3c --- /dev/null +++ b/assets/gamification.js @@ -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 = ` +
+
+
+
🎮
+
+
${data.spieler_name || "Python-Entwicklerin"}
+
${lvlInfo.titel} • ${lvlInfo.rang}
+
+
+
+
+ ⭐ Level ${lvlInfo.level} + ${data.xp || 0} XP +
+
+ 🔥 ${data.streak_tage || 1} Tage + Lern-Streak +
+
+ 🏆 ${badgeCount}/${totalBadges} + Trophäen +
+
+
+ +
+
+ Fortschritt zu Level ${lvlInfo.level + 1} + ${data.xp || 0} / ${lvlInfo.naechste_stufe_xp} XP (${lvlInfo.prozent}%) +
+
+
+
+
+ + + + +
+ `; + + // 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(); + } +})(); diff --git a/assets/gamification_data.js b/assets/gamification_data.js new file mode 100644 index 0000000..ef17816 --- /dev/null +++ b/assets/gamification_data.js @@ -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" + } +]; diff --git a/assets/style.css b/assets/style.css index b0a6a32..d10bdc1 100644 --- a/assets/style.css +++ b/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; } } diff --git a/gamification.py b/gamification.py new file mode 100644 index 0000000..f594756 --- /dev/null +++ b/gamification.py @@ -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() diff --git a/index.html b/index.html index c9baa65..5ff0391 100644 --- a/index.html +++ b/index.html @@ -518,5 +518,8 @@ object has no attribute 'gang' + + + diff --git a/profil.py b/profil.py new file mode 100755 index 0000000..00dc599 --- /dev/null +++ b/profil.py @@ -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() diff --git a/test_all.py b/test_all.py index 22d926f..7bdecac 100644 --- a/test_all.py +++ b/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__":