Aufgaben-Python/gamification.py

362 lines
15 KiB
Python

"""
🎮 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 professionellen IT-Kompetenzstufen
LEVEL_STUFEN = [
{"level": 1, "min_xp": 0, "max_xp": 150, "titel": "Junior IT-Trainee I: Syntax & Basistypen", "rang": "Stufe 1"},
{"level": 2, "min_xp": 151, "max_xp": 400, "titel": "Junior IT-Trainee II: Kontrollfluss & Logik", "rang": "Stufe 2"},
{"level": 3, "min_xp": 401, "max_xp": 800, "titel": "IT-Specialist: Funktionen & Datenstrukturen", "rang": "Stufe 3"},
{"level": 4, "min_xp": 801, "max_xp": 1300, "titel": "Software Developer: OOP-Modellierung", "rang": "Stufe 4"},
{"level": 5, "min_xp": 1301, "max_xp": 2000, "titel": "DevOps Engineer: Versionskontrolle & Tooling", "rang": "Stufe 5"},
{"level": 6, "min_xp": 2001, "max_xp": 2900, "titel": "Quality Engineer: Test-Driven Development (TDD)", "rang": "Stufe 6"},
{"level": 7, "min_xp": 2901, "max_xp": 4000, "titel": "Advanced Engineer: Entwurfsmuster & Polymorphie", "rang": "Stufe 7"},
{"level": 8, "min_xp": 4001, "max_xp": 5300, "titel": "Systems Engineer: Security & Error Handling", "rang": "Stufe 8"},
{"level": 9, "min_xp": 5301, "max_xp": 6800, "titel": "Application Engineer: Schnittstellen & UI", "rang": "Stufe 9"},
{"level": 10, "min_xp": 6801, "max_xp": 99999, "titel": "Certified IT Systems Professional (IHK AP2)", "rang": "Master-Stufe"},
]
# 14 freischaltbare IT-Kompetenz-Zertifikate (abgestimmt auf IHK-Rahmenlehrplan)
BADGES_KATALOG = {
"rechenkonegin": {
"id": "rechenkonegin",
"icon": "🧮",
"name": "Zertifikat: Numerische Typen & Arithmetik",
"desc": "Grundlagen 01 & 02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
"xp_bonus": 100,
"kriterium": [
"lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner",
"lehrpfad_1_grundlagen/02_variablen_und_datentypen"
]
},
"dialog_profi": {
"id": "dialog_profi",
"icon": "💬",
"name": "Zertifikat: Interaktive I/O & String-Interpolation",
"desc": "Grundlagen 03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
"xp_bonus": 75,
"kriterium": ["lehrpfad_1_grundlagen/03_ein_und_ausgabe"]
},
"weichenstellerin": {
"id": "weichenstellerin",
"icon": "🚦",
"name": "Zertifikat: Verzweigungslogik & Boolesche Algebra",
"desc": "Grundlagen 04 abgeschlossen (Bedingungslogik & Kontrollfluss gemeistert).",
"xp_bonus": 75,
"kriterium": ["lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen"]
},
"schleifen_dompteurin": {
"id": "schleifen_dompteurin",
"icon": "🎡",
"name": "Zertifikat: Iterationslogik & Sequenzverarbeitung",
"desc": "Grundlagen 05 abgeschlossen (While- & For-Schleifen gemeistert).",
"xp_bonus": 100,
"kriterium": ["lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen"]
},
"funktions_zauberin": {
"id": "funktions_zauberin",
"icon": "📐",
"name": "Zertifikat: Modulare Funktionsarchitektur",
"desc": "Grundlagen 06 abgeschlossen (Modulare Funktionen & Parameterübergabe).",
"xp_bonus": 100,
"kriterium": ["lehrpfad_1_grundlagen/06_funktionen_und_module"]
},
"listen_archivarin": {
"id": "listen_archivarin",
"icon": "📋",
"name": "Zertifikat: Datenstrukturen: Listen & Slicing",
"desc": "Grundlagen 07 & 08 abgeschlossen (Sequenzen, Listen & Slicing analysiert).",
"xp_bonus": 120,
"kriterium": [
"lehrpfad_1_grundlagen/07_listen_und_sequenzen",
"lehrpfad_1_grundlagen/08_textverarbeitung_und_strings"
]
},
"daten_strategin": {
"id": "daten_strategin",
"icon": "🗃️",
"name": "Zertifikat: Assoziative Arrays: Dicts & Sets",
"desc": "Grundlagen 09 & 10 abgeschlossen (Dictionaries, Sets & Hash-Lookup O(1)).",
"xp_bonus": 150,
"kriterium": [
"lehrpfad_1_grundlagen/09_dictionaries_und_sets",
"lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen"
]
},
"bug_jaegerin": {
"id": "bug_jaegerin",
"icon": "🔍",
"name": "Zertifikat: Systematisches Code-Debugging",
"desc": "Warm-up abgeschlossen (Fehleranalyse & Traceback-Debugging).",
"xp_bonus": 100,
"kriterium": ["lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen"]
},
"erste_architektin": {
"id": "erste_architektin",
"icon": "🏗️",
"name": "Zertifikat: OOP-Grundlagen: Klassen & Kapselung",
"desc": "OOP 01 bis 03 gelöst (Klassendefinition, Konstruktoren & Instanzmethoden).",
"xp_bonus": 200,
"kriterium": [
"lehrpfad_2_oop_einstieg/01_einstieg_klassen",
"lehrpfad_2_oop_einstieg/02_init_und_self",
"lehrpfad_2_oop_einstieg/03_methoden_und_verhalten"
]
},
"tamagotchi_mama": {
"id": "tamagotchi_mama",
"icon": "📦",
"name": "Zertifikat: State-Machine & Domänenmodellierung",
"desc": "OOP Mini-Projekt abgeschlossen (Zustandsverwaltung & Objektinteraktion).",
"xp_bonus": 250,
"kriterium": ["lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi"]
},
"zeit_reisende": {
"id": "zeit_reisende",
"icon": "🌿",
"name": "Zertifikat: String-Repräsentation & Dunder-Methoden",
"desc": "OOP 04 gelöst (Spezielle Methoden __str__, __repr__ implementiert).",
"xp_bonus": 100,
"kriterium": ["lehrpfad_2_oop_einstieg/04_str_und_darstellung"]
},
"qualitaets_garantin": {
"id": "qualitaets_garantin",
"icon": "🧪",
"name": "Zertifikat: Automatisierte Testsuites & TDD",
"desc": "OOP 09 abgeschlossen (Eigene professionelle Unit Tests mit Assertions).",
"xp_bonus": 200,
"kriterium": ["lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben"]
},
"oop_grossmeisterin": {
"id": "oop_grossmeisterin",
"icon": "🧬",
"name": "Zertifikat: Fortgeschrittene OOP & Robustes Error-Handling",
"desc": "OOP 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
"xp_bonus": 300,
"kriterium": [
"lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super",
"lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces",
"lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung"
]
},
"software_architektin": {
"id": "software_architektin",
"icon": "🏆",
"name": "Zertifikat: Master-Projekt Software Engineering",
"desc": "Master-Projekt abgeschlossen & vollwertige Anwendungsarchitektur gebaut!",
"xp_bonus": 500,
"kriterium": ["lehrpfad_3_fortgeschrittenes_oop/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": "IT-Entwickler",
"xp": 0,
"level": 1,
"titel": "Junior IT-Trainee I: Syntax & Basistypen",
"rang": "Stufe 1",
"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"]
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
}
def report_single_chapter_result(kapitel_pfad_relativ: str, tests_erfolgreich: bool, tests_anzahl: int):
"""
Wird von einzelnen test_aufgabe.py Skripten aufgerufen, wenn sie direkt ausgeführt werden.
Aktualisiert den Spielstand und gibt motivierendes Feedback im Terminal aus.
"""
gm = GamificationManager()
aktuelle_geloeste = set(gm.state.get("geloeste_kapitel", []))
if tests_erfolgreich and tests_anzahl > 0:
war_bereits_geloest = kapitel_pfad_relativ in aktuelle_geloeste
aktuelle_geloeste.add(kapitel_pfad_relativ)
# Gesamtbestandene Tests schätzen/updaten
alte_tests = gm.state.get("bestandene_tests", 0)
neue_tests = alte_tests if war_bereits_geloest else alte_tests + tests_anzahl
events = gm.sync_mit_test_ergebnissen(list(aktuelle_geloeste), neue_tests)
lvl = events["lvl_info"]
print("\n" + "═" * 60)
print(f"🎉 KAPITEL BESTANDEN! ⭐ {lvl['titel']} (Level {lvl['level']})")
print(f"💎 Aktuelle XP: {events['gesamt_xp']} XP | Rang: {lvl['rang']}")
if events.get("level_up"):
print(f"🚀 LEVEL UP! Du bist aufgestiegen zu Level {lvl['level']}!")
if events.get("neue_badges"):
for b in events["neue_badges"]:
print(f"🏆 NEUE TROPHÄE: {b['icon']} {b['name']} (+{b['xp_bonus']} XP Bonus!)")
print(f"💡 Zeige deinen Fortschrittspass mit: python3 profil.py")
print("═" * 60 + "\n")
# Singleton-Instanz
manager = GamificationManager()