edu-boardgame-generator/js/wizard.js

340 lines
15 KiB
JavaScript

/* wizard.js — Schritt-Navigation + Screens 1-3 (Grundinfos, Gestaltung, Regeln) */
/* ══════════════════════════════════════════════
NAVIGATION
══════════════════════════════════════════════ */
function goTo(step){
if(step>cur&&!canLeave(cur))return;
save();
const from=document.getElementById('s'+cur);
from.classList.remove('active');
from.style.display='none';
setTimeout(()=>{from.style.display='';},50);
if(step>cur)ST.highestStep=Math.max(ST.highestStep,step);
cur=step;
const to=document.getElementById('s'+cur);
to.style.display='';
to.classList.add('active','slide-in');
to.addEventListener('animationend',()=>to.classList.remove('slide-in'),{once:true});
const ep=document.getElementById('editorPane');if(ep)ep.scrollTo({top:0,behavior:'smooth'});else window.scrollTo({top:0,behavior:'smooth'});
updatePB();
showSummary(step);
if(step===1)s2init();
if(step===2){ s3init(); if(typeof maybeStartTour==='function') maybeStartTour(); }
if(step===3)buildQuiz();
if(step===4)buildReview();
showToast('✅ Schritt '+(step+1)+' geöffnet!');
}
function pbClick(step){if(step<cur&&step<=ST.highestStep){goTo(step);}}
function canLeave(step){
if(step===0)return ST.name.trim().length>0;
if(step===1)return !!(ST.figure&&ST.background) && (ST.playerCount!==2 || (ST.figure2 && ST.figure2!==ST.figure));
if(step===2)return ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length>0;
return true;
}
function updatePB(){
for(let i=0;i<5;i++){
const d=document.getElementById('pb'+i);
d.className='pb-step'+(i===cur?' active':i<cur?' done':'');
const c=document.getElementById('pbc'+i);
if(c)c.className='pb-connector'+(i<cur?' done':'');
}
document.getElementById('pbFill').style.width=((cur+1)/5*100)+'%';
}
function showSummary(step){
const idx=step-1;
if(idx<0)return;
const banner=document.getElementById('sum'+idx);
if(!banner)return;
banner.classList.add('visible');
const chips=document.getElementById('sum'+idx+'chips');
if(!chips)return;
let html='';
if(idx===0){if(ST.devName)html+=`<div class="sb-chip">👋 ${esc(ST.devName)}</div>`;html+=`<div class="sb-chip">🎮 ${esc(ST.name)}</div>`;if(ST.desc)html+=`<div class="sb-chip">"${esc(ST.desc.slice(0,40))}${ST.desc.length>40?'…':''}"</div>`;}
if(idx===1){const f=FIGURES.find(x=>x.id===ST.figure);const b=BACKGROUNDS.find(x=>x.id===ST.background);if(f)html+=`<div class="sb-chip">${f.e} ${f.n}</div>`;if(b)html+=`<div class="sb-chip">🌍 ${b.n}</div>`;}
if(idx===2){const filled=ST.fields.slice(1,ST.fieldCount-1).filter(Boolean).length;html+=`<div class="sb-chip">⬛ ${ST.fieldCount} Felder</div><div class="sb-chip">🕹️ ${filled} Mini-Games</div><div class="sb-chip">📖 ${ST.storyItems.length} Erzähltexte</div>`;}
if(idx===3){const done=ST.quizData.filter(isQComplete).length;html+=`<div class="sb-chip">❓ ${done}/${ST.quizData.length||0} Quiz-Fragen</div>`;}
chips.innerHTML=html;
}
function showToast(msg){
const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
setTimeout(()=>t.classList.remove('show'),2000);
}
/* ══════════════════════════════════════════════
STEP 1 — GRUNDINFOS
══════════════════════════════════════════════ */
function s1init(){
document.getElementById('s1devname').value=ST.devName;
document.getElementById('s1name').value=ST.name;
document.getElementById('s1desc').value=ST.desc;
s1update();
}
function s1update(){
ST.devName=document.getElementById('s1devname').value.trim();
ST.name=document.getElementById('s1name').value.trim();
ST.desc=document.getElementById('s1desc').value;
const nl=document.getElementById('s1name').value.length;
const dl=ST.desc.length;
document.getElementById('s1nc').textContent=nl+' / 40';
document.getElementById('s1dc').textContent=dl+' / 120';
document.getElementById('s1dc').className='char-row'+(dl>100?' warn':'');
const s1dis=!ST.name||!ST.devName;
document.getElementById('s1next').disabled=s1dis;
const s1hint=document.getElementById('s1hint');if(s1hint)s1hint.style.display=s1dis?'block':'none';
// Persönliche Begrüßung
const grEl=document.getElementById('devGreeting');
if(ST.devName){
const greetings=[
'Hey '+ST.devName+' 👋 Schön, dass du dabei bist!',
'Los geht\'s, '+ST.devName+'! Dein Spiel wartet 🎮',
'Hi '+ST.devName+'! Du wirst ein tolles Spiel bauen ✨',
'Willkommen, '+ST.devName+'! Game on! 🚀',
];
grEl.textContent=greetings[ST.devName.length%greetings.length];
grEl.style.opacity='1';
} else {grEl.style.opacity='0';}
// Vorschau
const show=ST.name.length>0&&ST.devName.length>0;
document.getElementById('s1preview').style.display=show?'':'none';
if(show){
document.getElementById('s1pvname').textContent=ST.name;
document.getElementById('s1pvdesc').textContent=ST.desc||'Keine Beschreibung.';
document.getElementById('s1pvdev').textContent='👨‍💻 Entwickelt von '+ST.devName;
}
}
function suggest(txt){document.getElementById('s1name').value=txt;s1update();}
/* ══════════════════════════════════════════════
STEP 2 — GESTALTUNG
══════════════════════════════════════════════ */
function buildFigureGrid(gridId, current, blocked, onPick){
const g=document.getElementById(gridId); if(!g) return;
g.innerHTML='';
FIGURES.forEach(f=>{
const isBlocked = blocked && blocked===f.id;
const c=document.createElement('div');
c.className='sel-card'+(current===f.id?' selected':'')+(isBlocked?' disabled':'');
c.innerHTML=`<span class="se">${f.e}</span><div class="sl">${f.n}</div>`;
if(isBlocked){ c.title='Bereits vom anderen Spieler gewählt'; }
else { c.onclick=()=>onPick(f.id); }
g.appendChild(c);
});
}
function setPlayerCount(n){
ST.playerCount = n;
if(n===1){ ST.figure2=''; }
setCodeFocus('figure');
save();
s2update();
}
function s2init(){
buildFigureGrid('figGrid', ST.figure, ST.figure2, id => {
ST.figure=id; setCodeFocus('figure');
if(ST.figure2===id) ST.figure2=''; // Konflikt löst sich automatisch
s2update();
});
const bg=document.getElementById('bgGrid');
bg.innerHTML='';
BACKGROUNDS.forEach(b=>{
const c=document.createElement('div');c.className='sel-card'+(ST.background===b.id?' selected':'');
c.innerHTML=`<span class="se">${b.e}</span><div class="sl">${b.n}</div>`;
c.onclick=()=>{ST.background=b.id;setCodeFocus('bg');s2update();bg.querySelectorAll('.sel-card').forEach(x=>x.classList.remove('selected'));c.classList.add('selected');};
bg.appendChild(c);
});
s2update();
}
function s2update(){
// Spielmodus-Karten markieren
document.getElementById('pmode1')?.classList.toggle('active', ST.playerCount!==2);
document.getElementById('pmode2')?.classList.toggle('active', ST.playerCount===2);
// Figur-Karten-Titel anpassen
const t = document.getElementById('figCardTitle');
const s = document.getElementById('figCardSub');
if(t){ t.textContent = ST.playerCount===2 ? '👤 Spielfigur Spieler 1' : '👤 Spielfigur'; }
if(s){ s.textContent = ST.playerCount===2 ? 'Wer bist du im Spiel?' : 'Das bist du im Spiel!'; }
// P2-Karte ein-/ausblenden + Grid neu bauen (für blocked-State)
const fig2Card = document.getElementById('fig2Card');
if(fig2Card){
fig2Card.style.display = ST.playerCount===2 ? '' : 'none';
if(ST.playerCount===2){
buildFigureGrid('fig2Grid', ST.figure2, ST.figure, id => {
ST.figure2=id; setCodeFocus('figure'); s2update();
});
}
}
// P1-Grid neu bauen (damit Block-State der gegenüberliegenden Figur stimmt)
buildFigureGrid('figGrid', ST.figure, ST.playerCount===2 ? ST.figure2 : '', id => {
ST.figure=id; setCodeFocus('figure');
if(ST.figure2===id) ST.figure2='';
s2update();
});
// Combo-Preview
const ok = !!(ST.figure&&ST.background) && (ST.playerCount!==2 || (ST.figure2 && ST.figure2!==ST.figure));
document.getElementById('s2next').disabled=!ok;
const combo=document.getElementById('s2combo');
if(ST.figure && ST.background){
const f=FIGURES.find(x=>x.id===ST.figure);const b=BACKGROUNDS.find(x=>x.id===ST.background);
combo.style.display='';
const f2 = ST.playerCount===2 && ST.figure2 ? FIGURES.find(x=>x.id===ST.figure2) : null;
document.getElementById('s2scene').textContent = b.scene + ' ' + f.e + (f2?' vs '+f2.e:'');
document.getElementById('s2clabel').textContent = f.n + (f2?' & '+f2.n:'') + ' im '+ b.n;
} else { combo.style.display='none'; }
}
/* ══════════════════════════════════════════════
STEP 3 — SPIELFELD
══════════════════════════════════════════════ */
let selGame=null,dragging=null,epFor=null,dragFromIdx=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'); unlockSection('consequences');
document.getElementById('cntVal').textContent=ST.fieldCount;
document.getElementById('cntMinus').disabled=ST.fieldCount<=6;
document.getElementById('cntPlus').disabled=ST.fieldCount>=50;
applyRuleUI();
buildPalette();
buildBoard();
updateConsequenceUI();
}
function selRule(el){
const g=el.dataset.g;
document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');ST.rules[g]=el.dataset.v;
// Map data-g to focus keys
const fkMap={'movement':'movement','fail':'failmode'};
setCodeFocus(fkMap[g]||g);
if(g==='fail'){
const sl = document.getElementById('sub_lives');
const sp = document.getElementById('sub_points');
if(ST.rules.fail==='lives'){
sl.classList.add('visible'); sl.style.display='flex';
sp.classList.remove('visible'); sp.style.display='';
} else {
sl.classList.remove('visible'); sl.style.display='';
sp.classList.add('visible'); sp.style.display='';
}
}
updateConsequenceUI();
}
function selSub(el){
const g=el.dataset.g;
document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');ST.rules[g]=el.dataset.v;
// lives/pts sub-options
setCodeFocus(g==='lives'?'lives':g==='pts'?'pts':'failmode');
}
// ── Konsequenz-Funktionen ──────────────────────────────────────────────────
function chgLives(delta){
const cur = parseInt(ST.rules.lives) || 3;
const next = Math.min(5, Math.max(1, cur + delta));
ST.rules.lives = String(next);
document.getElementById('livesDisplay').textContent = next;
document.getElementById('livesHearts').textContent = '❤️'.repeat(next);
setCodeFocus('lives');
save();
}
function selCQ(el,e){
e&&e.stopPropagation&&e.stopPropagation();
const side = el.dataset.cq;
const val = el.dataset.cv;
document.querySelectorAll(`[data-cq="${side}"]`)
.forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');
ST.consequences[side] = val;
setCodeFocus('consequences');
save();
}
function chgCQVal(key, delta, e){
e && e.stopPropagation();
const mins = {winVal:0,loseVal:0,winPts:0,losePts:0};
const maxs = {winVal:6,loseVal:6,winPts:100,losePts:100};
if(!ST.consequences[key]) ST.consequences[key] = key.includes('Pts') ? 10 : 1;
let v = (ST.consequences[key]||0) + delta;
v = Math.max(mins[key]||0, Math.min(maxs[key]||99, v));
ST.consequences[key] = v;
const elId = {winVal:'cqWinVal',loseVal:'cqLoseVal',winPts:'cqWinPts',losePts:'cqLosePts'}[key];
if(elId) document.getElementById(elId).textContent = v;
setCodeFocus('consequences');
save();
}
function updateConsequenceUI(){
const grid = document.getElementById('consequenceGrid');
if(!grid) return;
const isDice = ST.rules.movement === 'dice';
const isLives = ST.rules.fail === 'lives';
const isPts = ST.rules.fail === 'points';
// CSS-Klassen für sichtbare Optionen
// Alle Modus-Klassen zuerst entfernen
grid.classList.remove('cq-mode-step','cq-mode-lives','cq-mode-pts');
// Dann die passenden setzen
if(!isDice) grid.classList.add('cq-mode-step');
if(isLives) grid.classList.add('cq-mode-lives');
if(isPts) grid.classList.add('cq-mode-pts');
// Falls aktuelle Auswahl nicht mehr sichtbar → auf 'nothing' zurücksetzen
['win','lose'].forEach(side=>{
const cur = ST.consequences[side];
const diceOnly = ['forward','back','again','skip'];
const livesOnly = ['life'];
const ptsOnly = ['points'];
let invalid = false;
if(diceOnly.includes(cur) && !isDice) invalid = true;
if(livesOnly.includes(cur) && !isLives) invalid = true;
if(ptsOnly.includes(cur) && !isPts) invalid = true;
if(invalid){
ST.consequences[side] = 'nothing';
}
// UI aktualisieren
document.querySelectorAll(`[data-cq="${side}"]`)
.forEach(x=>x.classList.toggle('selected', x.dataset.cv === ST.consequences[side]));
});
// Werte anzeigen
const c = ST.consequences;
const wv = document.getElementById('cqWinVal');
const lv = document.getElementById('cqLoseVal');
const wp = document.getElementById('cqWinPts');
const lp = document.getElementById('cqLosePts');
if(wv) wv.textContent = c.winVal ?? 2;
if(lv) lv.textContent = c.loseVal ?? 1;
const ld = document.getElementById('livesDisplay');
const lh = document.getElementById('livesHearts');
const ln = parseInt(ST.rules.lives) || 3;
if(ld) ld.textContent = ln;
if(lh) lh.textContent = '❤️'.repeat(ln);
if(wp) wp.textContent = c.winPts ?? 10;
if(lp) lp.textContent = c.losePts ?? 5;
}
function applyRuleUI(){
['movement','fail'].forEach(g=>document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.toggle('selected',x.dataset.v===ST.rules[g])));
document.getElementById('sub_lives').classList.toggle('visible',ST.rules.fail==='lives');
document.getElementById('sub_points').classList.toggle('visible',ST.rules.fail==='points');
['lives','pts'].forEach(g=>document.querySelectorAll(`[data-g="${g}"]`).forEach(x=>x.classList.toggle('selected',x.dataset.v===ST.rules[g])));
updateConsequenceUI();
}
function chgCount(d){
const n=ST.fieldCount+d;if(n<6||n>50)return;
ST.fieldCount=n;setCodeFocus('fieldcount');document.getElementById('cntVal').textContent=n;
document.getElementById('cntMinus').disabled=n<=6;document.getElementById('cntPlus').disabled=n>=50;
while(ST.fields.length<n)ST.fields.push(null);
ST.fields=ST.fields.slice(0,n);
ST.storyItems=ST.storyItems.filter(s=>s.fieldIndex<n);
buildBoard();
}