fix: remove duplicate codegen.js inline block, add missing unlockSection
- codegen.js war doppelt: einmal im Haupt-Script (korrekt) und einmal nochmals inline eingebettet → 'const unlocked already declared' Fehler - Doppelten Inline-Block entfernt - unlockSection() fehlte (beim Refactoring verloren gegangen): als Wrapper für setCodeFocus/unlocked.add wiederhergestellt - s3init() läuft jetzt korrekt: Board + Palette erscheinen sofort
This commit is contained in:
parent
99852fc0f5
commit
da4f6dc102
1 changed files with 6 additions and 314 deletions
320
editor.html
320
editor.html
|
|
@ -1078,6 +1078,12 @@ function s2update(){
|
|||
let selGame=null,dragging=null,epFor=null;
|
||||
const STORY_MAX=5,MULTI_MAX=3;
|
||||
|
||||
// unlockSection: Markiert eine Sektion als "gesehen" für den Live-Code-Preview
|
||||
function unlockSection(key){
|
||||
if(typeof unlocked !== 'undefined') unlocked.add(key);
|
||||
if(typeof setCodeFocus === 'function') setCodeFocus(key);
|
||||
}
|
||||
|
||||
function s3init(){
|
||||
unlockSection('movement'); unlockSection('failmode'); unlockSection('fieldcount'); unlockSection('fields');
|
||||
document.getElementById('cntVal').textContent=ST.fieldCount;
|
||||
|
|
@ -2574,320 +2580,6 @@ setTimeout(updateCodePane, 200);
|
|||
<!-- ═══ Module (inline) ═══ -->
|
||||
<script>
|
||||
|
||||
/* ═══ codegen.js ═══ */
|
||||
/**
|
||||
* codegen.js
|
||||
* Python Live-Code-Generator für den Split-Screen
|
||||
* Wird von editor.html geladen
|
||||
*/
|
||||
|
||||
// ── 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;i<s.fieldCount-1;i++){
|
||||
const fId=(s.fields||[])[i];
|
||||
lines.push({t:'bl'});
|
||||
if(fId){
|
||||
lines.push({t:'ind-cm', v:`# Feld ${i}: Mini-Game → ${MG_CM[fId]||fId}`});
|
||||
lines.push({t:'ind', v:`felder.append(Field(index=${i}, typ="minigame", spiel="${MG_PY[fId]||fId}"))`,
|
||||
hi: hi('fields') && (s.fields||[])[i]===fId});
|
||||
} else {
|
||||
lines.push({t:'ind-cm', v:`# Feld ${i}: kein Spiel, einfach durchlaufen`});
|
||||
lines.push({t:'ind', v:`felder.append(Field(index=${i}, typ="leer"))`});
|
||||
}
|
||||
}
|
||||
lines.push({t:'bl'});
|
||||
lines.push({t:'ind-cm', v:`# Zielfeld (Index ${s.fieldCount-1}) 🏁`});
|
||||
lines.push({t:'ind', v:`felder.append(Field(index=${s.fieldCount-1}, typ="ziel"))`});
|
||||
lines.push({t:'bl'});
|
||||
lines.push({t:'ind', v:'return felder'});
|
||||
lines.push({t:'bl'});
|
||||
}
|
||||
|
||||
// MINIGAME FUNCTIONS
|
||||
const usedGames=[...new Set((s.fields||[]).filter(Boolean))];
|
||||
if(usedGames.length>0){
|
||||
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,'<').replace(/>/g,'>');
|
||||
let r='';
|
||||
switch(line.t){
|
||||
case 'cm': r=`<span class="py-cm">${esc(line.v)}</span>`; break;
|
||||
case 'bl': return '';
|
||||
case 'ph': r=`<span class="py-var">${esc(line.var)}</span><span class="py-punc"> = </span><span class="py-cm">"???" <span style="color:#e06c75;font-style:italic">${esc(line.hint||'')}</span></span>`; break;
|
||||
case 'kw': r=`<span class="py-kw">${esc(line.v)}</span><span class="py-val">${esc(line.rest||'')}</span>`; break;
|
||||
case 'ass': r=`<span class="py-var">${esc(line.var)}</span><span class="py-punc"> = </span><span class="py-str">${esc(String(line.val))}</span>`; break;
|
||||
case 'ass-end':r=`<span class="py-punc">${esc(line.v)}</span>`; break;
|
||||
case 'fn-def': r=`<span class="py-kw">def </span><span class="py-fn">${esc(line.name)}</span><span class="py-punc">(${esc(line.args||'')})</span><span class="py-punc">:</span>`; break;
|
||||
case 'ind': r=` <span class="py-val">${esc(line.v)}</span>`; break;
|
||||
case 'ind2': r=` <span class="py-val">${esc(line.v)}</span>`; break;
|
||||
case 'ind-cm': r=` <span class="py-cm">${esc(line.v)}</span>`; break;
|
||||
default: return esc(line.v||'');
|
||||
}
|
||||
return line.hi ? `<span class="py-hi">${r}</span>` : 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='<span class="py-cm"># Klicke ein Feld an — dein Code erscheint hier ✨</span>';
|
||||
numsEl.innerHTML='<div>1</div>';
|
||||
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=>`<div>${n}</div>`).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);
|
||||
|
||||
|
||||
/* ═══ _api.js ═══ */
|
||||
/**
|
||||
* minigames/_api.js
|
||||
|
|
|
|||
Loading…
Reference in a new issue