DrAIStudio Mascot DrAIStudio Interactive Activities β–Έ Scope Shooter Studio

Interactive Game Content

Edit generated scenarios, answers, correct sequences, and feedback.

Procedural AI Structuring...

Drafting curriculum-grounded options and SCORM score maps.

Analyzing curriculum domain & case goals
Generating scenario descriptions
Structuring decision paths & trap options
Writing explanation feedbacks
Injecting SCORM 1.2 tracking bridge
`; } engineLayout = `
Round 1/6
Time 60s
Score 0
Loading mission…
`; engineCss = ` :root{--bg:${colors.bg};--panel:${colors.panel};--border:${colors.border};--text:${colors.text};--highlight:${colors.highlight};--timer:${colors.timer};} body{background:var(--bg);color:var(--text);font-family:monospace;padding:0;margin:0;overflow:hidden;user-select:none;} body.shake { animation: shake 0.2s; } .hud-top{position:fixed;top:10px;left:10px;right:10px;display:flex;justify-content:space-between;z-index:10;} .badge{background:var(--panel);border:1px solid var(--border);padding:6px 12px;border-radius:2px;font-size:11px;} .badge b{color:var(--highlight);} .scenario{position:fixed;top:54px;left:50%;transform:translateX(-50%);max-width:90%;background:rgba(0,0,0,0.8);border:1px solid var(--border);border-radius:4px;padding:10px 16px;z-index:8;text-align:center;} .prompt{font-size:12px;font-weight:700;} #arena{position:fixed;inset:0;overflow:hidden;} .box{position:absolute;background:rgba(0,40,55,0.7);border:1px solid var(--highlight);padding:10px 16px;font-size:12px;font-weight:700;max-width:200px;text-align:center;cursor:crosshair;box-shadow:0 0 10px rgba(0,240,255,0.2);animation:flicker 3s infinite;} @keyframes flicker { 0%,100%{opacity:1;} 50%{opacity:0.95;} } .box.hit-good{animation:explodeGood 0.5s forwards; pointer-events:none;} .box.hit-bad{animation:explodeBad 0.5s forwards; pointer-events:none;} .modal{position:fixed;inset:0;background:rgba(0,0,0,0.85);display:flex;align-items:center;justify-content:center;padding:14px;z-index:100;backdrop-filter:blur(4px);} .modal.hidden{display:none;} .card{background:var(--panel);border:2px solid var(--border);border-radius:4px;max-width:480px;width:100%;padding:24px;text-align:center;box-shadow: 0 15px 35px rgba(0,0,0,0.4);} .card h2{color:var(--highlight);margin-bottom:8px;text-transform:uppercase;} .card .s{font-size:11px;color:var(--border);text-transform:uppercase;margin-bottom:14px;font-weight:700;} .rules{text-align:left;background:rgba(0,0,0,0.2);padding:12px 16px;border-radius:4px;font-size:13px;margin-bottom:16px;} .reveal{text-align:left;background:rgba(0,0,0,0.3);padding:12px;max-height:220px;overflow-y:auto;font-size:12px;margin-bottom:14px;border:1px solid var(--border);} .btn{cursor:pointer;background:var(--highlight);border:none;color:white;font-weight:700;padding:10px 20px;border-radius:4px;font-size:12px;text-transform:uppercase;transition:all 0.15s;} .btn:hover{filter:brightness(1.1);transform:translateY(-1px);} .stats{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:16px;} .stat{background:rgba(0,0,0,0.15);border:1px solid var(--border);border-radius:4px;padding:10px;} .stat .v{font-size:20px;font-weight:900;color:var(--highlight);} .stat .l{font-size:9px;color:var(--border);text-transform:uppercase;margin-top:2px;} @keyframes explodeGood { 0% { transform: scale(1); filter: brightness(2); background: #10b981; } 100% { transform: scale(0.1) rotate(15deg); opacity: 0; filter: blur(5px); } } @keyframes explodeBad { 0% { transform: scale(1); filter: brightness(2); background: #ef4444; } 100% { transform: scale(0.1) rotate(-15deg); opacity: 0; filter: blur(5px); } } @keyframes shake { 0%, 100% { transform: translate(0, 0); } 25% { transform: translate(-4px, 4px); } 50% { transform: translate(4px, -4px); } 75% { transform: translate(-2px, -2px); } } `; engineScript = ` const ROUNDS = ${dataStr}; const state = { round:0, score:0, misses:0, correctHits:0, wrongHits:0, timeLeft:60, t:null, active:false, boxes:[] }; document.getElementById('hudRoundsTotal').textContent = ROUNDS.length; document.getElementById('start').onclick = start; document.getElementById('again').onclick = start; document.getElementById('nextRoundBtn').onclick = nextRound; // Synthesizer Audio let audioCtx = null; function playTone(freq, duration, type = 'sine') { try { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.08, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration); osc.start(); osc.stop(audioCtx.currentTime + duration); } catch(e) {} } const sound = { laser: () => { try { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(1200, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(150, audioCtx.currentTime + 0.15); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.06, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.15); osc.start(); osc.stop(audioCtx.currentTime + 0.15); } catch(e) {} }, hitGood: () => playTone(880, 0.12, 'sine'), hitBad: () => playTone(200, 0.25, 'sawtooth'), click: () => playTone(600, 0.08, 'triangle'), }; // Sanitizer for safe formatting tags (b, i, u, strong, em, br) function sanitize(html) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; let escaped = String(html).replace(/[&<>"']/g, c => map[c]); escaped = escaped.replace(/<b>/g, '').replace(/<\/b>/g, ''); escaped = escaped.replace(/<strong>/g, '').replace(/<\/strong>/g, ''); escaped = escaped.replace(/<i>/g, '').replace(/<\/i>/g, ''); escaped = escaped.replace(/<em>/g, '').replace(/<\/em>/g, ''); escaped = escaped.replace(/<br\s*\/?>/gi, '
'); return escaped; } // SCORM API Helper let scormAPI = null; let isSCORM2004 = false; function findAPI(win) { let limit = 0; while (win) { if (win.API_1484_11 != null) { isSCORM2004 = true; return win.API_1484_11; } if (win.API != null) { isSCORM2004 = false; return win.API; } if (win.parent == win || win.parent == null) break; limit++; if (limit > 10) break; win = win.parent; } return null; } try { scormAPI = findAPI(window) || (window.opener ? findAPI(window.opener) : null); if (scormAPI) { if (isSCORM2004) { scormAPI.Initialize(""); scormAPI.SetValue("cmi.completion_status", "incomplete"); scormAPI.Commit(""); } else { scormAPI.LMSInitialize(""); scormAPI.LMSSetValue("cmi.core.lesson_status", "incomplete"); scormAPI.LMSCommit(""); } } } catch(e) {} function start() { sound.click(); document.getElementById('intro').classList.add('hidden'); document.getElementById('over').classList.add('hidden'); state.round = 0; state.score = 0; state.misses = 0; state.correctHits = 0; state.wrongHits = 0; loadRound(); } function loadRound() { if (state.round >= ROUNDS.length) return end(); document.getElementById('hudRound').textContent = state.round + 1; document.getElementById('hudScore').textContent = state.score; document.getElementById('hudTime').textContent = 60; const r = ROUNDS[state.round]; document.getElementById('prompt').textContent = r.prompt; const arena = document.getElementById('arena'); arena.innerHTML = ''; state.boxes = []; state.timeLeft = 60; state.active = true; r.options.forEach((opt, idx) => { const el = document.createElement('div'); el.className = 'box'; el.textContent = opt.t; // Position randomly el.style.left = (10 + Math.random()*65) + 'vw'; el.style.top = (100 + Math.random()*300) + 'px'; const vx = -1.2 + Math.random()*2.4; const vy = -0.8 - Math.random()*1.2; el.onclick = () => shoot(opt, el); arena.appendChild(el); state.boxes.push({ el, opt, x: parseFloat(el.style.left), y: parseFloat(el.style.top), vx, vy, hit:false }); }); clearInterval(state.t); state.t = setInterval(() => { state.timeLeft--; document.getElementById('hudTime').textContent = state.timeLeft; if (state.timeLeft <= 0) { clearInterval(state.t); showRoundReport(); } }, 1000); requestAnimationFrame(updateFrame); } function updateFrame() { if (!state.active) return; state.boxes.forEach(b => { if (b.hit) return; // Update positions let left = parseFloat(b.el.style.left) || 50; let top = parseFloat(b.el.style.top) || 300; left += b.vx * 0.1; top += b.vy * 1.0; // Bounds bounce if (left < 5 || left > 85) b.vx *= -1; // Loop top if (top < 80) top = window.innerHeight + 10; b.el.style.left = left + 'vw'; b.el.style.top = top + 'px'; }); requestAnimationFrame(updateFrame); } function shoot(opt, el) { const b = state.boxes.find(x => x.el === el); if (!b || b.hit) return; b.hit = true; // Trigger laser and shake sound.laser(); document.body.classList.add('shake'); setTimeout(() => document.body.classList.remove('shake'), 200); if (opt.correct) { el.classList.add('hit-good'); sound.hitGood(); state.score += 200; state.correctHits++; } else { el.classList.add('hit-bad'); sound.hitBad(); state.score -= 150; state.wrongHits++; } document.getElementById('hudScore').textContent = state.score; // Check if all correct ones are shot const remainingCorrect = state.boxes.filter(x => x.opt.correct && !x.hit).length; if (remainingCorrect === 0) { clearInterval(state.t); setTimeout(showRoundReport, 600); } } function showRoundReport() { state.active = false; const reportModal = document.getElementById('roundReport'); const reveal = document.getElementById('revealLogs'); const r = ROUNDS[state.round]; let html = '

Round Summary:


'; r.options.forEach(opt => { const matchedBox = state.boxes.find(b => b.opt === opt); const color = opt.correct ? '#10b981' : '#ef4444'; const symbol = opt.correct ? 'βœ“' : 'βœ—'; html += '
' + symbol + ' ' + esc(opt.t) + '
' + esc(opt.why) + '
'; }); reveal.innerHTML = html; reportModal.classList.remove('hidden'); } function nextRound() { sound.click(); document.getElementById('roundReport').classList.add('hidden'); state.round++; loadRound(); } function end() { document.getElementById('over').classList.remove('hidden'); document.getElementById('finScore').textContent = state.score; const totalShots = state.correctHits + state.wrongHits; const acc = totalShots > 0 ? Math.round((state.correctHits / totalShots)*100) : 0; document.getElementById('finAcc').textContent = acc + '%'; try { if (scormAPI) { // Map score out of 100 (Max logic based on round count * 600) const scaledScore = Math.max(0, Math.min(100, Math.round((state.score / (ROUNDS.length * 400)) * 100))); if (isSCORM2004) { scormAPI.SetValue("cmi.score.raw", scaledScore.toString()); scormAPI.SetValue("cmi.completion_status", "completed"); scormAPI.SetValue("cmi.success_status", scaledScore >= 50 ? "passed" : "failed"); scormAPI.Commit(""); scormAPI.Terminate(""); } else { scormAPI.LMSSetValue("cmi.core.score.raw", scaledScore.toString()); scormAPI.LMSSetValue("cmi.core.lesson_status", scaledScore >= 50 ? "passed" : "failed"); scormAPI.LMSCommit(""); scormAPI.LMSFinish(""); } } } catch(e) {} } function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } `; } else if (activeGameType === 'swipe') { engineLayout = `

${title}

${course}
`; engineCss = ` :root{--bg:${colors.bg};--panel:${colors.panel};--border:${colors.border};--text:${colors.text};--highlight:${colors.highlight};} body{background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,sans-serif;padding:20px;user-select:none;} .wrap{max-width:500px;margin:0 auto;text-align:center;} header.h{margin-bottom:20px;} h1{font-size:22px;color:var(--highlight);margin-bottom:4px;} .sub{font-size:12px;color:var(--border);font-weight:700;} .card-area{display:flex;flex-direction:column;align-items:center;gap:20px;} .card-stack{width:100%;max-width:360px;height:320px;position:relative;} .swipe-card{background:var(--panel);border:2px solid var(--border);border-radius:8px;padding:24px;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;box-shadow:0 10px 20px rgba(0,0,0,0.15);position:absolute;transition:all 0.3s cubic-bezier(0.16, 1, 0.3, 1);} .swipe-card.approved{transform:translate(400px, -40px) rotate(20deg); opacity:0;} .swipe-card.rejected{transform:translate(-400px, -40px) rotate(-20deg); opacity:0;} .card-avatar{width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,0.06);display:grid;place-items:center;} .card-text{font-size:14px;font-weight:600;line-height:1.6;flex:1;display:flex;align-items:center;} .controls{display:flex;gap:14px;} .btn{cursor:pointer;border:none;padding:12px 24px;border-radius:4px;font-weight:700;color:white;text-transform:uppercase;font-size:12px;display:flex;align-items:center;gap:6px;} .btn.reject{background:#ef4444;} .btn.approve{background:#10b981;} .modal{position:fixed;inset:0;background:rgba(0,0,0,0.85);display:flex;align-items:center;justify-content:center;padding:14px;z-index:100;backdrop-filter:blur(6px);} .modal.hidden{display:none;} .card{background:var(--panel);border:2px solid var(--border);border-radius:8px;max-width:440px;width:100%;padding:24px;text-align:center;box-shadow: 0 15px 35px rgba(0,0,0,0.4);} .rules{text-align:left;background:rgba(0,0,0,0.2);padding:12px 16px;margin-bottom:16px;} .reveal{text-align:left;background:rgba(0,0,0,0.3);padding:12px;max-height:260px;overflow-y:auto;font-size:12px;margin-bottom:14px;border:1px solid var(--border);} `; engineScript = ` const SCENARIOS = ${dataStr}; let cards = []; let currentIdx = 0; let results = []; document.getElementById('start').onclick = start; document.getElementById('again').onclick = start; document.getElementById('rejectBtn').onclick = () => swipe(false); document.getElementById('approveBtn').onclick = () => swipe(true); // Synthesizer Audio let audioCtx = null; function playTone(freq, duration, type = 'sine') { try { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.08, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration); osc.start(); osc.stop(audioCtx.currentTime + duration); } catch(e) {} } const sound = { swipeRight: () => { playTone(660, 0.12, 'sine'); setTimeout(() => playTone(880, 0.15, 'sine'), 60); }, swipeLeft: () => { playTone(440, 0.12, 'sine'); setTimeout(() => playTone(330, 0.15, 'sine'), 60); }, click: () => playTone(600, 0.08, 'triangle'), }; // Sanitizer for safe formatting tags (b, i, u, strong, em, br) function sanitize(html) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; let escaped = String(html).replace(/[&<>"']/g, c => map[c]); escaped = escaped.replace(/<b>/g, '').replace(/<\/b>/g, ''); escaped = escaped.replace(/<strong>/g, '').replace(/<\/strong>/g, ''); escaped = escaped.replace(/<i>/g, '').replace(/<\/i>/g, ''); escaped = escaped.replace(/<em>/g, '').replace(/<\/em>/g, ''); escaped = escaped.replace(/<br\s*\/?>/gi, '
'); return escaped; } // SCORM API Helper let scormAPI = null; let isSCORM2004 = false; function findAPI(win) { let limit = 0; while (win) { if (win.API_1484_11 != null) { isSCORM2004 = true; return win.API_1484_11; } if (win.API != null) { isSCORM2004 = false; return win.API; } if (win.parent == win || win.parent == null) break; limit++; if (limit > 10) break; win = win.parent; } return null; } try { scormAPI = findAPI(window) || (window.opener ? findAPI(window.opener) : null); if (scormAPI) { if (isSCORM2004) { scormAPI.Initialize(""); scormAPI.SetValue("cmi.completion_status", "incomplete"); scormAPI.Commit(""); } else { scormAPI.LMSInitialize(""); scormAPI.LMSSetValue("cmi.core.lesson_status", "incomplete"); scormAPI.LMSCommit(""); } } } catch(e) {} function start() { sound.click(); document.getElementById('intro').classList.add('hidden'); document.getElementById('over').classList.add('hidden'); cards = []; SCENARIOS.forEach(s => { s.cards.forEach(c => cards.push(c)); }); // Shuffle cards.sort(() => Math.random() - 0.5); currentIdx = 0; results = []; loadCard(); } function loadCard() { if (currentIdx >= cards.length) return end(); const el = document.getElementById('swipeCard'); el.className = 'swipe-card'; el.innerHTML = '
' + esc(cards[currentIdx].text) + '
'; } function swipe(choice) { const el = document.getElementById('swipeCard'); const target = cards[currentIdx]; const isCorrect = (choice === target.approve); results.push({ card: target, choice, isCorrect }); if (choice) { sound.swipeRight(); el.classList.add('approved'); } else { sound.swipeLeft(); el.classList.add('rejected'); } setTimeout(() => { currentIdx++; loadCard(); }, 250); } function end() { document.getElementById('over').classList.remove('hidden'); const total = results.length; const correctCount = results.filter(r => r.isCorrect).length; const scorePct = total > 0 ? Math.round((correctCount / total) * 100) : 0; const reveal = document.getElementById('finalLogs'); let html = '

Classification Report ('+scorePct+'%):


'; results.forEach(r => { const color = r.isCorrect ? '#10b981' : '#ef4444'; const symbol = r.isCorrect ? 'βœ“' : 'βœ—'; const uChoice = r.choice ? 'Approved' : 'Rejected'; html += '
' + '' + symbol + ' ['+uChoice+'] ' + esc(r.card.text) + '
' + 'Why: ' + esc(r.card.why) + '
'; }); reveal.innerHTML = html; try { if (scormAPI) { if (isSCORM2004) { scormAPI.SetValue("cmi.score.raw", scorePct.toString()); scormAPI.SetValue("cmi.completion_status", "completed"); scormAPI.SetValue("cmi.success_status", scorePct >= 60 ? "passed" : "failed"); scormAPI.Commit(""); scormAPI.Terminate(""); } else { scormAPI.LMSSetValue("cmi.core.score.raw", scorePct.toString()); scormAPI.LMSSetValue("cmi.core.lesson_status", scorePct >= 60 ? "passed" : "failed"); scormAPI.LMSCommit(""); scormAPI.LMSFinish(""); } } } catch(e) {} } function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } `; } else if (activeGameType === 'path') { engineLayout = `

${title}

${course}
Ethics
Budget
Morale
Trust
Loading scenario story...
`; engineCss = ` :root{--bg:${colors.bg};--panel:${colors.panel};--border:${colors.border};--text:${colors.text};--highlight:${colors.highlight};} body{background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,sans-serif;padding:20px;} .wrap{max-width:600px;margin:0 auto;} header.h{text-align:center;margin-bottom:16px;} h1{font-size:22px;color:var(--highlight);margin-bottom:4px;} .sub{font-size:12px;color:var(--border);font-weight:700;} .metrics-bar{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:12px;} .metric{display:flex;flex-direction:column;gap:4px;} .m-label{font-size:9px;text-transform:uppercase;font-weight:700;color:var(--border);} .m-track{height:6px;background:rgba(0,0,0,0.2);border-radius:3px;overflow:hidden;} .m-fill{height:100%;background:var(--highlight);border-radius:3px;transition:width 0.3s;} .console{background:var(--panel);border:2px solid var(--border);border-radius:8px;padding:24px;box-shadow: 0 10px 25px rgba(0,0,0,0.25);} .case-desc{font-size:14px;line-height:1.6;margin-bottom:24px;} .choices{display:flex;flex-direction:column;gap:10px;} .btn{cursor:pointer;background:rgba(0,0,0,0.2);border:1.5px solid var(--border);color:inherit;font-weight:700;padding:12px 18px;border-radius:4px;font-size:13px;text-align:left;transition:all 0.15s;} .btn:hover{border-color:var(--highlight);background:rgba(255,255,255,0.05);} .btn.restart{text-align:center;background:var(--highlight);color:white;border:none;} .modal{position:fixed;inset:0;background:rgba(0,0,0,0.85);display:flex;align-items:center;justify-content:center;padding:14px;z-index:100;backdrop-filter:blur(6px);} .modal.hidden{display:none;} .card{background:var(--panel);border:2px solid var(--border);border-radius:8px;max-width:440px;width:100%;padding:24px;text-align:center;} .rules{text-align:left;background:rgba(0,0,0,0.2);padding:12px 16px;margin-bottom:16px;} `; engineScript = ` const CASES = ${dataStr}; let currentNode = 'start'; let caseIdx = 0; // 4 live metrics const metrics = { ethics:80, budget:80, morale:80, trust:80 }; document.getElementById('start').onclick = start; // Synthesizer Audio let audioCtx = null; function playTone(freq, duration, type = 'sine') { try { if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.08, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration); osc.start(); osc.stop(audioCtx.currentTime + duration); } catch(e) {} } const sound = { click: () => playTone(600, 0.08, 'triangle'), outcome: () => { playTone(520, 0.1, 'sine'); setTimeout(() => playTone(780, 0.2, 'sine'), 100); }, }; // Sanitizer for safe formatting tags (b, i, u, strong, em, br) function sanitize(html) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; let escaped = String(html).replace(/[&<>"']/g, c => map[c]); escaped = escaped.replace(/<b>/g, '').replace(/<\/b>/g, ''); escaped = escaped.replace(/<strong>/g, '').replace(/<\/strong>/g, ''); escaped = escaped.replace(/<i>/g, '').replace(/<\/i>/g, ''); escaped = escaped.replace(/<em>/g, '').replace(/<\/em>/g, ''); escaped = escaped.replace(/<br\s*\/?>/gi, '
'); return escaped; } // SCORM API Helper let scormAPI = null; let isSCORM2004 = false; function findAPI(win) { let limit = 0; while (win) { if (win.API_1484_11 != null) { isSCORM2004 = true; return win.API_1484_11; } if (win.API != null) { isSCORM2004 = false; return win.API; } if (win.parent == win || win.parent == null) break; limit++; if (limit > 10) break; win = win.parent; } return null; } try { scormAPI = findAPI(window) || (window.opener ? findAPI(window.opener) : null); if (scormAPI) { if (isSCORM2004) { scormAPI.Initialize(""); scormAPI.SetValue("cmi.completion_status", "incomplete"); scormAPI.Commit(""); } else { scormAPI.LMSInitialize(""); scormAPI.LMSSetValue("cmi.core.lesson_status", "incomplete"); scormAPI.LMSCommit(""); } } } catch(e) {} function start() { sound.click(); document.getElementById('intro').classList.add('hidden'); caseIdx = 0; currentNode = 'start'; metrics.ethics = 80; metrics.budget = 80; metrics.morale = 80; metrics.trust = 80; updateMetricsUI(); loadNode(); } function updateMetricsUI() { document.getElementById('m-ethics').style.width = metrics.ethics + '%'; document.getElementById('m-budget').style.width = metrics.budget + '%'; document.getElementById('m-morale').style.width = metrics.morale + '%'; document.getElementById('m-trust').style.width = metrics.trust + '%'; } function loadNode() { const caseNode = CASES[caseIdx]; const node = caseNode.nodes[currentNode]; document.getElementById('storyText').innerHTML = sanitize(node.text); const container = document.getElementById('choiceContainer'); container.innerHTML = ''; if (node.end) { // Terminal node sound.outcome(); const btn = document.createElement('button'); btn.className = 'btn restart'; btn.textContent = 'Restart Game'; btn.onclick = start; container.appendChild(btn); // Calculate average metric score const avgScore = Math.round((metrics.ethics + metrics.budget + metrics.morale + metrics.trust) / 4); // Send SCORM score try { if (scormAPI) { if (isSCORM2004) { scormAPI.SetValue("cmi.score.raw", avgScore.toString()); scormAPI.SetValue("cmi.completion_status", "completed"); scormAPI.SetValue("cmi.success_status", avgScore >= 60 ? "passed" : "failed"); scormAPI.Commit(""); scormAPI.Terminate(""); } else { scormAPI.LMSSetValue("cmi.core.score.raw", avgScore.toString()); scormAPI.LMSSetValue("cmi.core.lesson_status", avgScore >= 60 ? "passed" : "failed"); scormAPI.LMSCommit(""); scormAPI.LMSFinish(""); } } } catch(e) {} } else { node.choices.forEach((ch, chIdx) => { const btn = document.createElement('button'); btn.className = 'btn'; btn.textContent = ch.text; btn.onclick = () => { sound.click(); // Procedurally update metrics based on choice index if (chIdx === 0) { metrics.ethics = Math.min(100, Math.max(10, metrics.ethics + 10)); metrics.budget = Math.min(100, Math.max(10, metrics.budget - 15)); metrics.morale = Math.min(100, Math.max(10, metrics.morale + 5)); } else if (chIdx === 1) { metrics.ethics = Math.min(100, Math.max(10, metrics.ethics - 15)); metrics.budget = Math.min(100, Math.max(10, metrics.budget + 10)); metrics.morale = Math.min(100, Math.max(10, metrics.morale - 5)); metrics.trust = Math.min(100, Math.max(10, metrics.trust + 5)); } else { metrics.morale = Math.min(100, Math.max(10, metrics.morale - 10)); metrics.trust = Math.min(100, Math.max(10, metrics.trust - 10)); metrics.budget = Math.min(100, Math.max(10, metrics.budget + 15)); } updateMetricsUI(); currentNode = ch.next; loadNode(); }; container.appendChild(btn); }); } } function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } `; } // Combine wrapper code return ` ${title} ${engineLayout} `; } function compileAndDownloadZip(version = '1.2') { const zip = new JSZip(); const gameTitle = document.getElementById('gameTitle').value || "Crisis Simulation Game"; const courseName = document.getElementById('courseName').value || "University Course"; const cleanTitle = escapeHtml(gameTitle); const cleanCourse = escapeHtml(courseName); let manifestXml = ''; if (version === '2004') { manifestXml = ` ADL SCORM 2004 3rd Edition ${cleanTitle} - ${cleanCourse} ${cleanTitle} `; } else { manifestXml = ` ADL SCORM 1.2 ${cleanTitle} - ${cleanCourse} ${cleanTitle} `; } // 2. Generate Launcher HTML const launcherHtml = compileIframeGameSource(); // 3. Add to ZIP zip.file("imsmanifest.xml", manifestXml); zip.file("index.html", launcherHtml); // 4. Download Trigger zip.generateAsync({type:"blob"}).then(function(content) { const url = window.URL.createObjectURL(content); const a = document.createElement("a"); a.href = url; // Clean filename const suffix = version === '2004' ? 'SCORM_2004' : 'SCORM_1_2'; const filename = `${courseName.replace(/[^a-zA-Z0-9]/g, '_')}_${gameTitle.replace(/[^a-zA-Z0-9]/g, '_')}_${suffix}.zip`; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }); } function downloadStandaloneHtml() { const gameTitle = document.getElementById('gameTitle').value || "Crisis Simulation Game"; const courseName = document.getElementById('courseName').value || "University Course"; const htmlContent = compileIframeGameSource(); const blob = new Blob([htmlContent], {type: "text/html"}); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const filename = `${courseName.replace(/[^a-zA-Z0-9]/g, '_')}_${gameTitle.replace(/[^a-zA-Z0-9]/g, '_')}_Standalone.html`; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function exportGameJson() { const gameTitle = document.getElementById('gameTitle').value || "Crisis Simulation Game"; const courseName = document.getElementById('courseName').value || "University Course"; const schema = { generator: "EduGame Studio", version: "1.0", gameType: activeGameType, theme: activeTheme, title: gameTitle, course: courseName, content: gameContent }; const jsonStr = JSON.stringify(schema, null, 2); const blob = new Blob([jsonStr], {type: "application/json"}); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const filename = `${courseName.replace(/[^a-zA-Z0-9]/g, '_')}_${gameTitle.replace(/[^a-zA-Z0-9]/g, '_')}_Config.json`; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function importGameJson(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const json = JSON.parse(e.target.result); if (!json || json.generator !== "EduGame Studio" || json.gameType !== "shooter" || !Array.isArray(json.content)) { alert("Invalid configuration file. This builder only supports Scope Shooter configurations (gameType: 'shooter')."); return; } // Apply details document.getElementById('gameTitle').value = json.title || ''; document.getElementById('courseName').value = json.course || ''; // Force theme to shooter (already hardcoded) activeGameType = 'shooter'; // Set theme activeTheme = json.theme || 'military'; document.querySelectorAll('.theme-option').forEach(el => { el.classList.remove('active'); if (el.getAttribute('data-theme') === activeTheme) el.classList.add('active'); }); // Force custom preset UI display const presetSelect = document.getElementById('subjectDomain'); presetSelect.value = 'custom'; document.getElementById('customTopicGroup').style.display = 'flex'; document.getElementById('customTopic').value = json.title || ''; document.getElementById('presetInfo').textContent = "Loaded from imported JSON configuration."; // Apply content gameContent = json.content; renderEditorUI(); alert("EduGame Studio configuration imported successfully!"); } catch(err) { alert("Error parsing imported JSON: " + err.message); } }; reader.readAsText(file); input.value = ''; // Reset input element } function downloadClassroomWorksheet() { const gameTitle = document.getElementById('gameTitle').value || "Crisis Simulation Game"; const courseName = document.getElementById('courseName').value || "University Course"; let md = `# Study Worksheet: ${gameTitle}\n`; md += `**Course**: ${courseName}\n`; md += `**Game Type**: ${activeGameType.toUpperCase()}\n`; md += `**Generated**: ${new Date().toLocaleDateString()}\n\n`; md += `---\n\n`; if (activeGameType === 'decision') { gameContent.forEach((scen, idx) => { md += `## Scenario ${idx + 1}: ${scen.title}\n\n`; md += `### Description\n${scen.desc}\n\n`; md += `### Decision Steps & Actions\n\n`; const correctSteps = scen.wires .filter(w => w.order > 0) .sort((a, b) => a.order - b.order); const traps = scen.wires.filter(w => w.order < 0); md += `#### Correct Execution Sequence:\n`; correctSteps.forEach(w => { md += `${w.order}. **[${w.color.toUpperCase()}] ${w.label}**\n *Effect / Feedback*: ${w.desc}\n`; }); if (traps.length > 0) { md += `\n#### Danger / Trap Decisions:\n`; traps.forEach(w => { md += `* **[${w.color.toUpperCase()}] ${w.label}** (TRAP)\n *Consequence*: ${w.desc}\n`; }); } md += `\n---\n\n`; }); } else if (activeGameType === 'shooter') { gameContent.forEach((scen, idx) => { md += `## Round ${idx + 1}: ${scen.title}\n\n`; md += `**Prompt / Mission**: ${scen.prompt}\n\n`; md += `### Target Options:\n\n`; scen.options.forEach(opt => { const status = opt.correct ? "βœ“ CORRECT TARGET" : "βœ— DISTRACTOR (TRAP)"; md += `* **${opt.t}** [${status}]\n *Explanation*: ${opt.why}\n\n`; }); md += `---\n\n`; }); } else if (activeGameType === 'swipe') { gameContent.forEach((scen, idx) => { md += `## Swipe Round ${idx + 1}: ${scen.title}\n\n`; md += `### Classification Cards:\n\n`; scen.cards.forEach(card => { const status = card.approve ? "APPROVE (Swipe Right)" : "REJECT (Swipe Left)"; md += `* **Card Text**: "${card.text}"\n *Correct Action*: **${status}**\n *Justification*: ${card.why}\n\n`; }); md += `---\n\n`; }); } else if (activeGameType === 'path') { gameContent.forEach((scen, idx) => { md += `## Branching Case ${idx + 1}: ${scen.title}\n\n`; md += `### Dialogue Graph Nodes:\n\n`; Object.keys(scen.nodes).forEach(nKey => { const node = scen.nodes[nKey]; md += `#### Node: ${nKey}\n`; md += `**Text**: "${node.text}"\n\n`; if (node.end) { md += `* **Ending Node** (SCORM Score: ${node.score}/100)\n\n`; } else { md += `**Choices / Decision Options**:\n`; node.choices.forEach(ch => { md += `* "${ch.text}" → leads to **${ch.next}**\n`; }); md += `\n`; } }); md += `---\n\n`; }); } md += `*Document compiled using EduGame Studio.*`; const blob = new Blob([md], {type: "text/markdown"}); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const filename = `${courseName.replace(/[^a-zA-Z0-9]/g, '_')}_${gameTitle.replace(/[^a-zA-Z0-9]/g, '_')}_Worksheet.md`; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }