Implementiere persistente Gamification (Level 1-10, XP, 14 Badges, profil.py CLI & Web-HUD für lokale Offline-Nutzung)

This commit is contained in:
Alex Mueller 2026-08-29 14:51:06 +02:00
parent 478b1d95fe
commit 86532b15bb
8 changed files with 1125 additions and 14 deletions

14
.gamification.json Normal file
View 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
View 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} &bull; <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
View 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"
}
];

View file

@ -506,6 +506,235 @@ summary:hover {
flex-wrap: wrap; 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 */ /* Responsive */
@media (max-width: 600px) { @media (max-width: 600px) {
header h1 { font-size: 1.6rem; } header h1 { font-size: 1.6rem; }
@ -513,4 +742,6 @@ summary:hover {
.footer-nav { flex-direction: column; } .footer-nav { flex-direction: column; }
.footer-nav .btn { width: 100%; justify-content: center; } .footer-nav .btn { width: 100%; justify-content: center; }
.extension-grid { grid-template-columns: 1fr; } .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
View 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()

View file

@ -518,5 +518,8 @@ object has no attribute 'gang'</code></pre>
</main> </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> </body>
</html> </html>

85
profil.py Executable file
View 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()

View file

@ -1,14 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
🧪 Gesamttest-Runner für alle Kapitel 🧪 Gesamttest-Runner & Gamification-Synchronisation
=================================== ===================================================
Führe dieses Skript aus, um zu sehen, welche Kapitel du bereits erfolgreich gelöst hast: 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 python3 test_all.py
""" """
import sys import sys
import unittest import unittest
from pathlib import Path from pathlib import Path
from gamification import GamificationManager
KAPITEL = [ KAPITEL = [
("Grundlagen 01: Python als Taschenrechner", "g01_erste_schritte_taschenrechner"), ("Grundlagen 01: Python als Taschenrechner", "g01_erste_schritte_taschenrechner"),
@ -42,11 +44,13 @@ KAPITEL = [
def main(): def main():
root = Path(__file__).parent.resolve() root = Path(__file__).parent.resolve()
print("=" * 65) print("=" * 68)
print("🎓 PYTHON OOP ÜBUNGSREIHE – GESAMTFORTSCHRITT") print("🎓 PYTHON LERNPORTAL – GESAMTFORTSCHRITT &amp; TESTS")
print("=" * 65) print("=" * 68)
gesamt_bestanden = 0 gesamt_bestanden = 0
bestandene_ordner = []
gesamt_tests_bestanden = 0
for titel, ordner in KAPITEL: for titel, ordner in KAPITEL:
kapitel_pfad = root / ordner kapitel_pfad = root / ordner
@ -74,19 +78,40 @@ def main():
erfolgreich = result.wasSuccessful() and anzahl_tests > 0 erfolgreich = result.wasSuccessful() and anzahl_tests > 0
if erfolgreich: 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 gesamt_bestanden += 1
bestandene_ordner.append(ordner)
gesamt_tests_bestanden += anzahl_tests
else: else:
fehlgeschlagen = len(result.failures) + len(result.errors) 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!") 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! 🎉") # Gamification-Status aktualisieren
else: gm = GamificationManager()
print("💡 Öffne das nächste offene Kapitel und starte mit aufgabe.py!") events = gm.sync_mit_test_ergebnissen(bestandene_ordner, gesamt_tests_bestanden)
print("=" * 65) 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__": if __name__ == "__main__":