Initial commit; create Website for learning python.
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
# Betriebssystem und Editor
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Python/lokale Server-Artefakte
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Lokale Notizen und Exporte
|
||||
*.log
|
||||
tmp/
|
||||
dist/
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Python-Abenteuer fuer Raphael
|
||||
|
||||
Ein lokaler, browserbasierter Python-Lernkurs fuer Raphael (12 Jahre). Der Kurs fuehrt in 16 Missionen von `print()` bis zu Funktionen, Listen, Bedingungen, Schleifen, `input()` und `random`.
|
||||
|
||||
## Starten
|
||||
|
||||
Variante 1: Datei direkt im Browser oeffnen:
|
||||
|
||||
```bash
|
||||
xdg-open index.html
|
||||
```
|
||||
|
||||
Variante 2: kleiner lokaler Webserver:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
Danach im Browser öffnen:
|
||||
|
||||
```text
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
## Wichtige Hinweise
|
||||
|
||||
- Fortschritt, Abzeichen und Code-Entwuerfe werden lokal im Browser via `localStorage` gespeichert.
|
||||
- Die Python-Ausfuehrung im Browser nutzt Pyodide von einem CDN. Dafuer braucht der Browser beim ersten Laden Internetzugang.
|
||||
- Ohne Pyodide bleiben Lektionen, Aufgaben und Hinweise lesbar; fuer die interaktiven Code-Uebungen wird Pyodide benoetigt.
|
||||
- Es gibt keinen externen KI/API-Aufruf. Der Mentor "Professor Python" arbeitet lokal mit kurzen, regelbasierten Hinweisen.
|
||||
|
||||
## Dateien
|
||||
|
||||
- `index.html` - komplette Single-Page-App
|
||||
- `docs/kurs-dokumentation.md` - didaktische und technische Dokumentation
|
||||
- `.gitignore` - sinnvolle Ignorierregeln fuer lokale Artefakte
|
||||
|
||||
## In Gitea hochladen
|
||||
|
||||
Falls das Verzeichnis noch kein Git-Repository ist:
|
||||
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initialer Python-Lernkurs fuer Raphael"
|
||||
git remote add origin <gitea-repo-url>
|
||||
git push -u origin main
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
# Kurs-Dokumentation
|
||||
|
||||
## Zielgruppe
|
||||
|
||||
Raphael ist 12 Jahre alt und soll Python praktisch, spielerisch und mit schnellen Erfolgserlebnissen lernen. Der Kurs verwendet eine Agenten-Geschichte, kurze Konzepte, direkt ausfuehrbare Uebungen und Mini-Tests.
|
||||
|
||||
## Didaktischer Aufbau
|
||||
|
||||
Jede Mission besteht aus fuenf Phasen:
|
||||
|
||||
1. Story: Motivation und Problemrahmen.
|
||||
2. Lernen: Ein kleines Konzept mit Beispielcode.
|
||||
3. Ueben: Raphael schreibt oder veraendert Code.
|
||||
4. Test: Eine kurze Verstaendnisfrage.
|
||||
5. Fertig: XP, Level und naechste Mission.
|
||||
|
||||
Die Reihenfolge ist bewusst progressiv:
|
||||
|
||||
- Ausgabe mit `print()`
|
||||
- Variablen und Datentypen
|
||||
- Rechnen
|
||||
- Fehlermeldungen lesen
|
||||
- Eingaben mit `input()`
|
||||
- Bedingungen mit `if`, `else`, `and`, `or`
|
||||
- Schleifen
|
||||
- Listen
|
||||
- Funktionen und Parameter
|
||||
- Kombination in Quiz, Text-Adventure, Passwortgenerator und Abschlussprojekt
|
||||
|
||||
## Paedagogische Regeln
|
||||
|
||||
- Kurze Aufgaben statt langer Theorie.
|
||||
- Fehler werden als Hinweise dargestellt, nicht als Scheitern.
|
||||
- Der Mentor gibt keine Komplettloesungen, sondern Denkstufen.
|
||||
- XP und Abzeichen motivieren, ersetzen aber nicht die eigentliche Uebung.
|
||||
- Aufgaben enthalten Hilfen in mehreren Stufen, damit Raphael zuerst selbst probieren kann.
|
||||
|
||||
## Technische Architektur
|
||||
|
||||
Die App ist eine einzelne HTML-Datei:
|
||||
|
||||
- HTML/CSS/JavaScript ohne Build-Schritt.
|
||||
- `localStorage` fuer Fortschritt und Code-Entwuerfe.
|
||||
- Pyodide fuer echte Python-Ausfuehrung im Browser.
|
||||
- Regelbasierter lokaler Mentor statt externer API.
|
||||
- Responsive Layout fuer Desktop, Tablet und schmale Bildschirme.
|
||||
|
||||
## Resilienz
|
||||
|
||||
- Wenn Pyodide nicht geladen werden kann, zeigt die App einen klaren Status und verhindert falsche Erfolgsmeldungen beim Ausfuehren.
|
||||
- Falsche Quiz-Antworten sperren die Mission nicht; Raphael kann erneut versuchen.
|
||||
- Code-Entwuerfe werden pro Mission gespeichert.
|
||||
- Texte werden beim Rendern escaped, damit Codebeispiele nicht als HTML interpretiert werden.
|
||||
- Die App benoetigt keinen lokalen Server, funktioniert aber auch mit `python3 -m http.server`.
|
||||
|
||||
## Weiterentwicklung
|
||||
|
||||
Sinnvolle naechste Schritte:
|
||||
|
||||
- Weitere Missionen zu Dictionaries, Dateien und einfachen Spielen.
|
||||
- Optionales lokales Vendoring von Pyodide, falls der Kurs komplett offline laufen soll.
|
||||
- Kleine Beispiel-Loesungen in einer separaten, nicht direkt sichtbaren Lehrerdatei.
|
||||
+849
@@ -0,0 +1,849 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Python-Abenteuer – Agent Raphael</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg:#12152E; --sf:#1B1F4A; --sf2:#232865; --bd:#2E3580;
|
||||
--cy:#4FC3F7; --cy-d:#1A3A52; --go:#FFC107; --go-d:#3A2800;
|
||||
--gr:#66BB6A; --gr-d:#1A3A1D; --rd:#EF5350; --rd-d:#3A1514;
|
||||
--pu:#CE93D8; --pu-d:#3A2042; --tx:#E8EAF6; --mu:#9FA8DA;
|
||||
--cb:#080C1E;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{background:var(--bg);color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;min-height:100vh;font-size:16px}
|
||||
.orb{font-family:Orbitron,Consolas,monospace}
|
||||
.mono{font-family:'JetBrains Mono','Fira Code',Consolas,monospace}
|
||||
.wrap{max-width:940px;margin:0 auto;padding:1.25rem 1rem}
|
||||
.card{background:var(--sf);border:1px solid var(--bd);border-radius:16px;padding:1.5rem}
|
||||
.card-sm{background:var(--sf);border:1px solid var(--bd);border-radius:12px;padding:1rem}
|
||||
|
||||
/* XP bar */
|
||||
.xp-track{background:var(--sf2);border-radius:999px;height:10px;overflow:hidden;margin:.5rem 0}
|
||||
.xp-fill{height:100%;background:linear-gradient(90deg,var(--go),#FF8C00);border-radius:999px;transition:width .6s ease}
|
||||
|
||||
/* Mission grid */
|
||||
.mgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:.625rem}
|
||||
.mc{background:var(--sf);border:1.5px solid var(--bd);border-radius:12px;padding:.875rem .75rem;cursor:pointer;transition:all .18s;position:relative;text-align:center}
|
||||
.mc:hover:not(.locked){border-color:var(--cy);transform:translateY(-2px)}
|
||||
.mc.done{border-color:var(--gr);background:var(--gr-d)}
|
||||
.mc.locked{opacity:.38;cursor:not-allowed;pointer-events:none}
|
||||
.mc.curr{border-color:var(--cy);background:var(--cy-d)}
|
||||
.mc .me{font-size:1.5rem;display:block;margin-bottom:.25rem}
|
||||
.mc .mt{font-size:.7rem;font-weight:700;color:var(--cy);display:block}
|
||||
.mc .ms{font-size:.65rem;color:var(--mu);display:block;margin-top:.15rem}
|
||||
.mc .check{position:absolute;top:.4rem;right:.5rem;color:var(--gr);font-size:.75rem}
|
||||
|
||||
/* Buttons */
|
||||
.btn{padding:.7rem 1.4rem;border-radius:10px;border:none;font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.95rem;font-weight:800;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:.4rem}
|
||||
.btn:hover{transform:translateY(-1px)}
|
||||
.btn:active{transform:translateY(0)}
|
||||
.btn-cy{background:var(--cy);color:#080C1E}
|
||||
.btn-gr{background:var(--gr);color:#fff}
|
||||
.btn-go{background:var(--go);color:#080C1E}
|
||||
.btn-gh{background:transparent;color:var(--mu);border:1px solid var(--bd)}
|
||||
.btn-rd{background:var(--rd);color:#fff}
|
||||
.btn-pu{background:var(--pu);color:#080C1E}
|
||||
.btn-sm{padding:.5rem 1rem;font-size:.85rem}
|
||||
|
||||
/* Code editor */
|
||||
.ced{background:var(--cb);border:1px solid var(--bd);border-radius:12px;overflow:hidden}
|
||||
.ced-hdr{background:var(--sf2);padding:.5rem .875rem;display:flex;align-items:center;gap:.5rem;border-bottom:1px solid var(--bd)}
|
||||
.dot{width:9px;height:9px;border-radius:50%}
|
||||
.cta{width:100%;min-height:160px;background:var(--cb);color:#A8E6A3;border:none;padding:1rem;font-family:'JetBrains Mono',monospace;font-size:.84rem;line-height:1.65;resize:vertical;outline:none;tab-size:4}
|
||||
.cout{background:var(--cb);border-top:1px solid var(--bd);padding:.875rem 1rem;min-height:56px;font-family:'JetBrains Mono',monospace;font-size:.84rem;white-space:pre-wrap;color:var(--mu)}
|
||||
.cout.ok{color:var(--tx)}
|
||||
.cout.err{color:var(--rd)}
|
||||
|
||||
/* Story */
|
||||
.story{background:var(--cb);border-left:3px solid var(--cy);border-radius:0 12px 12px 0;padding:1.25rem 1.5rem;font-size:.95rem;line-height:1.85;white-space:pre-wrap}
|
||||
.concept-code{background:var(--cb);border-radius:10px;padding:.875rem 1.125rem;font-family:'JetBrains Mono',monospace;font-size:.84rem;line-height:1.65;color:#A8E6A3;white-space:pre-wrap;margin:.875rem 0;border:1px solid var(--bd)}
|
||||
|
||||
/* Phases */
|
||||
.phases{display:flex;gap:.5rem;align-items:center}
|
||||
.ph{width:9px;height:9px;border-radius:50%;background:var(--bd);transition:all .25s}
|
||||
.ph.act{background:var(--cy);transform:scale(1.4)}
|
||||
.ph.don{background:var(--gr)}
|
||||
|
||||
/* Answers */
|
||||
.ans-btn{display:block;width:100%;text-align:left;padding:.8rem 1.1rem;margin:.45rem 0;background:var(--sf2);border:1.5px solid var(--bd);border-radius:10px;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.95rem;cursor:pointer;transition:all .15s;font-weight:600}
|
||||
.ans-btn:hover{border-color:var(--cy);background:var(--cy-d)}
|
||||
.ans-btn.cor{border-color:var(--gr);background:var(--gr-d);color:var(--gr)}
|
||||
.ans-btn.wrg{border-color:var(--rd);background:var(--rd-d);color:var(--rd)}
|
||||
|
||||
/* Badges */
|
||||
.bg-pill{display:inline-flex;align-items:center;gap:.35rem;background:var(--sf2);border:1px solid var(--bd);border-radius:20px;padding:.3rem .75rem;font-size:.78rem;font-weight:700}
|
||||
.bg-pill.earn{border-color:var(--go);background:var(--go-d);color:var(--go)}
|
||||
.bg-pill.lock{opacity:.3;filter:grayscale(1)}
|
||||
|
||||
/* Prof */
|
||||
.prof-panel{position:fixed;bottom:76px;right:1rem;width:320px;max-height:440px;background:var(--sf);border:1.5px solid var(--pu);border-radius:16px;display:flex;flex-direction:column;z-index:500;animation:slideUp .2s ease}
|
||||
@keyframes slideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
|
||||
.prof-msgs{flex:1;overflow-y:auto;padding:.875rem;display:flex;flex-direction:column;gap:.625rem}
|
||||
.pmsg{padding:.5rem .75rem;border-radius:10px;font-size:.84rem;line-height:1.5;max-width:88%}
|
||||
.pmsg.u{background:var(--cy-d);color:var(--cy);align-self:flex-end;border:1px solid var(--cy)}
|
||||
.pmsg.b{background:var(--sf2);align-self:flex-start;border:1px solid var(--pu)}
|
||||
.prof-fab{position:fixed;bottom:1rem;right:1rem;background:var(--pu);color:#080C1E;border:none;border-radius:50%;width:52px;height:52px;font-size:1.4rem;cursor:pointer;z-index:500;display:flex;align-items:center;justify-content:center;transition:all .2s;font-weight:900}
|
||||
.prof-fab:hover{transform:scale(1.1)}
|
||||
|
||||
/* Popups */
|
||||
@keyframes xpUp{0%{opacity:1;transform:translateY(0) scale(.9)}60%{opacity:1;transform:translateY(-24px) scale(1.15)}100%{opacity:0;transform:translateY(-56px) scale(1)}}
|
||||
.xp-pop{position:fixed;top:1.25rem;right:1.25rem;background:var(--go);color:#080C1E;padding:.45rem 1rem;border-radius:20px;font-weight:900;font-size:1rem;z-index:1000;animation:xpUp 2s ease forwards;pointer-events:none}
|
||||
|
||||
/* Viktor alert */
|
||||
.viktor{background:var(--rd-d);border:1px solid var(--rd);border-radius:10px;padding:.625rem 1rem;font-size:.875rem;color:var(--rd);margin-bottom:1rem}
|
||||
|
||||
/* Input overlay */
|
||||
.overlay{position:fixed;inset:0;background:#0007;z-index:800;display:flex;align-items:center;justify-content:center}
|
||||
.dlg{background:var(--sf);border:1.5px solid var(--cy);border-radius:16px;padding:1.5rem;width:360px;max-width:95vw}
|
||||
.dlg input{width:100%;background:var(--cb);border:1px solid var(--bd);border-radius:8px;padding:.6rem .875rem;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.9rem;margin:.35rem 0 .875rem;outline:none}
|
||||
.dlg input:focus{border-color:var(--cy)}
|
||||
|
||||
/* Progress bar mission */
|
||||
.mp-bar{height:4px;background:var(--sf2);border-radius:4px;overflow:hidden;margin:.5rem 0}
|
||||
.mp-fill{height:100%;background:var(--cy);border-radius:4px;transition:width .4s ease}
|
||||
|
||||
/* Hint box */
|
||||
.hint-box{background:var(--go-d);border:1px solid var(--go);border-radius:10px;padding:.75rem 1rem;font-size:.875rem;color:var(--go);margin-top:.875rem}
|
||||
|
||||
/* Concept bullet */
|
||||
.cpb{display:flex;align-items:flex-start;gap:.5rem;padding:.35rem 0;font-size:.9rem}
|
||||
|
||||
/* Result card */
|
||||
.result-card{background:var(--go-d);border:2px solid var(--go);border-radius:16px;padding:2rem;text-align:center}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar{width:4px}
|
||||
::-webkit-scrollbar-track{background:transparent}
|
||||
::-webkit-scrollbar-thumb{background:var(--bd);border-radius:4px}
|
||||
|
||||
/* Utils */
|
||||
.fl{display:flex}.ic{align-items:center}.jb{justify-content:space-between}
|
||||
.g2{gap:.5rem}.g3{gap:.75rem}.g4{gap:1rem}
|
||||
.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:.75rem}
|
||||
.mb4{margin-bottom:1rem}.mb5{margin-bottom:1.25rem}.mb6{margin-bottom:1.5rem}
|
||||
.mt2{margin-top:.5rem}.mt3{margin-top:.75rem}.mt4{margin-top:1rem}
|
||||
.mu{color:var(--mu)}.cy{color:var(--cy)}.go{color:var(--go)}
|
||||
.gr{color:var(--gr)}.rd{color:var(--rd)}.pu{color:var(--pu)}
|
||||
.sm{font-size:.875rem}.xs{font-size:.75rem}.lg{font-size:1.1rem}
|
||||
.xl{font-size:1.25rem}.fw8{font-weight:800}.fw7{font-weight:700}
|
||||
.ta-c{text-align:center}
|
||||
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
.fa{animation:fade .25s ease}
|
||||
@keyframes conf{from{transform:translateY(-80px) rotate(0deg);opacity:1}to{transform:translateY(110vh) rotate(720deg);opacity:0}}
|
||||
.hidden{display:none!important}
|
||||
@media (max-width:760px){
|
||||
.wrap{padding:.875rem}
|
||||
.card{padding:1rem;border-radius:12px}
|
||||
.mgrid{grid-template-columns:repeat(2,1fr)}
|
||||
.fl.jb{align-items:flex-start}
|
||||
.prof-panel{left:.75rem;right:.75rem;bottom:72px;width:auto;max-height:60vh}
|
||||
.btn{width:100%;justify-content:center}
|
||||
.cta{font-size:.8rem}
|
||||
}
|
||||
@media (max-width:430px){
|
||||
.mgrid{grid-template-columns:1fr}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="prof-layer"></div>
|
||||
|
||||
<script>
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// DATA
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
const M = [
|
||||
{id:1,e:"🔓",t:"Hallo Raphael",s:"Dein erster Python-Befehl",a:"#4FC3F7",
|
||||
story:`Ein kalter Montagmorgen. Raphael öffnet seinen Laptop – der Bildschirm leuchtet rot auf:\n\n⚠️ NOTFALL-ALERT ⚠️\n\n"Geheimes Python-Labor erkannt.\nNur echte Python-Agenten dürfen eintreten.\nBeweise, dass du Python sprechen kannst – jetzt."\n\nRaphael schluckt. Python? Noch nie programmiert.\nAber aufgeben? Keine Option.\nSeine Finger zittern... er beginnt zu tippen.`,
|
||||
w:{t:"print() – Dein erster Python-Befehl",c:'print("Hallo Raphael!")',p:["📢 print() zeigt Text auf dem Bildschirm an","✏️ Text in \" \" Anführungszeichen wird angezeigt","💡 Du kannst jeden beliebigen Text ausgeben"]},
|
||||
u:{a:"Das Labor wartet auf Raphaels Beweis! Schreibe 3 print()-Zeilen:\n→ Deinen Namen\n→ Deinen Agenten-Titel\n→ Eine Botschaft an Viktor",c:'# Raphael tippt seinen Beweis...\n',h:['💡 Benutze print() für jede Zeile','🔑 print("Dein Text") zeigt den Text an','✅ Beispiel: print("Ich bin Raphael")']},
|
||||
q:{q:"Was macht print() in Python?",a:["Druckt Papier aus","Zeigt Text auf dem Bildschirm an","Löscht Dateien","Rechnet Zahlen"],r:1,ok:"🎯 Exakt! print() zeigt Text an. Raphael hat das Labor überzeugt!"}
|
||||
},
|
||||
{id:2,e:"🗄️",t:"Geheime Informationen",s:"Variablen speichern",a:"#CE93D8",
|
||||
story:`Das Labor öffnet sich. Eine ruhige Stimme erklingt:\n\n"Willkommen, Agent Raphael. Ich bin ARIA.\n\nViktor – ein feindliches KI-System – greift unser\nDatennetz an. Bereits 3 Datenbanken kompromittiert.\n\nAber zuerst: Ein Agent ohne Gedächtnis ist nutzlos.\nLerne, Informationen zu speichern."`,
|
||||
w:{t:"Variablen – Dein digitales Gedächtnis",c:'name = "Raphael"\nalter = 12\nstadt = "München"\n\nprint(name)\nprint(alter)\nprint("Agent aus " + stadt)',p:["🗄️ Variablen sind wie Schubladen – sie speichern Werte","💬 Text braucht \" \": name = \"Raphael\"","🔢 Zahlen brauchen kein \" \": alter = 12"]},
|
||||
u:{a:"Erstelle Raphaels geheime Agent-Akte!\nSpeichere und zeige: Name, Alter, Lieblingsessen, Lieblingsspiel",c:"# Raphaels Agent-Akte\n# 4 Variablen erstellen...\n",h:['💡 name = "Raphael" speichert Text',"🔑 alter = 12 speichert Zahl (ohne \" \"!)","✅ print(name) zeigt den Wert an"]},
|
||||
q:{q:"Welche Variable enthält einen Text?",a:['alter = 12','name = "Raphael"','punkte = 100','level = 5'],r:1,ok:'📁 Richtig! Text braucht Anführungszeichen: "Raphael"'}
|
||||
},
|
||||
{id:3,e:"🧮",t:"Der Agenten-Rechner",s:"Mathematik mit Python",a:"#66BB6A",
|
||||
story:`"Gut gemacht, Agent Raphael!" ARIA klingt zufrieden.\n"Viktor verschlüsselt Nachrichten mit Zahlen-Codes.\nDu brauchst einen Rechner – blitzschnell und fehlerfrei.\nPython rechnet besser als jeder Taschenrechner."`,
|
||||
w:{t:"Python kann rechnen!",c:"a = 10\nb = 5\nprint(a + b) # 15\nprint(a - b) # 5\nprint(a * b) # 50\nprint(a / b) # 2.0",p:["➕ Addition: +","➖ Subtraktion: -","✖️ Multiplikation: *","➗ Division: /"]},
|
||||
u:{a:"Baue Raphaels Agenten-Rechner!\nBerechne die 4 Geheim-Codes: 15+7, 20-8, 5*6, 24/4",c:"# Raphaels Agenten-Rechner\n",h:["💡 print(15 + 7) berechnet UND zeigt das Ergebnis","🔑 * für Multiplikation, / für Division","✅ print(15 + 7) ergibt 22"]},
|
||||
q:{q:"Was ergibt print(3 * 4)?",a:["34","7","12","3.4"],r:2,ok:"✅ 3 × 4 = 12! Das * Zeichen ist die Multiplikation."}
|
||||
},
|
||||
{id:4,e:"🔍",t:"Der Fehler-Detektiv",s:"Fehlermeldungen lesen",a:"#FFC107",
|
||||
story:`"Agent Raphael – KRITISCH!" ARIA klingt alarmiert.\n"Viktor hat unsere Codebasis sabotiert!\nÜberall rote Fehlermeldungen.\n\nAber höre zu: Fehler sind keine Feinde.\nSie sind Hinweise. Schlüssel zum Problem.\nEin Agent, der Fehler lesen kann, ist unaufhaltbar."`,
|
||||
w:{t:"Fehler lesen = Superkraft!",c:'# SyntaxError: fehlende Klammer\n# print("Hallo" ← FALSCH\nprint("Hallo") # ← RICHTIG ✅\n\n# NameError: Variable nicht definiert\n# print(geheimwort) ← FALSCH\ngeheimwort = "Python"\nprint(geheimwort) # ← RICHTIG ✅',p:["🔴 SyntaxError = Schreibfehler (fehlende Klammer etc.)","🟡 NameError = Variable noch nicht definiert","🟢 Fehler lesen: Zeile → Tipp → Fix versuchen"]},
|
||||
u:{a:"Raphaels Detektiv-Mission:\nFinde und korrigiere die 2 Fehler im Code!",c:'# Korrigiere diese Fehler, Raphael!\n\n# Fehler 1 - was fehlt?\nprint("Agent Raphael"\n\n# Fehler 2 - was muss vorher kommen?\nprint(geheimwort)\n',h:["💡 Fehler 1: Schaue die Klammern an – sind alle zu?","🔑 Fehler 2: geheimwort muss VOR print() definiert werden!",'✅ geheimwort = "Python123" – dann print(geheimwort)']},
|
||||
q:{q:"Was bedeutet SyntaxError?",a:["Programm ist zu langsam","Schreibfehler im Code (z.B. fehlende Klammer)","Computer ist kaputt","Datei nicht gefunden"],r:1,ok:"🔍 Genau! SyntaxError = Python kann den Code nicht lesen."}
|
||||
},
|
||||
{id:5,e:"⌨️",t:"Magische Eingabe",s:"input() Funktion",a:"#FFAB40",
|
||||
story:`"Neue Aufgabe, Agent Raphael!" ARIA sendet eine Nachricht.\n"Viktor kann unsere Agenten nicht mehr erkennen.\nBaue ein interaktives Identifikationssystem\ndas Nutzer nach ihrem Namen fragt!"`,
|
||||
w:{t:"input() – Nutzer-Eingabe",c:'name = input("Wie heißt du? ")\nprint("Hallo, Agent " + name + "!")',p:["⌨️ input() hält das Programm an und wartet","📥 Die Eingabe wird als Text gespeichert","🔢 int(input()) wandelt Text in Zahl um"]},
|
||||
u:{a:"Baue Raphaels Agenten-Identifikationssystem!\nFrage nach Name und begrüße den Agenten persönlich.",c:"# Agenten-Identifikation\n# Frage nach dem Namen...\n\n",h:['💡 name = input("Wie heißt du? ")','🔑 print("Hallo, Agent " + name + "!")']},
|
||||
q:{q:"Wofür wird input() benutzt?",a:["Zahlen berechnen","Text vom Nutzer einlesen","Variablen löschen","Code drucken"],r:1,ok:"✅ input() liest Text ein, den der Nutzer tippt!"}
|
||||
},
|
||||
{id:6,e:"🎯",t:"Zahlen-Ratespiel",s:"if-Bedingungen",a:"#F06292",
|
||||
story:`"Viktor hat einen Geheim-Code versteckt – eine Zahl.\nRaphael muss sie erraten, um das System zu entsperren.\nPython kann prüfen ob eine Zahl korrekt ist!"`,
|
||||
w:{t:"if – Wenn... dann...",c:"geheimzahl = 42\nrateversuch = 42\n\nif rateversuch == geheimzahl:\n print('🎯 Treffer! Code entsperrt!')",p:["🔀 if prüft eine Bedingung","== bedeutet 'ist gleich'","↩️ Einrückung (4 Leerzeichen) ist Pflicht!"]},
|
||||
u:{a:"Baue Raphaels Zahlen-Ratespiel!\nGeheimzahl = 7. Prüfe ob die Eingabe stimmt.",c:"# Zahlen-Ratespiel\ngeheimzahl = 7\neingabe = int(input('Rate die Zahl: '))\n# Prüfe ob die Eingabe korrekt ist\n",h:["💡 int() wandelt die Texteingabe in eine Zahl","🔑 if eingabe == geheimzahl: print('Richtig!')"]},
|
||||
q:{q:"Wann läuft der Code unter if?",a:["Immer","Wenn die Bedingung True (wahr) ist","Nie","Zufällig"],r:1,ok:"✅ if-Code läuft nur wenn die Bedingung wahr ist!"}
|
||||
},
|
||||
{id:7,e:"⚖️",t:"Entscheidungsmeister",s:"if / else",a:"#4DB6AC",
|
||||
story:`"Sehr gut! Aber jetzt brauchen wir Plan B.\nWas passiert wenn jemand die falsche Antwort gibt?\nif/else gibt dir immer zwei Möglichkeiten!"`,
|
||||
w:{t:"if / else – Plan A und Plan B",c:"alter = 12\n\nif alter >= 12:\n print('✅ Zugang: GEWÄHRT')\nelse:\n print('❌ Noch mehr Training nötig')",p:["🔀 else ist der Alternativ-Fall","📋 Wenn if nicht zutrifft, läuft else"]},
|
||||
u:{a:"Agenten-Aufnahme-Check:\nAlter ≥ 12 → aufgenommen, sonst → abgelehnt!",c:"alter = int(input('Wie alt bist du? '))\n# Prüfe das Alter mit if/else\n",h:["💡 if alter >= 12:","🔑 else: print('Abgelehnt')"]},
|
||||
q:{q:"Was macht 'else'?",a:["Beendet das Programm","Läuft wenn if NICHT zutrifft","Neue Funktion starten","Zeigt Fehler an"],r:1,ok:"✅ else ist Plan B – läuft wenn if nicht zutrifft!"}
|
||||
},
|
||||
{id:8,e:"🔐",t:"Super-Agent-Check",s:"and & or",a:"#7986CB",
|
||||
story:`"Das Hochsicherheitslabor braucht doppelte Absicherung.\nViktor kann einzelne Bedingungen knacken.\nAber 'and' und 'or' bauen eine Festung!"`,
|
||||
w:{t:"and & or – Kombinierte Bedingungen",c:"name = 'Raphael'\nalter = 12\n\nif name == 'Raphael' and alter >= 12:\n print('🟢 ZUGANG GEWÄHRT')\nelse:\n print('🔴 ZUGANG VERWEIGERT')",p:["🔒 and = BEIDE Bedingungen müssen wahr sein","🔓 or = EINE der Bedingungen reicht"]},
|
||||
u:{a:"Baue ein 2-Faktor-Login!\nRichtiger Name UND richtiges Passwort = Zugang.",c:"name = input('Agent-Name: ')\npw = input('Passwort: ')\n# Prüfe beide Bedingungen mit 'and'\n",h:['💡 if name == "Raphael" and pw == "geheim":']},
|
||||
q:{q:"Wann ist 'A and B' wahr?",a:["Wenn A wahr ist","Wenn B wahr ist","Wenn BEIDE wahr sind","Wenn keins wahr ist"],r:2,ok:"✅ and braucht beide – wie ein Doppelschloss!"}
|
||||
},
|
||||
{id:9,e:"🚀",t:"Schleifen-Experte",s:"for-Schleifen",a:"#FF7043",
|
||||
story:`"Viktor verstärkt den Angriff! Abwehrrakete muss starten!\nAber zuerst – der Countdown.\nPython kann für Raphael rückwärts zählen!"`,
|
||||
w:{t:"for – Dinge wiederholen",c:"for i in range(5):\n print(i) # 0,1,2,3,4\n\n# Countdown:\nfor i in range(10, 0, -1):\n print(i)\nprint('🚀 LAUNCH!')",p:["🔁 for wiederholt Code für jeden Wert","📊 range(5) → 0,1,2,3,4","⬇️ range(10,0,-1) → Countdown 10..1"]},
|
||||
u:{a:"Starte die Abwehrrakete!\nCountdown von 10 bis 1, dann '🚀 LAUNCH!'",c:"# Raketen-Countdown\n# Zähle von 10 bis 1 runter\n",h:["💡 range(10, 0, -1) zählt rückwärts","🔑 print(i) zeigt die aktuelle Zahl"]},
|
||||
q:{q:"Was erzeugt range(3)?",a:["0, 1, 2","1, 2, 3","Nur 3","0, 1, 2, 3"],r:0,ok:"✅ range(3) → 0, 1, 2 – Python zählt ab 0!"}
|
||||
},
|
||||
{id:10,e:"🎒",t:"Inventar-System",s:"Listen",a:"#81C784",
|
||||
story:`"Gut gemacht! Die Rakete hat Viktors ersten Angriff\nabgewehrt. Jetzt brauchen wir ein Inventar-System.\nPython kann viele Dinge in einer Variablen speichern!"`,
|
||||
w:{t:"Listen – Sammlungen in Python",c:'rucksack = ["Seil", "Karte", "Lampe"]\nprint(rucksack[0]) # "Seil"\nrucksack.append("Kompass")\nfor item in rucksack:\n print("🎒 " + item)',p:["📋 Listen speichern mehrere Werte in [ ]","🔢 Index beginnt bei 0 (erstes = [0])","➕ .append() fügt am Ende hinzu","🔁 for item in liste: → alle ausgeben"]},
|
||||
u:{a:"Erstelle Raphaels Agenten-Rucksack!\n4 Gegenstände hinzufügen und alle anzeigen.",c:"# Raphaels Agenten-Rucksack\nrucksack = []\n# Füge 4 Gegenstände hinzu!\n",h:['💡 rucksack = ["Seil", "Karte", "Lampe", "Wasser"]','🔑 for item in rucksack: print("🎒 " + item)']},
|
||||
q:{q:"Welchen Index hat das ERSTE Element einer Liste?",a:["1","0","-1","Kein Index"],r:1,ok:"✅ Index 0 = erstes Element – Python zählt ab 0!"}
|
||||
},
|
||||
{id:11,e:"🛠️",t:"Eigene Funktionen",s:"def – Bausteine",a:"#B39DDB",
|
||||
story:`"Excellente Arbeit! Doch Viktor lernt auch dazu.\nWir brauchen wiederverwendbare Code-Bausteine.\nLerne, eigene Befehle zu erstellen – Funktionen!"`,
|
||||
w:{t:"def – Eigene Befehle erstellen",c:"def agent_begruessung():\n print('🕵️ Willkommen, Agent!')\n print('System: BEREIT')\n\nagent_begruessung() # 1. Aufruf\nagent_begruessung() # 2. Aufruf",p:["🔧 def name(): erstellt eine Funktion","↩️ Eingerückter Code = Inhalt der Funktion","▶️ name() ruft auf – beliebig oft!"]},
|
||||
u:{a:"Baue eine Funktion 'agentenstatus()'!\nSie soll Raphaels Agent-Infos anzeigen.",c:"def agentenstatus():\n pass # Ersetze 'pass' mit deinem Code\n\nagentenstatus()\n",h:["💡 Ersetze pass mit print()-Befehlen","🔑 Rufe die Funktion am Ende mit agentenstatus() auf"]},
|
||||
q:{q:"Womit startet eine Funktion in Python?",a:["function:","fun():","def name():","create:"],r:2,ok:"✅ def funktionsname(): – so wird eine Funktion erstellt!"}
|
||||
},
|
||||
{id:12,e:"⚡",t:"Agenten-Funktionen",s:"Parameter & f-Strings",a:"#FFD54F",
|
||||
story:`"Perfekt, Raphael! Aber was wenn verschiedene Agenten\ndie Funktion nutzen wollen?\nParameter machen Funktionen wirklich mächtig!"`,
|
||||
w:{t:"Parameter & f-Strings",c:"def begruesse(name, level):\n print(f'Hallo Agent {name}!')\n print(f'Dein Level: {level}')\n\nbegruesse('Raphael', 5)\nbegruesse('ARIA', 99)",p:["📥 Parameter = Eingaben für die Funktion","✨ f'Text {variable}' fügt Variable in Text ein","🎯 Beim Aufruf übergibst du die Werte"]},
|
||||
u:{a:"Erweitere agentenstatus(name, level)!\nNutze f-Strings für eine schöne Ausgabe.",c:"def agentenstatus(name, level):\n pass # Ersetze mit f-String Code\n\nagentenstatus('Raphael', 5)\n",h:["💡 f'Hallo {name}!' fügt die Variable ein","🔑 Ersetze pass mit echten print(f'...')-Zeilen"]},
|
||||
q:{q:"Was sind Parameter in einer Funktion?",a:["Fehler im Code","Eingaben, die die Funktion erhält","Der Funktionsname","Ausgabewerte"],r:1,ok:"✅ Parameter sind Eingaben – wie Zutaten für ein Rezept!"}
|
||||
},
|
||||
{id:13,e:"❓",t:"Das Quiz-Spiel",s:"Alles kombinieren",a:"#4DD0E1",
|
||||
story:`"Zeit für die echte Agenten-Prüfung!\nAber diesmal baust du das Quiz selbst.\nZeige ARIA, dass du alles verstanden hast!"`,
|
||||
w:{t:"Das komplette Quiz",c:"punkte = 0\nfragen = [('Was ergibt 3*4?','12'),('Welche Sprache lernst du?','Python')]\n\nfor frage, antwort in fragen:\n eingabe = input(frage + ' ')\n if eingabe == antwort:\n print('✅ Richtig! +1')\n punkte += 1\n else:\n print('❌ Falsch')\n\nprint(f'Ergebnis: {punkte}/{len(fragen)}')",p:["🎯 Variablen + Listen + Schleifen + if!","➕ punkte += 1 erhöht um 1","📏 len(liste) = Anzahl der Elemente"]},
|
||||
u:{a:"Baue dein eigenes 5-Fragen-Quiz!\nÜber Python, Raphael oder Allgemeinwissen.",c:"# Raphaels Agent-Quiz\npunkte = 0\nfragen = [\n # ('Frage?', 'Antwort'),\n # 5 Fragen einfügen!\n]\n\nfor frage, antwort in fragen:\n eingabe = input(frage + ' ')\n if eingabe == antwort:\n punkte += 1\n\nprint(f'Ergebnis: {punkte}/{len(fragen)}')\n",h:["💡 Füge Fragen als ('Frage?','Antwort') hinzu","🔑 punkte += 1 für jede richtige Antwort"]},
|
||||
q:{q:"Was macht 'punkte += 1'?",a:["Setzt punkte auf 1","Erhöht punkte um 1","Verringert um 1","Zeigt punkte an"],r:1,ok:"✅ += erhöht den Wert – kurz für punkte = punkte + 1!"}
|
||||
},
|
||||
{id:14,e:"🗺️",t:"Das Abenteuer-Spiel",s:"Text Adventure",a:"#AED581",
|
||||
story:`"Raphael! Wir haben Viktors Versteck geortet.\nAber der Weg ist ein interaktives Labyrinth.\nBaue es selbst – als Text-Adventure-Spiel!"`,
|
||||
w:{t:"Text Adventure – Interaktive Geschichten",c:"print('Du stehst vor Viktors Hauptquartier.')\nwahl = input('Links (L) oder Rechts (R)? ')\n\nif wahl == 'L':\n print('⚠️ Viktor entkommt!')\nelif wahl == 'R':\n print('🎉 Viktor besiegt!')\nelse:\n print('❓ Unbekannte Eingabe')",p:["📖 Text Adventures = Interaktive Geschichten","🔀 elif = 'else if' – weitere Bedingung","🌳 Mehr Entscheidungen = mehr Pfade"]},
|
||||
u:{a:"Baue Raphaels Agenten-Abenteuer!\n2 Entscheidungen → mindestens 3 verschiedene Enden.",c:"print('Agent Raphael betritt Viktors Versteck...')\n\nwahl = input('Tür A (A) oder Tür B (B)? ')\n",h:["💡 if/elif/else für verschiedene Pfade","🔑 input() für Entscheidungen des Spielers"]},
|
||||
q:{q:"Was macht 'elif' in Python?",a:["Beendet Schleife","'else if' – weitere Bedingungsprüfung","Definiert Funktion","Importiert Module"],r:1,ok:"✅ elif = else if – weitere Bedingungsprüfung!"}
|
||||
},
|
||||
{id:15,e:"🎲",t:"Geheimcode-Generator",s:"Module & Zufall",a:"#F48FB1",
|
||||
story:`"Die ultimative Waffe gegen Viktor:\nzufällige, unvorhersagbare Codes!\nViktor kann keine zufälligen Passwörter knacken."`,
|
||||
w:{t:"import random – Zufallsmodul",c:"import random\n\nzahl = random.randint(1, 100)\nprint(f'Geheimzahl: {zahl}')\n\noptionen = ['Seil', 'Laser', 'Tarnmantel']\nwahl = random.choice(optionen)\nprint(f'Waffe: {wahl}')",p:["📦 import lädt externe Module","🎲 random.randint(a, b) → Zufallszahl","🎯 random.choice(liste) → zufälliges Element"]},
|
||||
u:{a:"Baue Raphaels Passwort-Generator!\n4 zufällige Ziffern → unknackbares Passwort.",c:"import random\n# Erstelle ein 4-stelliges Zufalls-Passwort\n# Tipp: random.randint(0, 9) für eine Ziffer\n\n",h:["💡 random.randint(0, 9) gibt eine Ziffer (0-9)","🔑 str(zahl) wandelt Zahl in Text – mit + verbinden"]},
|
||||
q:{q:"Was macht 'import random'?",a:["Erstellt eine Zufallszahl","Lädt das Zufalls-Modul","Löscht alle Variablen","Startet das Programm neu"],r:1,ok:"✅ import lädt Module – random gibt Zufallsfunktionen!"}
|
||||
},
|
||||
{id:16,e:"👑",t:"Viktors letzter Code",s:"Das finale Abenteuer!",a:"#FFC107",
|
||||
story:`🚨 MAXIMALER ALARM 🚨\n\n"Agent Raphael – das ist es!" ARIA klingt ernst.\n"Viktor aktiviert seinen Haupt-Angriff!\nNur du kannst ihn stoppen.\n\nDu hast alles gelernt:\n✅ Variablen ✅ Schleifen ✅ Funktionen\n✅ Listen ✅ Bedingungen ✅ Module\n\nJetzt baue die ultimative Agentenzentrale.\nRette die digitale Welt, Raphael!"`,
|
||||
w:{t:"Die ultimative Agentenzentrale",c:"import random\n\ndef login(name, pw):\n return name == 'Raphael' and pw == 'python'\n\ndef zeige_inventar(items):\n for item in items:\n print(f' ⚔️ {item}')\n\nif login(input('Name: '), input('PW: ')):\n print('🟢 ZUGANG GEWÄHRT')\n zeige_inventar(['Seil', 'Karte', 'Lampe'])\n print(f'🎲 Code: {random.randint(1000,9999)}')\nelse:\n print('🔴 ABGELEHNT')",p:["🔙 return gibt einen Wert zurück","🔗 Alle Konzepte kombiniert!","✨ Raphael ist jetzt Python-Großmeister!"]},
|
||||
u:{a:"Baue Raphaels komplette Agentenzentrale!\n✅ Login mit Name + Passwort\n✅ Inventar anzeigen\n✅ Zufalls-Code generieren\n✅ Missionsstatus ausgeben",c:"# 👑 RAPHAELS AGENTENZENTRALE\nimport random\n\ndef login(name, passwort):\n pass # TODO\n\ndef zeige_inventar(items):\n pass # TODO\n\nprint('╔══════════════════════╗')\nprint('║ AGENTENZENTRALE ║')\nprint('╚══════════════════════╝')\n# TODO: Alles verbinden!\n",h:["💡 Benutze alle Konzepte der 15 Missionen!","🔑 def + if/else + for + listen + input + random","✅ Du schaffst das, Agent Raphael! 💪"]},
|
||||
q:{q:"Du hast alle 16 Missionen abgeschlossen! Du bist jetzt...",a:["Python-Neuling","Code-Entdecker","Script-Held","Python-GROSSMEISTER! 👑"],r:3,ok:"🎉 HERZLICHEN GLÜCKWUNSCH, RAPHAEL! Viktor ist besiegt! Die digitale Welt ist gerettet! Du bist offiziell Python-Großmeister! 🏆"}
|
||||
}
|
||||
];
|
||||
|
||||
const LEVS = [
|
||||
{n:"Python-Neuling",min:0,i:"🌱"},
|
||||
{n:"Code-Entdecker",min:100,i:"🔍"},
|
||||
{n:"Script-Held",min:250,i:"⚔️"},
|
||||
{n:"Schleifen-Meister",min:450,i:"🔄"},
|
||||
{n:"Funktions-Profi",min:650,i:"⚡"},
|
||||
{n:"Python-Agent",min:900,i:"🕵️"},
|
||||
{n:"Python-Großmeister",min:1100,i:"👑"}
|
||||
];
|
||||
|
||||
const BDGS = [
|
||||
{id:"b1",i:"📢",n:"Erste Ausgabe",m:1},
|
||||
{id:"b4",i:"🔍",n:"Fehler-Jäger",m:4},
|
||||
{id:"b9",i:"🚀",n:"Schleifen-Held",m:9},
|
||||
{id:"b10",i:"🎒",n:"Listen-Profi",m:10},
|
||||
{id:"b12",i:"⚡",n:"Funktions-Meister",m:12},
|
||||
{id:"b14",i:"🎮",n:"Spiele-Entwickler",m:14},
|
||||
{id:"b15",i:"🎲",n:"Code-Zauberer",m:15},
|
||||
{id:"b16",i:"👑",n:"Python-Großmeister",m:16}
|
||||
];
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// STATE
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
const ST = {
|
||||
xp:0, done:{}, badges:[], view:'home', mid:null,
|
||||
phase:0, sxp:0, hi:-1, tdone:false, tcor:false,
|
||||
code:'', out:'', oerr:false,
|
||||
profOpen:false, profMsgs:[],
|
||||
py:null, pySt:'loading',
|
||||
inputsNeeded:[], inputVals:{}, drafts:{}
|
||||
};
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// UTILS
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function getLev(xp){
|
||||
for(let i=LEVS.length-1;i>=0;i--) if(xp>=LEVS[i].min) return {...LEVS[i],idx:i};
|
||||
return {...LEVS[0],idx:0};
|
||||
}
|
||||
function getLevPct(xp){
|
||||
const l=getLev(xp); const nx=LEVS[l.idx+1];
|
||||
if(!nx) return 100;
|
||||
return Math.round((xp-l.min)/(nx.min-l.min)*100);
|
||||
}
|
||||
function isUnlocked(id){ return id===1||!!ST.done[id-1]; }
|
||||
function getMission(id){ return M.find(m=>m.id===id); }
|
||||
function esc(s){
|
||||
return String(s ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// STORAGE
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
async function loadProg(){
|
||||
try{
|
||||
const raw=localStorage.getItem('raphael_python_abenteuer_v4');
|
||||
if(raw){
|
||||
const d=JSON.parse(raw);
|
||||
ST.xp=d.xp||0; ST.done=d.done||{}; ST.badges=d.badges||[]; ST.drafts=d.drafts||{};
|
||||
}
|
||||
}catch(e){}
|
||||
}
|
||||
async function saveProg(){
|
||||
try{
|
||||
localStorage.setItem('raphael_python_abenteuer_v4',JSON.stringify({
|
||||
xp:ST.xp,done:ST.done,badges:ST.badges,drafts:ST.drafts
|
||||
}));
|
||||
}catch(e){}
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// PYODIDE
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function initPy(){
|
||||
const sc=document.createElement('script');
|
||||
sc.src='https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js';
|
||||
sc.onload=async()=>{
|
||||
try{
|
||||
const py=await window.loadPyodide({indexURL:'https://cdn.jsdelivr.net/pyodide/v0.25.0/full/'});
|
||||
await py.runPythonAsync(`
|
||||
import sys,builtins
|
||||
from io import StringIO
|
||||
_buf=StringIO()
|
||||
sys.stdout=_buf
|
||||
sys.stderr=_buf
|
||||
_ins=[]
|
||||
_ii=[0]
|
||||
def _inp(p=''):
|
||||
_buf.write(str(p))
|
||||
if _ii[0]<len(_ins):
|
||||
v=_ins[_ii[0]];_ii[0]+=1
|
||||
_buf.write(str(v)+'\\n');return str(v)
|
||||
return ''
|
||||
builtins.input=_inp
|
||||
`);
|
||||
ST.py=py; ST.pySt='ready'; updPySt();
|
||||
}catch(e){ ST.pySt='error'; updPySt(); }
|
||||
};
|
||||
sc.onerror=()=>{ ST.pySt='error'; updPySt(); };
|
||||
document.head.appendChild(sc);
|
||||
}
|
||||
|
||||
function updPySt(){
|
||||
const els=document.querySelectorAll('[data-py-status]');
|
||||
if(!els.length) return;
|
||||
const map={
|
||||
ready:['🟢 Python bereit','var(--gr)'],
|
||||
error:['🔴 Python konnte nicht geladen werden','var(--rd)'],
|
||||
loading:['⏳ Python lädt...','var(--mu)']
|
||||
};
|
||||
const [txt,col]=map[ST.pySt]||map.loading;
|
||||
els.forEach(el=>{ el.textContent=txt; el.style.color=col; });
|
||||
}
|
||||
|
||||
function parseInputs(code){
|
||||
const ps=[];
|
||||
const re=/input\((['"``])(.*?)\1\)/g; let m;
|
||||
while((m=re.exec(code))!==null) ps.push(m[2]||'Eingabe:');
|
||||
const bare=(code.match(/input\(\s*\)/g)||[]).length;
|
||||
for(let i=0;i<bare;i++) ps.push('Eingabe:');
|
||||
return ps;
|
||||
}
|
||||
|
||||
async function runPy(code,inputs=[]){
|
||||
if(!ST.py) return {out:'⏳ Python ist noch nicht bereit. Bitte kurz warten oder die Internetverbindung prüfen.',err:true};
|
||||
try{
|
||||
await ST.py.runPythonAsync(`_buf.seek(0);_buf.truncate(0);_ins.clear();_ins.extend(${JSON.stringify(inputs.map(String))});_ii[0]=0`);
|
||||
await ST.py.runPythonAsync(code);
|
||||
const out=ST.py.runPython('_buf.getvalue()');
|
||||
return {out:out||'(kein Output – print() benutzen!)',err:false};
|
||||
}catch(e){
|
||||
return {out:'❌ Fehler:\n'+e.message,err:true};
|
||||
}
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// XP & PROGRESS
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function awardXP(amt,lbl){
|
||||
const prev=getLev(ST.xp); ST.xp+=amt; ST.sxp+=amt;
|
||||
const nx=getLev(ST.xp);
|
||||
showPop(`+${amt} XP${lbl?' – '+lbl:''}`,null,null);
|
||||
if(nx.idx>prev.idx) setTimeout(()=>showPop(`⬆️ Level up! ${nx.i} ${nx.n}`,'var(--pu)','80px'),600);
|
||||
updXPBar(); saveProg();
|
||||
}
|
||||
|
||||
function showPop(txt,bg,top){
|
||||
const p=document.createElement('div');
|
||||
p.className='xp-pop'; p.textContent=txt;
|
||||
if(bg) p.style.background=bg;
|
||||
if(top) p.style.top=top;
|
||||
document.body.appendChild(p);
|
||||
setTimeout(()=>p.remove(),2100);
|
||||
}
|
||||
|
||||
function updXPBar(){
|
||||
const f=document.getElementById('xp-f');
|
||||
const xl=document.getElementById('xp-lbl');
|
||||
const ln=document.getElementById('lev-n');
|
||||
if(f) f.style.width=getLevPct(ST.xp)+'%';
|
||||
if(xl) xl.textContent=ST.xp+' XP';
|
||||
if(ln){ const l=getLev(ST.xp); ln.textContent=l.i+' '+l.n; }
|
||||
}
|
||||
|
||||
function completeMiss(mid){
|
||||
if(ST.done[mid]) return;
|
||||
ST.done[mid]=true;
|
||||
BDGS.forEach(b=>{
|
||||
if(b.m===mid&&!ST.badges.includes(b.id)){
|
||||
ST.badges.push(b.id);
|
||||
setTimeout(()=>showPop(`🏅 Abzeichen: ${b.n}!`,'var(--go)','100px'),1200);
|
||||
}
|
||||
});
|
||||
saveProg();
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// RENDER HOME
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function renderHome(){
|
||||
const lv=getLev(ST.xp); const nx=LEVS[lv.idx+1];
|
||||
const pct=getLevPct(ST.xp); const dc=Object.keys(ST.done).length;
|
||||
|
||||
const mCards=M.map(mis=>{
|
||||
const done=!!ST.done[mis.id];
|
||||
const unlk=isUnlocked(mis.id);
|
||||
const cls='mc'+(done?' done':!unlk?' locked':'');
|
||||
return `<div class="${cls}" onclick="openMission(${mis.id})">
|
||||
${done?'<span class="check">✓</span>':''}
|
||||
<span class="me">${mis.e}</span>
|
||||
<span class="mt">${mis.id}. ${mis.t}</span>
|
||||
<span class="ms">${mis.s}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const bdgHtml=BDGS.map(b=>{
|
||||
const earn=ST.badges.includes(b.id);
|
||||
return `<span class="bg-pill ${earn?'earn':'lock'}">${b.i} ${b.n}</span>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="wrap fa">
|
||||
<div class="fl ic jb mb4">
|
||||
<div>
|
||||
<div class="fl ic g2 mb1">
|
||||
<span style="font-size:1.5rem">🕵️</span>
|
||||
<h1 class="orb" style="font-size:1.1rem;color:var(--cy)">AGENTEN-ZENTRALE</h1>
|
||||
</div>
|
||||
<div style="font-size:.8rem;color:var(--mu)">Agent Raphael · Python-Abenteuer</div>
|
||||
</div>
|
||||
<div data-py-status style="font-size:.8rem;color:var(--mu)">⏳ Python lädt...</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb4" style="border-color:var(--go)">
|
||||
<div class="fl ic jb mb2">
|
||||
<div>
|
||||
<div id="lev-n" style="font-weight:800;font-size:1.05rem">${lv.i} ${lv.n}</div>
|
||||
<div style="font-size:.8rem;color:var(--mu)">${nx?'Nächstes Level: '+nx.i+' '+nx.n:'🏆 Maximales Level erreicht!'}</div>
|
||||
</div>
|
||||
<div id="xp-lbl" style="font-size:1.4rem;font-weight:900;color:var(--go)">${ST.xp} XP</div>
|
||||
</div>
|
||||
<div class="xp-track"><div id="xp-f" class="xp-fill" style="width:${pct}%"></div></div>
|
||||
<div class="fl ic jb mt2">
|
||||
<span style="font-size:.8rem;color:var(--mu)">${dc}/16 Missionen abgeschlossen</span>
|
||||
<span style="font-size:.8rem;color:var(--cy)">${pct}% zum nächsten Level</span>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<button class="btn btn-gh btn-sm" onclick="resetProgress()">Fortschritt zurücksetzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fl ic jb mb3">
|
||||
<h2 class="fw8" style="font-size:1.05rem">🗂️ Missionen</h2>
|
||||
<span class="sm mu">Klicke auf eine Mission um zu starten</span>
|
||||
</div>
|
||||
<div class="mgrid mb5">${mCards}</div>
|
||||
|
||||
<div class="mb3"><h2 class="fw8 mb2" style="font-size:1.05rem">🏅 Abzeichen</h2>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:.5rem">${bdgHtml}</div>
|
||||
</div>
|
||||
|
||||
<div class="viktor mt4">⚠️ Virus Viktor ist noch aktiv – ${16-dc} Missionen bis zum finalen Showdown!</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// RENDER MISSION
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function renderMission(){
|
||||
const mis=getMission(ST.mid); if(!mis) return '';
|
||||
const ph=ST.phase;
|
||||
const phases=['Story','Lernen','Üben','Test','Fertig'];
|
||||
const dots=phases.map((_,i)=>`<div class="ph ${i<ph?'don':i===ph?'act':''}"></div>`).join('');
|
||||
|
||||
let body='';
|
||||
if(ph===0) body=renderStory(mis);
|
||||
else if(ph===1) body=renderLesen(mis);
|
||||
else if(ph===2) body=renderUeben(mis);
|
||||
else if(ph===3) body=renderTest(mis);
|
||||
else body=renderDone(mis);
|
||||
|
||||
return `
|
||||
<div class="wrap fa">
|
||||
<div class="fl ic jb mb4">
|
||||
<button class="btn btn-gh btn-sm" onclick="goHome()">← Zurück</button>
|
||||
<div class="fl ic g3">
|
||||
<div class="fl ic g2">${dots}</div>
|
||||
<span style="font-size:.8rem;color:var(--go);font-weight:700">+${ST.sxp} XP diese Mission</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fl ic g3 mb4">
|
||||
<span style="font-size:2rem">${mis.e}</span>
|
||||
<div>
|
||||
<h2 class="fw8" style="font-size:1.2rem">${mis.id}. ${mis.t}</h2>
|
||||
<div class="mu sm">${mis.s} · ${phases[ph]}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="phase-body">${body}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderStory(mis){
|
||||
return `
|
||||
<div class="card mb4">
|
||||
<div class="viktor mb3">📡 Neue Mission von ARIA für Agent Raphael</div>
|
||||
<div class="story">${esc(mis.story)}</div>
|
||||
</div>
|
||||
<button class="btn btn-cy" onclick="nextPhase()">Weiter zur Lektion →</button>`;
|
||||
}
|
||||
|
||||
function renderLesen(mis){
|
||||
const pts=mis.w.p.map(p=>`<div class="cpb"><span style="flex-shrink:0">${p.slice(0,2)}</span><span>${p.slice(2)}</span></div>`).join('');
|
||||
return `
|
||||
<div class="card mb4">
|
||||
<h3 class="fw8 mb3" style="font-size:1.05rem;color:var(--cy)">${mis.w.t}</h3>
|
||||
<div class="concept-code">${esc(mis.w.c)}</div>
|
||||
<div class="mt3">${pts}</div>
|
||||
</div>
|
||||
<div class="fl ic g3">
|
||||
<button class="btn btn-cy" onclick="nextPhase();awardXP(10,'Lesen')">Verstanden! +10 XP →</button>
|
||||
<span data-py-status class="sm mu">⏳ Python lädt...</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderUeben(mis){
|
||||
const code=ST.code||mis.u.c;
|
||||
const outCls='cout'+(ST.out&&!ST.oerr?' ok':ST.oerr?' err':'');
|
||||
const hintHtml=ST.hi>=0&&mis.u.h[ST.hi]
|
||||
?`<div class="hint-box">💡 ${esc(mis.u.h[ST.hi])}</div>`:'';
|
||||
const hintBtn=ST.hi<mis.u.h.length-1
|
||||
?`<button class="btn btn-gh btn-sm" onclick="showHint()">💡 Hinweis ${ST.hi+2>mis.u.h.length?'':ST.hi+2+'/'+mis.u.h.length}</button>`
|
||||
:'';
|
||||
|
||||
return `
|
||||
<div class="card mb3">
|
||||
<h3 class="fw8 mb2" style="font-size:1rem;color:var(--go)">✏️ Raphaels Aufgabe</h3>
|
||||
<div class="mb3 sm" style="white-space:pre-wrap;line-height:1.65">${esc(mis.u.a)}</div>
|
||||
<div class="ced">
|
||||
<div class="ced-hdr">
|
||||
<div class="dot" style="background:#FF5F56"></div>
|
||||
<div class="dot" style="background:#FFBD2E"></div>
|
||||
<div class="dot" style="background:#27C93F"></div>
|
||||
<span class="mono xs mu" style="margin-left:.5rem">raphael_code.py</span>
|
||||
<span data-py-status class="mono xs" style="margin-left:auto;color:var(--mu)">⏳</span>
|
||||
</div>
|
||||
<textarea id="code-ta" class="cta" onkeydown="handleTab(event)" oninput="saveDraft(this.value)">${esc(code)}</textarea>
|
||||
<div class="${outCls}" id="code-out">${ST.out?esc(ST.out):'▶ Code ausführen...'}</div>
|
||||
</div>
|
||||
${hintHtml}
|
||||
</div>
|
||||
<div class="fl ic g3">
|
||||
<button class="btn btn-gr" onclick="executeCode()">▶ Ausführen</button>
|
||||
${hintBtn}
|
||||
${ST.out&&!ST.oerr?`<button class="btn btn-cy" onclick="nextPhase();awardXP(20,'Übung')">Weiter +20 XP →</button>`:''}
|
||||
</div>
|
||||
<div id="input-overlay"></div>`;
|
||||
}
|
||||
|
||||
function renderTest(mis){
|
||||
const ans=mis.q.a.map((a,i)=>{
|
||||
let cls='ans-btn';
|
||||
if(ST.tdone){ if(i===mis.q.r) cls+=' cor'; else if(ST.tcor===false&&i!==mis.q.r) cls+=' '; }
|
||||
return `<button class="${cls}" onclick="answerTest(${i})">${esc(a)}</button>`;
|
||||
}).join('');
|
||||
const fb=ST.tdone?`<div class="card mt3" style="border-color:${ST.tcor?'var(--gr)':'var(--rd)'}">
|
||||
<div style="color:${ST.tcor?'var(--gr)':'var(--rd)'};font-weight:800;margin-bottom:.5rem">${ST.tcor?'✅ Richtig!':'❌ Nicht ganz...'}</div>
|
||||
<div class="sm">${esc(mis.q.ok)}</div>
|
||||
${ST.tcor?'':`<button class="btn btn-gh btn-sm mt3" onclick="resetTest()">Nochmal versuchen</button>`}
|
||||
</div>`:'';
|
||||
|
||||
return `
|
||||
<div class="card mb4">
|
||||
<h3 class="fw8 mb3" style="font-size:1rem;color:var(--pu)">❓ Mini-Test</h3>
|
||||
<p class="fw7 mb3">${esc(mis.q.q)}</p>
|
||||
${ans}
|
||||
${fb}
|
||||
</div>
|
||||
${ST.tdone&&ST.tcor?`<button class="btn btn-go" onclick="nextPhase();awardXP(20,'Test bestanden')">Weiter +20 XP →</button>`:''}`;
|
||||
}
|
||||
|
||||
function renderDone(mis){
|
||||
const xpEarned=ST.sxp;
|
||||
const lv=getLev(ST.xp);
|
||||
return `
|
||||
<div class="result-card mb4">
|
||||
<div style="font-size:3rem;margin-bottom:.75rem">${mis.e}</div>
|
||||
<h2 class="fw8 mb1" style="font-size:1.4rem">Mission abgeschlossen, Raphael!</h2>
|
||||
<p class="mb3" style="color:var(--go-d);font-weight:700">${mis.t}</p>
|
||||
<div style="font-size:2rem;font-weight:900;color:var(--go);margin-bottom:.5rem">+${xpEarned} XP</div>
|
||||
<div style="font-size:.875rem;font-weight:700">Du bist jetzt: ${lv.i} ${lv.n}</div>
|
||||
</div>
|
||||
<div class="fl ic g3">
|
||||
<button class="btn btn-cy" onclick="goHome()">← Alle Missionen</button>
|
||||
${ST.mid<16?`<button class="btn btn-go" onclick="openMission(${ST.mid+1})">Nächste Mission →</button>`:''}
|
||||
</div>
|
||||
${ST.mid===16?confettiHTML():''}`;
|
||||
}
|
||||
|
||||
function confettiHTML(){
|
||||
const colors=['#FFC107','#4FC3F7','#66BB6A','#CE93D8','#EF5350'];
|
||||
let bits='';
|
||||
for(let i=0;i<40;i++){
|
||||
const c=colors[i%colors.length];
|
||||
const x=Math.random()*100; const d=Math.random()*3+1; const del=Math.random()*2;
|
||||
bits+=`<div style="position:fixed;left:${x}%;top:-40px;width:8px;height:8px;background:${c};border-radius:2px;animation:conf ${d}s ${del}s ease forwards;z-index:900"></div>`;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// EVENTS
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function handleTab(e){
|
||||
if(e.key==='Tab'){
|
||||
e.preventDefault();
|
||||
const t=e.target; const s=t.selectionStart;
|
||||
t.value=t.value.substring(0,s)+' '+t.value.substring(t.selectionEnd);
|
||||
t.selectionStart=t.selectionEnd=s+4;
|
||||
saveDraft(t.value);
|
||||
}
|
||||
}
|
||||
|
||||
function openMission(id){
|
||||
if(!isUnlocked(id)) return;
|
||||
const mis=getMission(id); if(!mis) return;
|
||||
ST.mid=id; ST.phase=0; ST.sxp=0; ST.hi=-1;
|
||||
ST.code=ST.drafts[id]||mis.u.c; ST.out=''; ST.oerr=false;
|
||||
ST.tdone=false; ST.tcor=false; ST.view='mission';
|
||||
render();
|
||||
}
|
||||
|
||||
function goHome(){ ST.view='home'; render(); }
|
||||
|
||||
function nextPhase(){
|
||||
if(ST.phase===1&&ST.view==='mission'){
|
||||
const mis=getMission(ST.mid);
|
||||
ST.code=ST.drafts[ST.mid]||mis.u.c; ST.out=''; ST.oerr=false; ST.hi=-1;
|
||||
}
|
||||
ST.phase++;
|
||||
if(ST.phase===4) completeMiss(ST.mid);
|
||||
render();
|
||||
}
|
||||
|
||||
function showHint(){
|
||||
ST.hi=Math.min(ST.hi+1, getMission(ST.mid).u.h.length-1);
|
||||
renderPhaseBody();
|
||||
}
|
||||
|
||||
function saveDraft(value){
|
||||
ST.code=value;
|
||||
if(ST.mid) ST.drafts[ST.mid]=value;
|
||||
saveProg();
|
||||
}
|
||||
|
||||
function resetProgress(){
|
||||
if(!confirm('Fortschritt, Abzeichen und gespeicherte Code-Entwürfe wirklich löschen?')) return;
|
||||
ST.xp=0; ST.done={}; ST.badges=[]; ST.drafts={}; ST.code=''; ST.out=''; ST.oerr=false;
|
||||
saveProg();
|
||||
ST.view='home'; ST.mid=null; ST.phase=0;
|
||||
render();
|
||||
}
|
||||
|
||||
function answerTest(idx){
|
||||
if(ST.tdone) return;
|
||||
const mis=getMission(ST.mid);
|
||||
ST.tdone=true; ST.tcor=(idx===mis.q.r);
|
||||
renderPhaseBody();
|
||||
}
|
||||
|
||||
function resetTest(){
|
||||
ST.tdone=false; ST.tcor=false;
|
||||
renderPhaseBody();
|
||||
}
|
||||
|
||||
async function executeCode(){
|
||||
const ta=document.getElementById('code-ta');
|
||||
const code=ta?ta.value:ST.code;
|
||||
ST.code=code;
|
||||
|
||||
const prompts=parseInputs(code);
|
||||
if(prompts.length>0){
|
||||
showInputDialog(prompts,code);
|
||||
return;
|
||||
}
|
||||
await doRunCode(code,[]);
|
||||
}
|
||||
|
||||
async function doRunCode(code,inputs){
|
||||
const outEl=document.getElementById('code-out');
|
||||
if(outEl){ outEl.className='cout'; outEl.textContent='▶ Wird ausgeführt...'; }
|
||||
const {out,err}=await runPy(code,inputs);
|
||||
ST.out=out; ST.oerr=err;
|
||||
renderPhaseBody();
|
||||
}
|
||||
|
||||
function showInputDialog(prompts,code){
|
||||
const fields=prompts.map((p,i)=>`
|
||||
<label style="font-size:.85rem;color:var(--mu);display:block;margin-top:.5rem">${esc(p)}</label>
|
||||
<input id="inp-${i}" placeholder="Raphael tippt hier..." />
|
||||
`).join('');
|
||||
|
||||
const overlay=document.getElementById('input-overlay');
|
||||
overlay.innerHTML=`<div class="overlay">
|
||||
<div class="dlg">
|
||||
<h3 class="fw8 mb2 cy">⌨️ Python fragt Raphael:</h3>
|
||||
<p class="sm mu mb3">Dein Programm benutzt input() – gib hier die Antworten ein:</p>
|
||||
${fields}
|
||||
<div class="fl ic g3 mt3">
|
||||
<button class="btn btn-cy" onclick='submitInputs(${JSON.stringify(encodeURIComponent(code))},${prompts.length})'>▶ Ausführen</button>
|
||||
<button class="btn btn-gh" onclick="document.getElementById('input-overlay').innerHTML=''">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function submitInputs(encodedCode,count){
|
||||
const code=decodeURIComponent(encodedCode);
|
||||
const inputs=[];
|
||||
for(let i=0;i<count;i++){
|
||||
const el=document.getElementById('inp-'+i);
|
||||
inputs.push(el?el.value:'');
|
||||
}
|
||||
document.getElementById('input-overlay').innerHTML='';
|
||||
await doRunCode(code,inputs);
|
||||
}
|
||||
|
||||
function renderPhaseBody(){
|
||||
const mis=getMission(ST.mid); if(!mis) return;
|
||||
const ph=ST.phase;
|
||||
const el=document.getElementById('phase-body'); if(!el) return;
|
||||
if(ph===0) el.innerHTML=renderStory(mis);
|
||||
else if(ph===1) el.innerHTML=renderLesen(mis);
|
||||
else if(ph===2) el.innerHTML=renderUeben(mis);
|
||||
else if(ph===3) el.innerHTML=renderTest(mis);
|
||||
else el.innerHTML=renderDone(mis);
|
||||
updPySt();
|
||||
const ta=document.getElementById('code-ta');
|
||||
if(ta){ ta.value=ST.code; }
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// PROFESSOR PYTHON
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function renderProf(){
|
||||
const layer=document.getElementById('prof-layer'); if(!layer) return;
|
||||
const mis=ST.mid?getMission(ST.mid):null;
|
||||
const msgs=ST.profMsgs.map(m=>`<div class="pmsg ${m.r}">${esc(m.t)}</div>`).join('');
|
||||
|
||||
layer.innerHTML=`
|
||||
<button class="prof-fab" onclick="toggleProf()" title="Professor Python fragen">🤖</button>
|
||||
${ST.profOpen?`<div class="prof-panel">
|
||||
<div class="fl ic jb" style="padding:.875rem 1rem .5rem;border-bottom:1px solid var(--bd)">
|
||||
<span class="fw8 pu" style="font-size:.9rem">🤖 Professor Python</span>
|
||||
<button class="btn btn-gh btn-sm" onclick="toggleProf()">✕</button>
|
||||
</div>
|
||||
<div class="prof-msgs" id="prof-msgs">
|
||||
${ST.profMsgs.length===0?'<div class="pmsg b">Hallo Raphael! 👋 Ich bin Professor Python.\nFrag mich alles – ich helfe dir (aber gebe keine direkten Lösungen 😄)</div>':''}
|
||||
${msgs}
|
||||
</div>
|
||||
<div style="padding:.75rem;border-top:1px solid var(--bd);display:flex;gap:.5rem">
|
||||
<input id="prof-in" placeholder="Frage an Professor Python..." style="flex:1;background:var(--cb);border:1px solid var(--bd);border-radius:8px;padding:.5rem .75rem;color:var(--tx);font-family:Nunito,Inter,Segoe UI,system-ui,sans-serif;font-size:.85rem;outline:none" onkeydown="if(event.key==='Enter')sendProf()">
|
||||
<button class="btn btn-pu btn-sm" onclick="sendProf()">Senden</button>
|
||||
</div>
|
||||
</div>`:''}`;
|
||||
|
||||
const msgs_el=document.getElementById('prof-msgs');
|
||||
if(msgs_el) msgs_el.scrollTop=msgs_el.scrollHeight;
|
||||
}
|
||||
|
||||
function toggleProf(){ ST.profOpen=!ST.profOpen; renderProf(); }
|
||||
|
||||
async function sendProf(){
|
||||
const inp=document.getElementById('prof-in'); if(!inp) return;
|
||||
const q=inp.value.trim(); if(!q) return;
|
||||
inp.value='';
|
||||
ST.profMsgs.push({r:'u',t:q});
|
||||
const mis=ST.mid?getMission(ST.mid):null;
|
||||
ST.profMsgs.push({r:'b',t:getMentorReply(q,mis)});
|
||||
renderProf();
|
||||
}
|
||||
|
||||
function getMentorReply(question,mis){
|
||||
const q=question.toLowerCase();
|
||||
const prefix=mis?`Raphael, in Mission ${mis.id} geht es um ${mis.s}. `:'Raphael, ';
|
||||
if(q.includes('fehler')||q.includes('error')) return prefix+'lies die erste rote Zeile langsam: Sie sagt meist, ob eine Klammer, ein Name oder eine Einrückung fehlt. Ändere immer nur eine Sache und starte dann neu.';
|
||||
if(q.includes('input')) return prefix+'input() fragt den Nutzer nach Text. Wenn du damit rechnen willst, wandle die Eingabe mit int(...) in eine Zahl um.';
|
||||
if(q.includes('if')||q.includes('else')) return prefix+'prüfe zuerst die Bedingung wie eine Ja/Nein-Frage. Der eingerückte Block darunter läuft nur, wenn diese Antwort passt.';
|
||||
if(q.includes('schleife')||q.includes('range')||q.includes('for')) return prefix+'eine for-Schleife wiederholt denselben eingerückten Code. range(5) liefert 0 bis 4, also fünf Durchläufe.';
|
||||
if(q.includes('liste')||q.includes('append')) return prefix+'eine Liste sammelt mehrere Werte in eckigen Klammern. Mit append(...) hängst du einen neuen Wert hinten an.';
|
||||
if(q.includes('funktion')||q.includes('def')) return prefix+'eine Funktion ist ein eigener Befehl. def erstellt ihn, und mit name() rufst du ihn später auf.';
|
||||
if(q.includes('lösung')||q.includes('fertig')) return prefix+'ich gebe dir nicht die komplette Lösung, aber nimm den Beispielcode links als Muster und ändere nur Namen, Zahlen oder Texte für deine Aufgabe.';
|
||||
return prefix+'starte mit dem kleinsten nächsten Schritt: Beispiel ansehen, eine Zeile schreiben, ausführen, Ergebnis lesen. Wenn etwas rot wird, ist das nur ein Hinweis zum Verbessern.';
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// MAIN RENDER
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
function render(){
|
||||
const app=document.getElementById('app');
|
||||
if(ST.view==='home') app.innerHTML=renderHome();
|
||||
else app.innerHTML=renderMission();
|
||||
updXPBar(); updPySt(); renderProf();
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// INIT
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
loadProg().then(()=>{ render(); initPy(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user