""" 🎮 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()