Medien- und Bildungsmanagement
Informatik (Lehramt)
@@ -772,12 +772,18 @@ const FIGURES=[
{id:'fox',e:'๐ฆ',n:'Fuchs'},{id:'dragon',e:'๐ฒ',n:'Drache'},{id:'astronaut',e:'๐จโ๐',n:'Astronaut'},
];
const BACKGROUNDS=[
- {id:'jungle',e:'๐ด',scene:'๐ด๐ฆ๐ฟ',n:'Dschungel'},{id:'space',e:'๐',scene:'๐๐ชโญ',n:'Weltraum'},
- {id:'ocean',e:'๐',scene:'๐๐ ๐',n:'Unterwasser'},{id:'fantasy',e:'๐ฐ',scene:'๐ฐ๐โจ',n:'Fantasy'},
- {id:'school',e:'๐ซ',scene:'๐ซ๐โ๏ธ',n:'Schule'},{id:'volcano',e:'๐',scene:'๐๐๏ธ๐ฅ',n:'Vulkaninsel'},
- {id:'snow',e:'โ๏ธ',scene:'โ๏ธ๐๏ธโ',n:'Schneereich'},{id:'city',e:'๐',scene:'๐๐๐๏ธ',n:'Groรstadt'},
- {id:'candy',e:'๐ญ',scene:'๐ญ๐ฌ๐',n:'Sรผรigkeitenland'},{id:'desert',e:'๐๏ธ',scene:'๐๏ธ๐ต๐ช',n:'Wรผste'},
- {id:'haunted',e:'๐ป',scene:'๐ป๐๏ธ๐ธ๏ธ',n:'Geisterhaus'},{id:'future',e:'๐ค',scene:'๐ค๐๏ธ๐ก',n:'Zukunft'},
+ {id:'jungle', e:'๐ด',scene:'๐ด๐ฆ๐ฟ',n:'Dschungel', color:'#22c55e',glow:'rgba(34,197,94,0.4)'},
+ {id:'space', e:'๐',scene:'๐๐ชโญ', n:'Weltraum', color:'#818cf8',glow:'rgba(129,140,248,0.4)'},
+ {id:'ocean', e:'๐',scene:'๐๐ ๐',n:'Unterwasser', color:'#06b6d4',glow:'rgba(6,182,212,0.4)'},
+ {id:'fantasy',e:'๐ฐ',scene:'๐ฐ๐โจ', n:'Fantasy', color:'#c084fc',glow:'rgba(192,132,252,0.4)'},
+ {id:'school', e:'๐ซ',scene:'๐ซ๐โ๏ธ',n:'Schule', color:'#fbbf24',glow:'rgba(251,191,36,0.4)'},
+ {id:'volcano',e:'๐',scene:'๐๐๏ธ๐ฅ',n:'Vulkaninsel', color:'#f97316',glow:'rgba(249,115,22,0.4)'},
+ {id:'snow', e:'โ๏ธ',scene:'โ๏ธ๐๏ธโ', n:'Schneereich', color:'#bae6fd',glow:'rgba(186,230,253,0.4)'},
+ {id:'city', e:'๐',scene:'๐๐๐๏ธ',n:'Groรstadt', color:'#94a3b8',glow:'rgba(148,163,184,0.4)'},
+ {id:'candy', e:'๐ญ',scene:'๐ญ๐ฌ๐', n:'Sรผรigkeitenland',color:'#f472b6',glow:'rgba(244,114,182,0.4)'},
+ {id:'desert', e:'๐๏ธ',scene:'๐๏ธ๐ต๐ช',n:'Wรผste', color:'#fcd34d',glow:'rgba(252,211,77,0.4)'},
+ {id:'haunted',e:'๐ป',scene:'๐ป๐๏ธ๐ธ๏ธ',n:'Geisterhaus', color:'#818cf8',glow:'rgba(129,140,248,0.4)'},
+ {id:'future', e:'๐ค',scene:'๐ค๐๏ธ๐ก',n:'Zukunft', color:'#34d399',glow:'rgba(52,211,153,0.4)'},
];
const MINIGAMES=[
{id:'quiz',e:'โ',n:'Quiz',multi:'โ'},{id:'reaction',e:'โก',n:'Reaktion',multi:3},
@@ -1460,28 +1466,13 @@ function doPublish(){
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
INIT
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
-s1init();
-updatePB();
-// Restore to last step
-if(ST.highestStep>0){
- for(let i=0;i
m.id===id);if(!mg)return;
closeMGTest();
+ currentMGId=id;
document.getElementById('mgTestEmoji').textContent=mg.e;
document.getElementById('mgTestTitle').textContent=mg.n+' โ Probespiel';
- const demo=MG_DEMOS[id];
- document.getElementById('mgTestDesc').textContent=demo?demo.desc:'';
+ // Beschreibung aus Modul oder MINIGAMES
+ const mod=window['MG_'+id];
+ document.getElementById('mgTestDesc').textContent=mod?mod.desc:(mg.desc||'');
document.getElementById('mgTestMsg').textContent='';
document.getElementById('mgTestMsg').className='';
const ctrl=document.getElementById('mgTestControls');
ctrl.innerHTML='';
document.getElementById('mgTestOverlay').classList.add('open');
document.body.style.overflow='hidden';
- if(demo)demo.run();
+ // Modul-System: preview() aufrufen
+ const wrap=document.getElementById('mgTestWrap');
+ wrap.innerHTML='';
+ if(mod && mod.preview){
+ // Theme aus aktuellem ST-State
+ const bg=BACKGROUNDS.find(b=>b.id===ST.background);
+ const modCfg={
+ theme: bg?{primary:bg.color||'#7c3aed',glow:bg.glow||'rgba(124,58,237,0.4)'}:{primary:'#7c3aed',glow:'rgba(124,58,237,0.4)'},
+ quizData: ST.quizData||[],
+ devName: ST.devName,
+ gameName: ST.name,
+ };
+ // onResult-Handler
+ window._mgOnResult=(won)=>{
+ setMsg(won?'๐ Gewonnen! ๐':'๐ Verloren! Versuch es nochmal.', won?'win':'lose');
+ };
+ if(window.MGAPI) MGAPI.onResult=window._mgOnResult;
+ mgTestActive=mod.preview(wrap, wrap.offsetWidth||400, 280, modCfg);
+ } else {
+ // Fallback: altes Canvas-System
+ const canvas=document.getElementById('mgTestCanvas');
+ canvas.width=400;canvas.height=280;canvas.style.display='block';
+ wrap.appendChild(canvas);
+ const demo=MG_DEMOS[id];
+ if(demo)demo.run();
+ }
}
function closeMGTest(){
+ // Modul stoppen falls aktiv
+ if(mgTestActive&&mgTestActive.stop){mgTestActive.stop();mgTestActive=null;}
if(mgTestLoop){clearInterval(mgTestLoop);cancelAnimationFrame(mgTestLoop);mgTestLoop=null;}
document.removeEventListener('keydown',mgKeyHandler);
document.removeEventListener('mousemove',mgMouseHandler);
document.getElementById('mgTestOverlay').classList.remove('open');
document.body.style.overflow='';
+ // Wrap leeren
+ const wrap=document.getElementById('mgTestWrap');
+ if(wrap)wrap.innerHTML='';
const canvas=document.getElementById('mgTestCanvas');
- const ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);
+ if(canvas){const ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);}
currentMGId=null;
}
let currentMGId=null;
@@ -1534,69 +1557,778 @@ let mgKeyHandler=null,mgMouseHandler=null;
function setMsg(txt,cls=''){const m=document.getElementById('mgTestMsg');m.textContent=txt;m.className=cls;}
/* โโโ SNAKE โโโ */
-// โโ Mini-Game Modul-Dispatcher โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-// Die run*-Funktionen werden durch das Modul-System ersetzt.
-// Jedes Modul (window.MG_snake etc.) stellt eine preview()-Funktion bereit.
-
-function runMGModule(id) {
- const mod = window[`MG_${id}`];
- if (!mod) { runStub(id, '๐ฎ', 'Modul wird geladen...', id); return; }
-
- currentMGId = id;
- const canvas = document.getElementById('mgTestCanvas');
- const wrap = canvas.parentNode;
- const W = canvas.offsetWidth || 400;
- const H = canvas.offsetHeight || 280;
-
- // Canvas leeren, wrap vorbereiten
- wrap.innerHTML = '';
-
- // onResult-Handler fรผr Vorschau
- window._mgOnResult = (won) => {
- setMsg(won ? '๐ Gewonnen!' : '๐ Verloren!', won ? 'win' : 'lose');
+function runSnake(){
+ currentMGId='snake';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ const CELL=20,COLS=20,ROWS=14;
+ let snake=[{x:10,y:7}],dir={x:1,y:0},nextDir={x:1,y:0};
+ let apple=rndApple(),score=0,running=true;
+ function rndApple(){return{x:Math.floor(Math.random()*COLS),y:Math.floor(Math.random()*ROWS)};}
+ function draw(){
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,canvas.width,canvas.height);
+ ctx.strokeStyle='#1a1827';ctx.lineWidth=0.5;
+ for(let x=0;x{ctx.fillStyle=i===0?'#a78bfa':'#7c3aed';ctx.fillRect(s.x*CELL+1,s.y*CELL+1,CELL-2,CELL-2);});
+ ctx.font=`${CELL-2}px serif`;ctx.textAlign='center';
+ ctx.fillText('๐',apple.x*CELL+CELL/2,apple.y*CELL+CELL/1.2);
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='left';
+ ctx.fillText('รpfel: '+score+' / 3',6,16);
+ }
+ document.removeEventListener('keydown',mgKeyHandler);
+ mgKeyHandler=e=>{
+ const map={'ArrowUp':{x:0,y:-1},'ArrowDown':{x:0,y:1},'ArrowLeft':{x:-1,y:0},'ArrowRight':{x:1,y:0},
+ 'w':{x:0,y:-1},'s':{x:0,y:1},'a':{x:-1,y:0},'d':{x:1,y:0}};
+ if(map[e.key]){e.preventDefault();const d=map[e.key];if(d.x!==-dir.x||d.y!==-dir.y)nextDir=d;}
};
- MGAPI.onResult = window._mgOnResult;
-
- // Thema aus aktuellem ST-State ableiten
- const bg = BACKGROUNDS.find(b => b.id === ST.background);
- const cfg = {
- theme: bg ? { primary: bg.color, glow: bg.glow } : { primary: '#7c3aed', glow: 'rgba(124,58,237,0.4)' },
- quizData: ST.quizData || [],
- devName: ST.devName,
- gameName: ST.name,
- };
-
- if (mgTestActive && mgTestActive.stop) mgTestActive.stop();
- mgTestActive = mod.preview(wrap, W, H, cfg);
+ document.addEventListener('keydown',mgKeyHandler);
+ if(mgTestLoop)clearInterval(mgTestLoop);
+ mgTestLoop=setInterval(()=>{
+ if(!running)return;
+ dir=nextDir;
+ const head={x:snake[0].x+dir.x,y:snake[0].y+dir.y};
+ if(head.x<0||head.x>=COLS||head.y<0||head.y>=ROWS||snake.some(s=>s.x===head.x&&s.y===head.y)){
+ running=false;setMsg('๐ Game Over! Neu starten?','lose');return;}
+ snake.unshift(head);
+ if(head.x===apple.x&&head.y===apple.y){
+ score++;apple=rndApple();
+ if(score>=3){running=false;draw();setMsg('๐ Gewonnen! 3 รpfel gefressen!','win');return;}
+ }else snake.pop();
+ draw();
+ },140);
}
-// Alle run* als Aliase auf den Dispatcher
-function runSnake() { runMGModule('snake'); }
-function runFlappy() { runMGModule('flappy'); }
-function runMemory() { runMGModule('memory'); }
-function runReaction() { runMGModule('reaction'); }
-function runQuizDemo() { runMGModule('quiz'); }
-function runCatch() { runMGModule('catch'); }
-function runBasketball() { runMGModule('basketball'); }
-function runMaze() { runMGModule('maze'); }
-function runSimon() { runMGModule('simon'); }
-function runTyping() { runMGModule('typing'); }
-function runPuzzle() { runMGModule('puzzle'); }
-function runSpotdiff() { runMGModule('spotdiff'); }
-
-function runStub(id, emoji, desc, name) {
- currentMGId = id;
- const canvas = document.getElementById('mgTestCanvas');
- canvas.width = 400; canvas.height = 280;
- const ctx = canvas.getContext('2d');
- ctx.fillStyle = '#0f0e17'; ctx.fillRect(0, 0, 400, 280);
- ctx.font = '60px serif'; ctx.textAlign = 'center'; ctx.fillText(emoji, 200, 130);
- ctx.fillStyle = '#a7a3c2'; ctx.font = 'bold 16px Nunito,sans-serif';
- ctx.fillText(desc, 200, 190);
- ctx.fillStyle = '#7c3aed'; ctx.font = 'bold 13px Nunito,sans-serif';
- ctx.fillText('(Modul wird geladen...)', 200, 220);
+/* โโโ FLAPPY โโโ */
+function runFlappy(){
+ currentMGId='flappy';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ let bird={y:140,vy:0},pipes=[],frame=0,score=0,running=true;
+ const PIPE_GAP=80,PIPE_W=40,GRAV=0.5,JUMP=-8;
+ function addPipe(){const top=40+Math.random()*100;pipes.push({x:400,top,bot:top+PIPE_GAP});}
+ function flap(){if(running)bird.vy=JUMP;}
+ canvas.onclick=flap;
+ document.removeEventListener('keydown',mgKeyHandler);
+ mgKeyHandler=e=>{if(e.code==='Space'&&running){e.preventDefault();flap();}};
+ document.addEventListener('keydown',mgKeyHandler);
+ if(mgTestLoop)cancelAnimationFrame(mgTestLoop);
+ function loop(){
+ if(!running)return;
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ bird.vy+=GRAV;bird.y+=bird.vy;
+ ctx.font='24px serif';ctx.textAlign='center';
+ ctx.fillText('๐ฆ',40,bird.y);
+ if(frame%90===0)addPipe();
+ pipes.forEach(p=>{
+ p.x-=2.5;
+ ctx.fillStyle='#10b981';
+ ctx.fillRect(p.x,0,PIPE_W,p.top);
+ ctx.fillRect(p.x,p.bot,PIPE_W,280-p.bot);
+ if(p.x+PIPE_W<40&&!p.passed){p.passed=true;score++;if(score>=3){running=false;setMsg('๐ Gewonnen! 3 Hindernisse geschafft!','win');return;}}
+ if(p.x<50&&p.x+PIPE_W>26&&(bird.yp.bot)){running=false;setMsg('๐ Getroffen! Neu starten?','lose');}
+ });
+ pipes=pipes.filter(p=>p.x>-PIPE_W);
+ if(bird.y>280||bird.y<0){running=false;setMsg('๐ Abgestรผrzt! Neu starten?','lose');}
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='left';
+ ctx.fillText('Hindernisse: '+score+' / 3',6,16);
+ frame++;
+ mgTestLoop=requestAnimationFrame(loop);
+ }
+ loop();
}
+/* โโโ MEMORY โโโ */
+function runMemory(){
+ currentMGId='memory';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ const EMOJIS=['๐ถ','๐ฑ','๐ฆ','๐ธ','๐ฆ','๐'];
+ let cards=[...EMOJIS,...EMOJIS].sort(()=>Math.random()-0.5).map((e,i)=>({e,i,flipped:false,matched:false}));
+ let first=null,lock=false,pairs=0;
+ const CW=60,CH=60,COLS=6,ROWS=2,OX=20,OY=60;
+ function draw(){
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText('Finde alle '+EMOJIS.length+' Paare!',200,30);
+ cards.forEach((card,i)=>{
+ const col=i%COLS,row=Math.floor(i/COLS);
+ const x=OX+col*(CW+8),y=OY+row*(CH+8);
+ if(card.matched){ctx.fillStyle='rgba(16,185,129,0.2)';}
+ else if(card.flipped){ctx.fillStyle='#22203a';}
+ else{ctx.fillStyle='#22203a';}
+ ctx.beginPath();ctx.roundRect(x,y,CW,CH,8);ctx.fill();
+ if(card.matched){ctx.strokeStyle='#10b981';ctx.lineWidth=2;ctx.stroke();}
+ else if(card.flipped){ctx.strokeStyle='#7c3aed';ctx.lineWidth=2;ctx.stroke();}
+ else{ctx.strokeStyle='#2e2b4a';ctx.lineWidth=1;ctx.stroke();}
+ if(card.flipped||card.matched){ctx.font=`${CW-16}px serif`;ctx.textAlign='center';ctx.fillText(card.e,x+CW/2,y+CH/1.4);}
+ else{ctx.fillStyle='#a7a3c2';ctx.font='bold 22px Nunito';ctx.textAlign='center';ctx.fillText('?',x+CW/2,y+CH/1.5);}
+ });
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='left';
+ ctx.fillText('Paare: '+pairs+' / '+EMOJIS.length,6,16);
+ }
+ canvas.onclick=e=>{
+ if(lock)return;
+ const rect=canvas.getBoundingClientRect();
+ const mx=(e.clientX-rect.left)*(canvas.width/rect.width);
+ const my=(e.clientY-rect.top)*(canvas.height/rect.height);
+ cards.forEach((card,i)=>{
+ if(card.flipped||card.matched)return;
+ const col=i%COLS,row=Math.floor(i/COLS);
+ const x=OX+col*(CW+8),y=OY+row*(CH+8);
+ if(mx>x&&mxy&&my{
+ if(first.e===card.e){first.matched=card.matched=true;pairs++;if(pairs===EMOJIS.length)setMsg('๐ Alle Paare gefunden!','win');}
+ else{first.flipped=card.flipped=false;}
+ first=null;lock=false;draw();
+ },800);
+ }
+ }
+ });
+ };
+ draw();
+}
+
+/* โโโ REACTION โโโ */
+function runReaction(){
+ currentMGId='reaction';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ let phase='wait',startT=0,round=0,times=[],timeout=null;
+ function drawCircle(color,label){
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ ctx.beginPath();ctx.arc(200,140,70,0,Math.PI*2);
+ ctx.fillStyle=color;ctx.fill();
+ ctx.fillStyle='#fff';ctx.font='bold 16px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText(label,200,148);
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';
+ ctx.fillText('Runde '+Math.min(round+1,3)+' / 3',200,24);
+ }
+ function nextRound(){
+ phase='wait';drawCircle('#ef4444','Warte...');
+ if(timeout)clearTimeout(timeout);
+ timeout=setTimeout(()=>{phase='go';startT=Date.now();drawCircle('#10b981','JETZT!');},1500+Math.random()*2000);
+ }
+ canvas.onclick=()=>{
+ if(phase==='go'){
+ const t=Date.now()-startT;times.push(t);round++;
+ if(round>=3){const avg=Math.round(times.reduce((a,b)=>a+b)/times.length);setMsg('๐ Durchschnitt: '+avg+'ms โ '+(avg<500?'Blitzschnell!':'Gut gemacht!'),'win');return;}
+ setMsg('โก '+t+'ms!');nextRound();
+ } else if(phase==='wait'){
+ if(timeout)clearTimeout(timeout);
+ setMsg('Zu frรผh! Warte auf Grรผn.','lose');
+ setTimeout(()=>{setMsg('');nextRound();},1200);
+ }
+ };
+ nextRound();
+}
+
+/* โโโ CATCH โโโ */
+function runCatch(){
+ currentMGId='catch';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ let bowl={x:200},apples=[],score=0,missed=0,running=true;
+ canvas.addEventListener('mousemove',e=>{const r=canvas.getBoundingClientRect();bowl.x=(e.clientX-r.left)*(400/r.width);});
+ canvas.addEventListener('touchmove',e=>{e.preventDefault();const r=canvas.getBoundingClientRect();bowl.x=(e.touches[0].clientX-r.left)*(400/r.width);},{passive:false});
+ if(mgTestLoop)cancelAnimationFrame(mgTestLoop);
+ let frame=0;
+ function loop(){
+ if(!running)return;
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ if(frame%50===0)apples.push({x:20+Math.random()*360,y:0,speed:2+Math.random()*2});
+ apples.forEach(a=>{a.y+=a.speed;ctx.font='24px serif';ctx.textAlign='center';ctx.fillText('๐',a.x,a.y);});
+ // Bowl
+ ctx.fillStyle='#f5a623';ctx.beginPath();ctx.ellipse(bowl.x,260,40,12,0,0,Math.PI*2);ctx.fill();
+ // Check catch/miss
+ apples=apples.filter(a=>{
+ if(a.y>248&&Math.abs(a.x-bowl.x)<40){score++;if(score>=5){running=false;setMsg('๐ 5 รpfel gefangen!','win');}return false;}
+ if(a.y>280){missed++;if(missed>=3){running=false;setMsg('๐ 3 รpfel verpasst!','lose');}return false;}
+ return true;
+ });
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='left';
+ ctx.fillText('Gefangen: '+score+'/5 Verpasst: '+missed+'/3',6,16);
+ frame++;mgTestLoop=requestAnimationFrame(loop);
+ }
+ loop();
+}
+
+/* โโโ BASKETBALL โโโ */
+function runBasketball(){
+ currentMGId='basketball';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ let power=0,dir=1,shooting=false,ball=null,score=0,shots=0,running=true;
+ const HOOP={x:300,y:100};
+ if(mgTestLoop)cancelAnimationFrame(mgTestLoop);
+ function draw(){
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ // Hoop
+ ctx.strokeStyle='#f5a623';ctx.lineWidth=3;
+ ctx.beginPath();ctx.moveTo(HOOP.x-20,HOOP.y);ctx.lineTo(HOOP.x+20,HOOP.y);ctx.stroke();
+ ctx.strokeStyle='#aaa';ctx.lineWidth=2;
+ ctx.beginPath();ctx.moveTo(HOOP.x+20,HOOP.y);ctx.lineTo(HOOP.x+20,HOOP.y+40);ctx.lineTo(HOOP.x+60,HOOP.y+40);ctx.stroke();
+ // Power bar
+ if(!shooting){
+ ctx.fillStyle='#2e2b4a';ctx.fillRect(30,240,160,14);
+ ctx.fillStyle=power<50?'#10b981':'#ef4444';ctx.fillRect(30,240,power*1.6,14);
+ ctx.fillStyle='#fff';ctx.font='bold 11px Nunito';ctx.textAlign='left';ctx.fillText('Klicken = Schieรen!',30,232);
+ }
+ // Ball
+ if(ball){ctx.font='28px serif';ctx.textAlign='center';ctx.fillText('๐',ball.x,ball.y);}
+ else{ctx.font='28px serif';ctx.textAlign='center';ctx.fillText('๐',80,250);}
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='left';
+ ctx.fillText('Treffer: '+score+' Wรผrfe: '+shots+'/5',6,16);
+ }
+ function loop(){
+ if(!shooting){power+=dir*2;if(power>=100||power<=0)dir*=-1;}
+ if(ball){
+ ball.x+=ball.vx;ball.y+=ball.vy;ball.vy+=0.4;
+ if(Math.abs(ball.x-HOOP.x)<22&&Math.abs(ball.y-HOOP.y)<18){score++;setMsg('๐ Treffer! +1','win');}
+ if(ball.y>290){shooting=false;ball=null;if(shots>=5){running=false;setMsg(score>=3?'๐ '+score+'/5 Treffer!':'๐
'+score+'/5 Treffer','win');return;}}
+ }
+ draw();if(running)mgTestLoop=requestAnimationFrame(loop);
+ }
+ canvas.onclick=()=>{
+ if(!running||shooting)return;
+ shots++;shooting=true;
+ const angle=-(0.6+power/100*0.6);const speed=8+power/12;
+ ball={x:80,y:250,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed};
+ setTimeout(()=>{if(shooting){shooting=false;ball=null;}},2000);
+ };
+ loop();
+}
+
+/* โโโ MAZE โโโ */
+function runMaze(){
+ currentMGId='maze';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ const CELL=40,COLS=10,ROWS=7;
+ // Simple fixed maze: 0=open, 1=wall
+ const maze=[
+ [0,1,0,0,0,1,0,0,0,0],
+ [0,1,0,1,0,1,0,1,1,0],
+ [0,0,0,1,0,0,0,0,1,0],
+ [1,1,0,1,1,1,1,0,1,0],
+ [0,0,0,0,0,0,1,0,0,0],
+ [0,1,1,1,0,1,1,1,0,1],
+ [0,0,0,0,0,0,0,0,0,0],
+ ];
+ let player={x:0,y:0},won=false;
+ const GOAL={x:9,y:6};
+ document.removeEventListener('keydown',mgKeyHandler);
+ mgKeyHandler=e=>{
+ if(won)return;
+ const moves={'ArrowUp':{dx:0,dy:-1},'ArrowDown':{dx:0,dy:1},'ArrowLeft':{dx:-1,dy:0},'ArrowRight':{dx:1,dy:0}};
+ if(!moves[e.key])return;e.preventDefault();
+ const{dx,dy}=moves[e.key];const nx=player.x+dx,ny=player.y+dy;
+ if(nx>=0&&nx=0&&ny{
+ ctx.fillStyle=i===lit?COLORS[i]:COLORS[i]+'55';
+ ctx.beginPath();ctx.roundRect(r.x,r.y,r.w,r.h,10);ctx.fill();
+ ctx.fillStyle='#fff';ctx.font='bold 14px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText(LABELS[i],r.x+r.w/2,r.y+r.h/2+5);
+ });
+ ctx.fillStyle='#f5a623';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText('Runde '+seq.length+' / 4',200,22);
+ }
+ function showSeq(i=0){
+ if(i>=seq.length){showing=false;setMsg('Jetzt du! Tippe die Reihenfolge nach.');return;}
+ showing=true;draw(seq[i]);
+ setTimeout(()=>{draw(-1);setTimeout(()=>showSeq(i+1),300);},600);
+ }
+ function nextRound(){seq.push(Math.floor(Math.random()*4));playerSeq=[];step=0;draw();setTimeout(()=>showSeq(),600);}
+ canvas.onclick=e=>{
+ if(showing||won)return;
+ const rect=canvas.getBoundingClientRect();
+ const mx=(e.clientX-rect.left)*(400/rect.width);
+ const my=(e.clientY-rect.top)*(280/rect.height);
+ RECTS.forEach((r,i)=>{
+ if(mx>r.x&&mxr.y&&mydraw(-1),200);
+ if(seq[step]===i){
+ step++;
+ if(step===seq.length){
+ if(seq.length>=4){won=true;setMsg('๐ Simon Says gewonnen!','win');return;}
+ setMsg('โ
Richtig! Weiter...');setTimeout(nextRound,1000);
+ }
+ } else {setMsg('โ Falsch! Nochmal.','lose');setTimeout(()=>{seq=[];nextRound();},1200);}
+ }
+ });
+ };
+ nextRound();
+}
+
+/* โโโ QUIZ DEMO โโโ */
+function runQuizDemo(){
+ currentMGId='quiz';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ // Use own quiz data or fallback
+ const questions=ST.quizData.filter(q=>q.question&&q.answers.every(a=>a)&&q.correct!==null);
+ const fallback=[{question:'Was ist die Hauptstadt von Deutschland?',answers:['Paris','London','Berlin','Madrid'],correct:2},{question:'Wie viele Seiten hat ein Dreieck?',answers:['2','3','4','5'],correct:1}];
+ const pool=questions.length>0?questions:fallback;
+ const q=pool[Math.floor(Math.random()*pool.length)];
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ ctx.fillStyle='#fff';ctx.font='bold 14px Nunito,sans-serif';ctx.textAlign='center';
+ wrapText(ctx,q.question,200,50,360,20);
+ const cols=['#7c3aed','#10b981','#f5a623','#ef4444'];
+ const ANSWERS_RECTS=[{x:20,y:110,w:170,h:60},{x:210,y:110,w:170,h:60},{x:20,y:180,w:170,h:60},{x:210,y:180,w:170,h:60}];
+ ANSWERS_RECTS.forEach((r,i)=>{
+ ctx.fillStyle=cols[i]+'aa';ctx.beginPath();ctx.roundRect(r.x,r.y,r.w,r.h,10);ctx.fill();
+ ctx.fillStyle='#fff';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText(['A','B','C','D'][i]+') '+q.answers[i],r.x+r.w/2,r.y+r.h/2+5);
+ });
+ canvas.onclick=e=>{
+ const rect=canvas.getBoundingClientRect();
+ const mx=(e.clientX-rect.left)*(400/rect.width);
+ const my=(e.clientY-rect.top)*(280/rect.height);
+ ANSWERS_RECTS.forEach((r,i)=>{
+ if(mx>r.x&&mxr.y&&my{
+ ctx.fillStyle=j===q.correct?'#10b981':j===i?'#ef4444':cols[j]+'44';
+ ctx.beginPath();ctx.roundRect(r2.x,r2.y,r2.w,r2.h,10);ctx.fill();
+ ctx.fillStyle='#fff';ctx.font='bold 13px Nunito,sans-serif';ctx.textAlign='center';
+ ctx.fillText(['A','B','C','D'][j]+') '+q.answers[j],r2.x+r2.w/2,r2.y+r2.h/2+5);
+ });
+ wrapText(ctx,q.question,200,50,360,20);
+ setMsg(i===q.correct?'๐ Richtig!':'โ Falsch! Richtig war: '+['A','B','C','D'][q.correct],i===q.correct?'win':'lose');
+ }
+ });
+ };
+}
+function wrapText(ctx,text,x,y,maxW,lh){
+ const words=text.split(' ');let line='';
+ for(let w of words){const test=line+w+' ';if(ctx.measureText(test).width>maxW&&line!==''){ctx.fillText(line.trim(),x,y);line=w+' ';y+=lh;}else line=test;}
+ ctx.fillText(line.trim(),x,y);
+}
+
+/* โโโ STUBS FOR OTHER GAMES โโโ */
+function runPuzzle(){runStub('puzzle','๐งฉ','Sortiere die Zahlen!','Zahlen-Reihenfolge');}
+function runSpotdiff(){runStub('spotdiff','๐','Finde den Unterschied zwischen den Bildern.','Unterschied finden');}
+function runTyping(){
+ currentMGId='typing';
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ const WORDS=['Hallo Welt','Spielen macht Spaร','Quiz Turnier','Abenteuer'];
+ const target=WORDS[Math.floor(Math.random()*WORDS.length)];
+ let typed='',done=false,startT=Date.now();
+ function draw(){
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ ctx.fillStyle='#a7a3c2';ctx.font='14px Nunito,sans-serif';ctx.textAlign='center';ctx.fillText('Tippe den folgenden Text:',200,60);
+ ctx.fillStyle='#f5a623';ctx.font='bold 22px Fredoka One,cursive';ctx.fillText(target,200,110);
+ // Show typed so far
+ for(let i=0;i{
+ if(done)return;
+ if(e.key==='Backspace'){typed=typed.slice(0,-1);}
+ else if(e.key.length===1){typed+=e.key;}
+ if(typed===target){done=true;const t=((Date.now()-startT)/1000).toFixed(1);setMsg('๐ Fertig in '+t+'s!','win');}
+ draw();
+ };
+ document.addEventListener('keydown',mgKeyHandler);
+ draw();
+}
+function runStub(id,emoji,desc,name){
+ currentMGId=id;
+ const canvas=document.getElementById('mgTestCanvas');canvas.width=400;canvas.height=280;
+ const ctx=canvas.getContext('2d');
+ ctx.fillStyle='#0f0e17';ctx.fillRect(0,0,400,280);
+ ctx.font='60px serif';ctx.textAlign='center';ctx.fillText(emoji,200,130);
+ ctx.fillStyle='#a7a3c2';ctx.font='bold 16px Nunito,sans-serif';ctx.fillText(desc,200,190);
+ ctx.fillStyle='#7c3aed';ctx.font='bold 13px Nunito,sans-serif';ctx.fillText('(Im echten Spiel vollstรคndig spielbar)',200,220);
+}
+
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ PYTHON CODE GENERATOR โ Live-Vorschau
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+
+const MINIGAME_PY = {
+ snake: 'snake_game',
+ flappy: 'flappy_bird',
+ memory: 'memory_cards',
+ quiz: 'quiz_challenge',
+ reaction: 'reaction_test',
+ basketball: 'basketball_shot',
+ catch: 'apple_catcher',
+ maze: 'maze_runner',
+ simon: 'simon_says',
+ puzzle: 'number_puzzle',
+ spotdiff: 'spot_difference',
+ typing: 'typing_race',
+};
+const MINIGAME_COMMENTS = {
+ snake: 'Schlange steuern โ Iss das Futter ohne gegen die Wand zu fahren',
+ flappy: 'Flugspiel โ Durch Hindernisse fliegen ohne zu crashen',
+ memory: 'Memory โ Alle Paare finden und merken',
+ quiz: 'Quiz โ Frage beantworten, richtige Antwort wรคhlen',
+ reaction: 'Reaktionstest โ So schnell wie mรถglich auf Signal reagieren',
+ basketball: 'Basketball โ Ball zum richtigen Zeitpunkt werfen',
+ catch: 'Fangspiel โ Fallende Objekte mit dem Korb auffangen',
+ maze: 'Labyrinth โ Den Ausweg aus dem Labyrinth finden',
+ simon: 'Simon Says โ Farbsequenz merken und wiederholen',
+ puzzle: 'Zahlenrรคtsel โ Teile in die richtige Reihenfolge bringen',
+ spotdiff: 'Unterschiede suchen โ Alle Unterschiede im Bild finden',
+ typing: 'Tipp-Rennen โ Text so schnell wie mรถglich eintippen',
+};
+
+// โโ Progressive unlock tracking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+const unlocked = new Set();
+let lastKey = null; // which section was LAST interacted with โ gets yellow highlight
+
+function setCodeFocus(key){
+ unlocked.add(key);
+ lastKey = key;
+ scheduleCodeUpdate(50);
+}
+
+// Focus listener for text inputs
+document.addEventListener('focusin', e=>{
+ const id = e.target.id || '';
+ if(id==='s1devname') setCodeFocus('dev');
+ if(id==='s1name') setCodeFocus('gamename');
+ if(id==='s1desc') setCodeFocus('gamedesc');
+ if(id && id.startsWith('qq')) setCodeFocus('quiz');
+});
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function buildPythonCode(){
+ const s = ST;
+ const fig = FIGURES.find(f=>f.id===s.figure);
+ const bg = BACKGROUNDS.find(b=>b.id===s.background);
+
+ // Empty until first interaction
+ if(unlocked.size===0 && !s.devName && !s.name && !s.figure) return [];
+
+ const lines = [];
+ // hi() returns true only for the last-touched key
+ const hi = key => lastKey === key;
+
+ // Header + imports โ always shown once anything unlocked
+ lines.push({t:'cm', v:'# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'});
+ lines.push({t:'cm', v:'# boardgame.py โ Spiel-Generator PH Weingarten'});
+ lines.push({t:'cm', v:'# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'});
+ lines.push({t:'bl'});
+ lines.push({t:'cm', v:'# Module importieren'});
+ lines.push({t:'kw', v:'import', rest:' boardgame_engine as engine'});
+ lines.push({t:'kw', v:'import', rest:' minigames'});
+ lines.push({t:'kw', v:'from', rest:' config import GameConfig, Field, StoryText'});
+ lines.push({t:'bl'});
+
+ // DEVELOPER
+ if(unlocked.has('dev') || s.devName){
+ lines.push({t:'cm', v:'# Wer hat dieses Spiel programmiert?'});
+ if(s.devName)
+ lines.push({t:'ass', var:'developer_name', val:`"${s.devName}"`, hi:hi('dev')});
+ else
+ lines.push({t:'ph', var:'developer_name', hint:'โ tippe deinen Namen'});
+ lines.push({t:'bl'});
+ }
+
+ // GAME NAME
+ if(unlocked.has('gamename') || s.name){
+ lines.push({t:'cm', v:'# Wie heiรt das Spiel?'});
+ if(s.name)
+ lines.push({t:'ass', var:'game_name', val:`"${s.name}"`, hi:hi('gamename')});
+ else
+ lines.push({t:'ph', var:'game_name', hint:'โ tippe den Spielnamen'});
+ lines.push({t:'bl'});
+ }
+
+ // DESCRIPTION
+ if(unlocked.has('gamedesc') || s.desc){
+ lines.push({t:'cm', v:'# Kurze Beschreibung des Spiels'});
+ if(s.desc)
+ lines.push({t:'ass', var:'game_description', val:`"${s.desc}"`, hi:hi('gamedesc')});
+ else
+ lines.push({t:'ph', var:'game_description', hint:'โ tippe eine Beschreibung'});
+ lines.push({t:'bl'});
+ }
+
+ // FIGURE
+ if(unlocked.has('figure') || s.figure){
+ lines.push({t:'cm', v:'# Spielfigur auswรคhlen'});
+ if(s.figure)
+ lines.push({t:'ass', var:'player_figure',
+ val:`"${s.figure}" # ${fig?fig.e+' '+fig.n:''}`, hi:hi('figure')});
+ else
+ lines.push({t:'ph', var:'player_figure', hint:'โ klicke eine Figur an'});
+ lines.push({t:'bl'});
+ }
+
+ // BACKGROUND
+ if(unlocked.has('background') || s.background){
+ lines.push({t:'cm', v:'# Spielwelt / Hintergrund'});
+ if(s.background)
+ lines.push({t:'ass', var:'world_setting',
+ val:`"${s.background}" # ${bg?bg.e+' '+bg.n:''}`, hi:hi('background')});
+ else
+ lines.push({t:'ph', var:'world_setting', hint:'โ klicke eine Welt an'});
+ lines.push({t:'bl'});
+ }
+
+ // MOVEMENT
+ if(unlocked.has('movement')){
+ lines.push({t:'cm', v:'# Wie viele Felder pro Runde?'});
+ lines.push({t:'ass', var:'movement_type',
+ val: s.rules.movement==='step' ? '"schritt" # immer genau 1 Feld'
+ : '"wuerfeln" # zufรคllig 1-6 Felder',
+ hi:hi('movement')});
+ lines.push({t:'bl'});
+ }
+
+ // FAIL MODE
+ if(unlocked.has('failmode')){
+ lines.push({t:'cm', v:'# Was passiert wenn man verliert?'});
+ if(s.rules.fail==='lives'){
+ lines.push({t:'ass', var:'verlust_system', val:'"leben"', hi:hi('failmode')});
+ lines.push({t:'ass', var:'anzahl_leben', val:String(s.rules.lives||3), hi:hi('lives')});
+ } else {
+ lines.push({t:'ass', var:'verlust_system', val:'"punkte"', hi:hi('failmode')});
+ lines.push({t:'ass', var:'punkte_pro_sieg',val:String(s.rules.pts||10), hi:hi('pts')});
+ }
+ lines.push({t:'bl'});
+ }
+
+ // LIVES / PTS sub-option
+ if(unlocked.has('lives') && s.rules.fail==='lives'){
+ // already rendered above, just make sure hi updates
+ }
+
+ // FIELD COUNT
+ if(unlocked.has('fieldcount')){
+ lines.push({t:'cm', v:'# Wie groร ist das Spielfeld?'});
+ lines.push({t:'ass', var:'anzahl_felder', val:String(s.fieldCount), hi:hi('fieldcount')});
+ lines.push({t:'bl'});
+ }
+
+ // BOARD SETUP โ only once a game was dropped
+ const MG_PY ={snake:'schlangen_spiel',flappy:'flug_spiel',memory:'memory',quiz:'quiz',reaction:'reaktionstest',basketball:'basketball',catch:'fangen',maze:'labyrinth',simon:'simon_says',puzzle:'raetsel',spotdiff:'unterschiede',typing:'tipp_rennen'};
+ const MG_CM ={snake:'Schlange steuern',flappy:'Durch Hindernisse fliegen',memory:'Paare finden',quiz:'Frage beantworten',reaction:'Schnell reagieren',basketball:'Ball werfen',catch:'Objekte fangen',maze:'Ausweg finden',simon:'Sequenz merken',puzzle:'Rรคtsel lรถsen',spotdiff:'Unterschiede finden',typing:'Text eintippen'};
+
+ const hasAnyField = (s.fields||[]).some(Boolean);
+ if(unlocked.has('fields') || hasAnyField){
+ lines.push({t:'cm', v:'# Spielfeld aufbauen โ jedes Feld wird definiert'});
+ lines.push({t:'fn-def', name:'setup_spielfeld', args:''});
+ lines.push({t:'ind', v:'felder = []'});
+ lines.push({t:'bl'});
+ lines.push({t:'ind-cm', v:'# Startfeld (Index 0)'});
+ lines.push({t:'ind', v:'felder.append(Field(index=0, typ="start"))'});
+
+ for(let i=1;i0){
+ const MG_CM2={snake:'Schlange steuern ohne Wand zu treffen',flappy:'Durch alle Hindernisse fliegen',memory:'Alle Kartenpaare finden',quiz:'Die richtige Antwort wรคhlen',reaction:'Schnell auf Signal drรผcken',basketball:'Ball zum richtigen Moment werfen',catch:'Fallende Objekte auffangen',maze:'Den Ausgang aus dem Labyrinth finden',simon:'Farbreihenfolge merken und wiederholen',puzzle:'Zahlen in die richtige Reihenfolge bringen',spotdiff:'Alle Unterschiede im Bild finden',typing:'Text so schnell wie mรถglich eintippen'};
+ lines.push({t:'cm', v:'# Fรผr jedes Mini-Game gibt es eine eigene Funktion'});
+ usedGames.forEach(id=>{
+ const pyName=MG_PY[id]||id;
+ lines.push({t:'bl'});
+ lines.push({t:'fn-def', name:`spiele_${pyName}`, args:'spieler'});
+ lines.push({t:'ind-cm', v:`# Aufgabe: ${MG_CM2[id]||id}`});
+ lines.push({t:'ind', v:`ergebnis = minigames.${pyName}.starten(spieler)`});
+ lines.push({t:'ind', v:`return ergebnis.gewonnen`});
+ });
+ lines.push({t:'bl'});
+ }
+
+ // QUIZ
+ const quizItems=(s.quizData||[]).filter(q=>q&&q.question);
+ if(quizItems.length>0 || unlocked.has('quiz')){
+ lines.push({t:'cm', v:'# Quiz-Fragen fรผr das Spiel'});
+ lines.push({t:'ass', var:'fragen', val:'[', hi:false});
+ quizItems.forEach(q=>{
+ const clean=q.question.replace(/"/g,"'").slice(0,50);
+ const ans=q.answers.map(a=>`"${(a||'').replace(/"/g,"'")}"`).join(', ');
+ const cor=q.correct!=null?`"${(q.answers[q.correct]||'').replace(/"/g,"'")}"`:'None';
+ lines.push({t:'ind', v:`{"frage": "${clean}", "antworten": [${ans}], "richtig": ${cor}},`});
+ });
+ if(quizItems.length===0) lines.push({t:'ind-cm', v:'# noch keine Fragen eingetragen...'});
+ lines.push({t:'ass-end', v:']'});
+ lines.push({t:'bl'});
+ }
+
+ // MAIN BLOCK
+ lines.push({t:'cm', v:'# โโ Spiel starten โโโโโโโโโโโโโโโโโโโโโโโโโโโ'});
+ lines.push({t:'kw', v:'if', rest:" __name__ == '__main__':"});
+ lines.push({t:'ind-cm', v:'# Konfiguration zusammenstellen und Spiel starten'});
+ lines.push({t:'ind', v:'config = GameConfig('});
+ lines.push({t:'ind2', v:`name=${s.name?`"${s.name}"`:'""'},`});
+ lines.push({t:'ind2', v:`developer=${s.devName?`"${s.devName}"`:'""'},`});
+ if(s.figure) lines.push({t:'ind2', v:`figure="${s.figure}",`});
+ if(s.background) lines.push({t:'ind2', v:`setting="${s.background}",`});
+ if(hasAnyField) lines.push({t:'ind2', v:'board=setup_spielfeld(),'});
+ if(quizItems.length) lines.push({t:'ind2', v:'questions=fragen,'});
+ lines.push({t:'ind', v:')'});
+ lines.push({t:'bl'});
+ lines.push({t:'ind', v:'engine.run(config)'});
+
+ return lines;
+}
+
+function renderToken(line){
+ const esc = s => String(s).replace(/&/g,'&').replace(//g,'>');
+ let r='';
+ switch(line.t){
+ case 'cm': r=`${esc(line.v)}`; break;
+ case 'bl': return '';
+ case 'ph': r=`${esc(line.var)} = "???" ${esc(line.hint||'')}`; break;
+ case 'kw': r=`${esc(line.v)}${esc(line.rest||'')}`; break;
+ case 'ass': r=`${esc(line.var)} = ${esc(String(line.val))}`; break;
+ case 'ass-end':r=`${esc(line.v)}`; break;
+ case 'fn-def': r=`def ${esc(line.name)}(${esc(line.args||'')}):`; break;
+ case 'ind': r=` ${esc(line.v)}`; break;
+ case 'ind2': r=` ${esc(line.v)}`; break;
+ case 'ind-cm': r=` ${esc(line.v)}`; break;
+ default: return esc(line.v||'');
+ }
+ return line.hi ? `${r}` : r;
+}
+
+let codeTypingTimer = null;
+let lastLineCount = 0;
+let lastKey_scroll = null; // prevent scroll thrash
+
+function updateCodePane(){
+ const lines = buildPythonCode();
+ const codeEl = document.getElementById('codeContent');
+ const numsEl = document.getElementById('codeLineNums');
+ const linesEl= document.getElementById('codeLines');
+ if(!codeEl) return;
+
+ // Filename
+ const dev = ST.devName ? ST.devName.toLowerCase().replace(/[^a-z0-9]/g,'_') : 'dev';
+ const gn = ST.name ? ST.name.toLowerCase().replace(/[^a-z0-9]/g,'_').slice(0,20) : 'boardgame';
+ const fnEl = document.getElementById('codeFilename');
+ if(fnEl) fnEl.textContent = (ST.name||ST.devName) ? `${gn}_von_${dev}.py` : 'boardgame.py';
+
+ if(lines.length===0){
+ codeEl.innerHTML='# Klicke ein Feld an โ dein Code erscheint hier โจ';
+ numsEl.innerHTML='1
';
+ if(linesEl) linesEl.textContent='1 Zeile';
+ lastLineCount=0; return;
+ }
+
+ let html='', lineNum=1;
+ const lineNums=[];
+ lines.forEach(line=>{
+ if(line.t==='bl'){ html+='\n'; }
+ else { html+=renderToken(line)+'\n'; }
+ lineNums.push(lineNum++);
+ });
+
+ const newCount = lines.length;
+ const grew = newCount > lastLineCount;
+ const prevCount= lastLineCount;
+ lastLineCount = newCount;
+
+ codeEl.innerHTML = html;
+ numsEl.innerHTML = lineNums.map(n=>`${n}
`).join('');
+ if(linesEl) linesEl.textContent = lineNum+' Zeilen';
+
+ const status = document.getElementById('codeStatus');
+ if(status){ status.textContent='โ Live'; setTimeout(()=>{ status.textContent='โ Bereit'; },800); }
+
+ // Scroll: only when new lines appeared AND key changed (avoid thrash while typing)
+ const scroll = document.getElementById('codeScroll');
+ if(scroll && grew && lastKey !== lastKey_scroll){
+ lastKey_scroll = lastKey;
+ // Find the first highlighted line's position
+ setTimeout(()=>{
+ const hiEl = codeEl.querySelector('.py-hi');
+ if(hiEl){
+ const pre = codeEl;
+ const preTop = pre.getBoundingClientRect().top;
+ const hiTop = hiEl.getBoundingClientRect().top;
+ const offset = hiTop - preTop;
+ const scrollTarget = scroll.scrollTop + offset - 80;
+ scroll.scrollTo({top: Math.max(0, scrollTarget), behavior:'smooth'});
+ }
+ }, 80);
+ }
+}
+
+function scheduleCodeUpdate(delay=80){
+ clearTimeout(codeTypingTimer);
+ codeTypingTimer = setTimeout(updateCodePane, delay);
+}
+
+document.addEventListener('input', ()=>scheduleCodeUpdate(50));
+document.addEventListener('change', ()=>scheduleCodeUpdate(50));
+
+// Initial render
+setTimeout(updateCodePane, 200);
+
+
+
+
+
+
+
@@ -1609,7 +2341,9 @@ function runStub(id, emoji, desc, name) {