- board.js: fix bug waarbij de opts-parameter (readonly e.d.) nooit bij mountMath aankwam, doordat de registry-wrapper hem liet vallen - math.js/letters.js/ball.js: nieuwe opts.onProgress-callback, aangeroepen bij resp. einde rekenronde, voltooid woord en geknapte ballon - pupil.js: geeft onProgress mee bij het mounten en post het naar /my/progress; alleen actief in leerling-speelmodus, niet bij bewerken
111 lines
4.1 KiB
JavaScript
111 lines
4.1 KiB
JavaScript
/* widget: Ballonnenwoord */
|
|
/* =========================================================
|
|
Balloon word widget — guess before the balloons pop
|
|
==========================================================*/
|
|
function mountBall(root, initState, opts){
|
|
const onProgress = opts && opts.onProgress;
|
|
const S = { card:0, snd:true, lives:6, guessed:new Set(), over:false, order:[], idx:0, stars:0 };
|
|
if(initState){ S.card = initState.card ?? 0; S.snd = initState.snd !== false; }
|
|
const play = f=>{ if(S.snd) f(); };
|
|
|
|
root.innerHTML = `
|
|
<div class="mt">
|
|
<div class="mt-top">
|
|
<button class="lg-snd"></button>
|
|
<select class="fl-sel bl-src"></select>
|
|
<div class="mt-score bl-score"></div>
|
|
</div>
|
|
<div class="mt-card">
|
|
<div class="bl-balloons"></div>
|
|
<div class="bl-word"></div>
|
|
<div class="bl-msg"></div>
|
|
<button class="lg-next bl-next"></button>
|
|
</div>
|
|
<div class="lg-tray bl-kbd lg-kbd" style="display:flex;"></div>
|
|
</div>`;
|
|
const el = s => root.querySelector(s);
|
|
const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"),
|
|
wordEl = el(".bl-word"), msgEl = el(".bl-msg"),
|
|
nextBtn = el(".bl-next"), kbdEl = el(".bl-kbd"),
|
|
scoreEl = el(".bl-score"), sndBtn = el(".lg-snd");
|
|
|
|
function list(){ S.card = normWordSource(S.card); return wordSourceList(S.card); }
|
|
function buildSrc(){ fillWordSourceSel(srcSel, S.card); }
|
|
"abcdefghijklmnopqrstuvwxyz".split("").forEach(l=>{
|
|
const b = document.createElement("button");
|
|
b.textContent = l; b.dataset.l = l;
|
|
b.className = VOWELS.includes(l) ? "v" : "c";
|
|
b.addEventListener("click", ()=>guess(l, b));
|
|
kbdEl.appendChild(b);
|
|
});
|
|
function word(){ return list()[S.order[S.idx]][0]; }
|
|
function drawWord(reveal){
|
|
wordEl.textContent = word().split("").map(l=>(reveal || S.guessed.has(l)) ? l : "_").join(" ");
|
|
}
|
|
function drawBalloons(){
|
|
balloonsEl.textContent = "🎈".repeat(S.lives) + "💥".repeat(6-S.lives);
|
|
}
|
|
function newWord(){
|
|
if(!S.order.length || S.idx >= S.order.length){
|
|
S.order = shuffle(list().map((_,i)=>i));
|
|
S.idx = 0;
|
|
}
|
|
S.lives = 6; S.guessed = new Set(); S.over = false;
|
|
msgEl.textContent = ""; msgEl.className = "bl-msg";
|
|
nextBtn.classList.remove("show");
|
|
kbdEl.querySelectorAll("button").forEach(b=>{ b.disabled = false; b.style.opacity = 1; });
|
|
drawWord(false); drawBalloons();
|
|
scoreEl.textContent = S.stars ? S.stars+" ★" : "";
|
|
}
|
|
function guess(l, btn){
|
|
if(S.over || btn.disabled) return;
|
|
btn.disabled = true; btn.style.opacity = .3;
|
|
if(word().includes(l)){
|
|
S.guessed.add(l);
|
|
play(sndGood);
|
|
drawWord(false);
|
|
if(word().split("").every(x=>S.guessed.has(x))){
|
|
S.over = true; S.stars++;
|
|
msgEl.textContent = T("ballWin");
|
|
msgEl.classList.add("win");
|
|
play(sndWin); confetti(el(".mt-card"));
|
|
nextBtn.classList.add("show");
|
|
scoreEl.textContent = S.stars+" ★";
|
|
if(onProgress) onProgress({ attempts: 1, correct: 1, stars: 1 });
|
|
}
|
|
}else{
|
|
S.lives--;
|
|
play(sndBad);
|
|
drawBalloons();
|
|
if(S.lives <= 0){
|
|
S.over = true;
|
|
drawWord(true);
|
|
msgEl.textContent = `${T("ballLose")} ${word()}`;
|
|
msgEl.classList.add("lose");
|
|
nextBtn.classList.add("show");
|
|
}
|
|
}
|
|
}
|
|
nextBtn.addEventListener("click", ()=>{ S.idx++; newWord(); });
|
|
srcSel.addEventListener("change", ()=>{
|
|
S.card = srcSel.value;
|
|
S.order = []; newWord();
|
|
});
|
|
sndBtn.addEventListener("click", ()=>{
|
|
S.snd = !S.snd;
|
|
sndBtn.textContent = S.snd ? "🔔" : "🔕";
|
|
sndBtn.classList.toggle("off", !S.snd);
|
|
});
|
|
function labels(){
|
|
sndBtn.textContent = S.snd ? "🔔" : "🔕";
|
|
sndBtn.classList.toggle("off", !S.snd);
|
|
sndBtn.title = T("sndWidget");
|
|
nextBtn.textContent = T("next");
|
|
buildSrc();
|
|
}
|
|
document.addEventListener("langchange", ()=>{ S.card=0; S.order=[]; labels(); newWord(); });
|
|
document.addEventListener("wordschange", ()=>{ buildSrc(); S.order=[]; newWord(); });
|
|
labels(); newWord();
|
|
return { getState: ()=>({card:S.card, snd:S.snd}) };
|
|
}
|
|
|