diff --git a/editor.html b/editor.html index e49ec96..d5230ed 100644 --- a/editor.html +++ b/editor.html @@ -401,7 +401,7 @@ label.lbl{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing .mg-card.disabled .mg-test-btn{display:none;} #mgTestOverlay{position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:5000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(4px);} #mgTestOverlay.open{display:flex;} -#mgTestBox{background:#1a1827;border:1.5px solid #2e2b4a;border-radius:20px;padding:0;width:min(480px,95vw);max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 24px 60px rgba(0,0,0,0.7);} +#mgTestBox{background:#1a1827;border:1.5px solid #2e2b4a;border-radius:20px;padding:0;width:min(520px,95vw);max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 24px 60px rgba(0,0,0,0.7);} #mgTestHeader{display:flex;align-items:center;gap:12px;padding:18px 22px;border-bottom:1px solid #2e2b4a;background:#22203a;} #mgTestEmoji{font-size:2rem;} #mgTestTitle{font-family:'Fredoka One',cursive;font-size:1.3rem;color:#f5a623;flex:1;} @@ -498,7 +498,7 @@ label.lbl{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing
- PH Weingarten + PH Weingarten
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;im.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) {
- +
+ +
@@ -1643,8 +2377,8 @@ function runStub(id, emoji, desc, name) {
+ - @@ -1658,7 +2392,25 @@ function runStub(id, emoji, desc, name) { - - + + diff --git a/game.html b/game.html index 6087876..82197be 100644 --- a/game.html +++ b/game.html @@ -398,13 +398,20 @@ const BG_THEMES={ }; const THEME = BG_THEMES[cfg.background] || BG_THEMES.space; -// MG_INFO wird dynamisch aus den geladenen Modulen aufgebaut -function getMgInfo(id) { - const mod = window[`MG_${id}`]; - if (mod) return { emoji: mod.emoji, name: mod.name, desc: mod.desc, controls: mod.controls }; - return { emoji: '๐ŸŽฎ', name: id, desc: 'Mini-Game!', controls: 'Variiert' }; -} - +const MG_INFO={ + snake: {emoji:'๐Ÿ',name:'Snake', desc:'Steuere die Schlange! Iss das Essen ohne gegen die Wand zu fahren.',controls:'Pfeiltasten oder WASD'}, + flappy: {emoji:'๐Ÿฆ',name:'Flappy Bird', desc:'Klick oder Leertaste, um durch die Rรถhren zu fliegen!', controls:'Klick / Leertaste'}, + memory: {emoji:'๐Ÿƒ',name:'Memory', desc:'Finde alle Paare โ€” so schnell wie mรถglich!', controls:'Mausklick'}, + quiz: {emoji:'โ“',name:'Quiz', desc:'Beantworte die Frage richtig!', controls:'Mausklick'}, + reaction: {emoji:'โšก',name:'Reaktion', desc:'Drรผck den Knopf so schnell du kannst wenn er erscheint!', controls:'Klick / Leertaste'}, + basketball:{emoji:'๐Ÿ€',name:'Basketball', desc:'Ziehe den Ball und lass ihn los um zu werfen!', controls:'Maus ziehen'}, + catch: {emoji:'๐ŸŽ',name:'ร„pfel fangen',desc:'Bewege den Korb mit den Pfeiltasten!', controls:'โ† โ†’ oder A/D'}, + maze: {emoji:'๐ŸŒ€',name:'Labyrinth', desc:'Finde den Ausgang!', controls:'Pfeiltasten / WASD'}, + simon: {emoji:'๐Ÿ”ด',name:'Simon Says', desc:'Merke und wiederhole die Farb-Sequenz!', controls:'Mausklick'}, + puzzle: {emoji:'๐Ÿงฉ',name:'Rรคtsel', desc:'Lรถse die Aufgabe!', controls:'Mausklick'}, + spotdiff: {emoji:'๐Ÿ”',name:'Unterschiede',desc:'Finde alle Unterschiede!', controls:'Mausklick'}, + typing: {emoji:'โŒจ๏ธ',name:'Tipp-Rennen', desc:'Tippe das Wort so schnell wie mรถglich!', controls:'Tastatur'}, +}; const figEmoji = FIGURES[cfg.figure] || '๐ŸŽฎ'; const LETTERS = ['A','B','C','D']; @@ -959,56 +966,47 @@ function enableRoll(){ /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• MINIGAME SYSTEM โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ function openMinigame(id){ - const info = getMgInfo(id); - currentMgId = id; - document.getElementById('mgTitle').innerHTML = `${info.emoji} ${info.name}`; - document.getElementById('mgDesc').textContent = info.desc; - document.getElementById('mgControls').innerHTML = `๐ŸŽฎ ${info.controls}`; - document.getElementById('mgResultBar').style.display = 'none'; - document.getElementById('btnMgContinue').style.display = 'none'; + const info=MG_INFO[id]||{emoji:'๐ŸŽฎ',name:id,desc:'Mini-Game!',controls:'Variiert'}; + currentMgId=id; + document.getElementById('mgTitle').innerHTML=`${info.emoji} ${info.name}`; + document.getElementById('mgDesc').textContent=info.desc; + document.getElementById('mgControls').innerHTML=`๐ŸŽฎ ${info.controls}`; + document.getElementById('mgResultBar').style.display='none'; + document.getElementById('btnMgContinue').style.display='none'; - const wrap = document.getElementById('mgCanvasWrap'); - wrap.innerHTML = ''; - wrap.style.minHeight = ''; + const wrap=document.getElementById('mgCanvasWrap'); + wrap.innerHTML=''; - if (mgActive && mgActive.stop) mgActive.stop(); - mgActive = null; mgResult = null; + if(mgActive&&mgActive.stop) mgActive.stop(); + mgActive=null; mgResult=null; playSfx('mg'); document.getElementById('mgOverlay').classList.add('open'); - const W = Math.min(560, window.innerWidth - 80) - 48; - const H = 300; + const W=Math.min(560, window.innerWidth-80)-48; + const H=260; - // Theme aus THEME-Objekt - const modCfg = { - theme: { primary: THEME.primary, glow: THEME.glow }, - quizData: quizData, - fieldIndex: pos, - devName: cfg.devName, - gameName: cfg.name, + const launchers={ + snake: launchSnake, + flappy: launchFlappy, + memory: launchMemory, + quiz: launchQuiz, + reaction:launchReaction, }; - // onResult-Handler setzen - window._mgOnResult = finishMinigame; - if (window.MGAPI) MGAPI.onResult = finishMinigame; - - const mod = window[`MG_${id}`]; - if (mod) { - mgActive = mod.launch(wrap, W, H, modCfg); - } else { - // Fallback - wrap.style.minHeight = H + 'px'; - wrap.innerHTML = `
+ if(launchers[id]){ mgActive=launchers[id](wrap,W,H); } + else { + // Placeholder for other games + wrap.style.minHeight=H+'px'; + wrap.innerHTML=`
${info.emoji}
${info.name}
-
Modul wird geladen...
+
Dieses Mini-Game wird bald verfรผgbar sein!
`; } } - function finishMinigame(won){ if(mgActive&&mgActive.stop) mgActive.stop(); mgActive=null; @@ -1053,6 +1051,213 @@ function skipMinigame(){ enableRoll(); } +/* โ•โ• SNAKE โ•โ• */ +function launchSnake(wrap,W,H){ + const canvas=document.createElement('canvas'); + canvas.width=W; canvas.height=H; + wrap.appendChild(canvas); + const ctx=canvas.getContext('2d'); + const SZ=20, cols=Math.floor(W/SZ), rows=Math.floor(H/SZ); + let snake=[{x:5,y:5},{x:4,y:5},{x:3,y:5}],dir={x:1,y:0},food={x:10,y:8},score=0,dead=false,started=true; + let raf,stopped=false; + const onKey=e=>{ + if(e.key==='ArrowUp'&&dir.y===0)dir={x:0,y:-1}; + if(e.key==='ArrowDown'&&dir.y===0)dir={x:0,y:1}; + if(e.key==='ArrowLeft'&&dir.x===0)dir={x:-1,y:0}; + if(e.key==='ArrowRight'&&dir.x===0)dir={x:1,y:0}; + if(e.key==='w')dir={x:0,y:-1}; if(e.key==='s')dir={x:0,y:1}; + if(e.key==='a')dir={x:-1,y:0}; if(e.key==='d')dir={x:1,y:0}; + }; + document.addEventListener('keydown',onKey); + let last=0, endTimer=null; + function loop(ts){ + if(stopped)return; raf=requestAnimationFrame(loop); + if(ts-last<140)return; last=ts; + if(!dead){ + const h={x:snake[0].x+dir.x,y:snake[0].y+dir.y}; + if(h.x<0||h.x>=cols||h.y<0||h.y>=rows||snake.some(s=>s.x===h.x&&s.y===h.y)){ + dead=true; + if(!endTimer) endTimer=setTimeout(()=>finishMinigame(score>=3),1200); + } else { + snake.unshift(h); + if(h.x===food.x&&h.y===food.y){score++;food={x:Math.floor(Math.random()*cols),y:Math.floor(Math.random()*rows)};} + else snake.pop(); + } + } + ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H); + ctx.fillStyle=THEME.primary; ctx.beginPath(); ctx.arc(food.x*SZ+SZ/2,food.y*SZ+SZ/2,SZ*0.4,0,Math.PI*2); ctx.fill(); + snake.forEach((s,i)=>{ + ctx.fillStyle=i===0?THEME.primary:`${THEME.primary}99`; + ctx.fillRect(s.x*SZ+1,s.y*SZ+1,SZ-2,SZ-2); + }); + ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`๐Ÿ• ${score} Ziel: 3`,8,18); + if(dead){ + ctx.fillStyle='rgba(239,68,68,0.8)'; ctx.fillRect(0,H/2-28,W,56); + ctx.fillStyle='#fff'; ctx.font='bold 20px Fredoka One,cursive'; ctx.textAlign='center'; + ctx.fillText(score>=3?'๐ŸŽ‰ Geschafft!':'๐Ÿ’€ Game Over',W/2,H/2+6); + } + } + raf=requestAnimationFrame(loop); + return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);}}; +} + +/* โ•โ• FLAPPY โ•โ• */ +function launchFlappy(wrap,W,H){ + const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas); + const ctx=canvas.getContext('2d'); + let bird={y:H/2,vy:0},pipes=[{x:W,gap:Math.random()*(H-100)+30}],score=0,dead=false,started=false; + const GRAV=0.5,JUMP=-8,PW=38,GAP=85; + let raf,stopped=false,endTimer=null; + const jump=()=>{if(!dead){bird.vy=JUMP;started=true;}}; + const onKey=e=>{if(e.code==='Space'){e.preventDefault();jump();}}; + document.addEventListener('keydown',onKey); canvas.addEventListener('click',jump); + function loop(){ + if(stopped)return; raf=requestAnimationFrame(loop); + ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H); + if(started&&!dead){ + bird.vy+=GRAV; bird.y+=bird.vy; + pipes.forEach(p=>p.x-=2.8); + if(pipes[pipes.length-1].xp.x>-PW); + pipes.forEach(p=>{if(p.x+PW<50&&!p.passed){p.passed=true;score++;}}); + if(bird.y<0||bird.y+20>H)dead=true; + pipes.forEach(p=>{if(50p.x&&(bird.yp.gap+GAP))dead=true;}); + if(dead&&!endTimer) endTimer=setTimeout(()=>finishMinigame(score>=3),1000); + } + pipes.forEach(p=>{ + ctx.fillStyle=THEME.primary+'bb'; + ctx.fillRect(p.x,0,PW,p.gap); ctx.fillRect(p.x,p.gap+GAP,PW,H-p.gap-GAP); + }); + ctx.fillStyle=dead?'#ef4444':THEME.primary; + ctx.beginPath(); ctx.ellipse(50+14,bird.y+10,14,10,bird.vy*0.04,0,Math.PI*2); ctx.fill(); + ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`๐Ÿฆ ${score} Ziel: 3`,8,18); + if(!started){ctx.fillStyle='rgba(255,255,255,0.7)';ctx.textAlign='center';ctx.font='13px Nunito,sans-serif';ctx.fillText('Klicken oder Leertaste',W/2,H/2+40);} + if(dead){ctx.fillStyle='rgba(239,68,68,0.8)';ctx.fillRect(0,H/2-28,W,56);ctx.fillStyle='#fff';ctx.font='bold 20px Fredoka One,cursive';ctx.textAlign='center';ctx.fillText(score>=3?'๐ŸŽ‰ Geschafft!':'๐Ÿ’€ Daneben!',W/2,H/2+6);} + } + raf=requestAnimationFrame(loop); + return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);canvas.removeEventListener('click',jump);}}; +} + +/* โ•โ• MEMORY โ•โ• */ +function launchMemory(wrap,W,H){ + const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas); + const ctx=canvas.getContext('2d'); + const emojis=['๐Ÿฑ','๐Ÿถ','๐ŸฆŠ','๐Ÿธ','๐Ÿฆ‹','๐Ÿ ']; + const all=[...emojis,...emojis].sort(()=>Math.random()-0.5); + let cards=all.map((e,i)=>({e,i,open:false})); + let flipped=[],matched=[],checking=false,moves=0; + let raf,stopped=false; + const C=4,R=3,cw=Math.floor((W-16)/(C+0.5)),ch=Math.floor((H-16)/R); + const ox=(W-C*cw)/2, oy=(H-R*ch)/2; + canvas.addEventListener('click',e=>{ + if(checking)return; + const r=canvas.getBoundingClientRect(); + const mx=e.clientX-r.left, my=e.clientY-r.top; + for(let ri=0;ri=x&&mx=y&&my{ + if(flipped[0].e===flipped[1].e) matched.push(flipped[0].i,flipped[1].i); + else flipped.forEach(c=>c.open=false); + flipped=[]; checking=false; + if(matched.length===cards.length) setTimeout(()=>finishMinigame(true),600); + },700); + } + break; + } + } + }); + function loop(){ + if(stopped)return; raf=requestAnimationFrame(loop); + ctx.fillStyle='#050508'; ctx.fillRect(0,0,W,H); + ctx.fillStyle='#fff'; ctx.font='bold 13px Nunito,sans-serif'; ctx.textAlign='left'; ctx.fillText(`๐Ÿƒ ${matched.length/2}/${emojis.length} Paare Zรผge: ${moves}`,8,18); + for(let ri=0;ri{stopped=true;cancelAnimationFrame(raf);}}; +} + +/* โ•โ• QUIZ โ•โ• */ +function launchQuiz(wrap,W,H){ + const q=quizData[quizIdx]||null; quizIdx++; + if(!q||!q.question){ wrap.innerHTML=`
โ“
Keine Quiz-Frage hinterlegt.
`; return{stop:()=>{}};} + let answered=false; + wrap.innerHTML=` +
+
โ“ Quiz-Frage
+
${q.question}
+
+ +
+ `; + const grid=wrap.querySelector('#quizAnswers'); + LETTERS.forEach((L,i)=>{ + const btn=document.createElement('button'); + btn.style.cssText=`background:rgba(255,255,255,0.06);border:1.5px solid rgba(255,255,255,0.12);border-radius:10px;padding:10px 12px;font-family:'Nunito',sans-serif;font-size:13px;font-weight:700;color:#fff;cursor:pointer;display:flex;align-items:center;gap:8px;transition:all 0.2s;text-align:left;`; + btn.innerHTML=`${L}${q.answers[i]||'?'}`; + btn.addEventListener('mouseenter',()=>{ if(!answered) btn.style.borderColor=THEME.primary; }); + btn.addEventListener('mouseleave',()=>{ if(!answered) btn.style.borderColor='rgba(255,255,255,0.12)'; }); + btn.addEventListener('click',()=>{ + if(answered)return; answered=true; + const correct=i===q.correct; + btn.style.borderColor=correct?'#10b981':'#ef4444'; + btn.style.background=correct?'rgba(16,185,129,0.15)':'rgba(239,68,68,0.1)'; + if(!correct){ + const cBtn=grid.children[q.correct]; + cBtn.style.borderColor='#10b981'; cBtn.style.background='rgba(16,185,129,0.15)'; + } + const res=wrap.querySelector('#quizResult'); + res.style.display='block'; + res.style.color=correct?'#10b981':'#ef4444'; + res.textContent=correct?'โœ… Richtig!':'โŒ Falsch!'; + setTimeout(()=>finishMinigame(correct),1200); + }); + grid.appendChild(btn); + }); + return{stop:()=>{}}; +} + +/* โ•โ• REACTION โ•โ• */ +function launchReaction(wrap,W,H){ + const canvas=document.createElement('canvas'); canvas.width=W; canvas.height=H; wrap.appendChild(canvas); + const ctx=canvas.getContext('2d'); + let phase='wait',waitEnd=0,reactionStart=0,times=[],best=Infinity; + let raf,stopped=false; + function reset(){phase='wait';waitEnd=performance.now()+(2+Math.random()*3)*1000;}reset(); + const react=()=>{ + if(phase==='ready'){const t=performance.now()-reactionStart;times.push(t);best=Math.min(best,t);if(times.length>=3){setTimeout(()=>finishMinigame(best<600),800);}else reset();} + else if(phase==='wait'){phase='toosoon';setTimeout(reset,1000);} + }; + const onKey=e=>{if(e.code==='Space')react();}; + document.addEventListener('keydown',onKey); canvas.addEventListener('click',react); + function loop(ts){ + if(stopped)return; raf=requestAnimationFrame(loop); + if(phase==='wait'&&ts>waitEnd){phase='ready';reactionStart=performance.now();} + ctx.fillStyle=phase==='ready'?'#16a34a':phase==='toosoon'?'#7f1d1d':'#050508'; + ctx.fillRect(0,0,W,H); + ctx.textAlign='center'; + if(phase==='wait'){ctx.fillStyle='#a7a3c2';ctx.font='16px Nunito,sans-serif';ctx.fillText('Warte...',W/2,H/2-20);ctx.fillText(`${times.length}/3 Runden`,W/2,H/2+10);} + if(phase==='ready'){ctx.fillStyle='#fff';ctx.font='bold 36px Fredoka One,cursive';ctx.fillText('JETZT!',W/2,H/2);ctx.font='14px Nunito,sans-serif';ctx.fillText('Klick oder Leertaste!',W/2,H/2+36);} + if(phase==='toosoon'){ctx.fillStyle='#fca5a5';ctx.font='bold 24px Fredoka One,cursive';ctx.fillText('Zu frรผh! ๐Ÿ˜…',W/2,H/2);} + if(times.length>0){const last=times[times.length-1];ctx.fillStyle='rgba(255,255,255,0.6)';ctx.font='13px Nunito,sans-serif';ctx.fillText(`Letzte: ${Math.round(last)}ms Beste: ${Math.round(best)}ms`,W/2,H-16);} + ctx.textAlign='left'; + } + raf=requestAnimationFrame(loop); + return{stop:()=>{stopped=true;cancelAnimationFrame(raf);document.removeEventListener('keydown',onKey);canvas.removeEventListener('click',react);}}; +} /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• WIN / GAME OVER โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ function showWin(){ @@ -1088,19 +1293,5 @@ function showGameOver(){ function showMenu(){ showToast('โ˜ฐ Menรผ kommt in der finalen Version!'); } function backToEditor(){ window.close(); } - - - - - - - - - - - - - -