\n' }, { icon: '🎨', name: 'CSS Art Gallery', desc: 'Create colorful shapes with CSS', topic: 'css', code: '\n
' }, { icon: '🧮', name: 'JS Calculator', desc: 'A working calculator', topic: 'js', code: 'function calc(a, op, b) {\n if(op === "+") return a + b;\n if(op === "-") return a - b;\n if(op === "*") return a * b;\n if(op === "/") return a / b;\n}\nconsole.log(calc(5, "+", 3));' }, { icon: '🐍', name: 'Python Greeter', desc: 'A greeting program', topic: 'python', code: '# Try this in a Python environment\ndef greet(name):\n return f"Hello, {name}!"\n\nprint(greet("World"))' }, ]; let html = '

📁 Real-World Projects

Try these projects using what you learned!

'; projects.forEach((p, i) => { html += `

${p.icon} ${p.name}

${p.desc}

Topic: ${p.topic}

${p.code}
${p.topic === 'js' ? `` : ''}
`; }); app.innerHTML = `

📁 Projects

${html}
`; } function runProject(code) { const result = runJSCode(code); toast(result.error ? '❌ ' + result.output : '✅ ' + result.output, result.error ? 'error' : 'success', 5000); unlockAchievement('project_1'); } // ---- TYPESCRIPT QUEST GAME ---- const TS_LEVELS = [ { id: 1, name: 'Basic Types', icon: '📘', desc: 'Numbers, strings, booleans, arrays', color: '#6366f1', bg: 'rgba(99,102,241,0.15)', questions: 5, unlock: [1] }, { id: 2, name: 'Functions & Objects', icon: '📦', desc: 'Function types, interfaces, type aliases', color: '#8b5cf6', bg: 'rgba(139,92,246,0.15)', questions: 5, unlock: [1] }, { id: 3, name: 'Generics & Enums', icon: '⚙️', desc: 'Generics, enums, utility types', color: '#a855f7', bg: 'rgba(168,85,247,0.15)', questions: 5, unlock: [2] }, { id: 4, name: 'Advanced TS', icon: '🔷', desc: 'Type guards, conditional types, mapped types', color: '#d946ef', bg: 'rgba(217,70,239,0.15)', questions: 5, unlock: [2,3] }, { id: 5, name: 'Final Challenge', icon: '👑', desc: 'Mixed TS topics — prove your mastery!', color: '#f59e0b', bg: 'rgba(245,158,11,0.15)', questions: 5, unlock: [4] }, ]; const TS_STORAGE_KEY = 'codequest-ts'; function loadTsState() { try { const raw = localStorage.getItem(TS_STORAGE_KEY); if (raw) return JSON.parse(raw); } catch(e) {} return { completed: [], stars: {}, unlocked: [1], totalScore: 0 }; } function saveTsState() { try { localStorage.setItem(TS_STORAGE_KEY, JSON.stringify(S_TS)); } catch(e) {} } let S_TS = loadTsState(); let tsGameState = null; function renderTsTab() { if (tsGameState) { renderTsGame(); return; } const allDone = S_TS.completed.length === TS_LEVELS.length; let levelsHTML = ''; TS_LEVELS.forEach(lv => { const unlocked = S_TS.unlocked.includes(lv.id); const done = S_TS.completed.includes(lv.id); const stars = S_TS.stars[lv.id] || 0; levelsHTML += `
${unlocked ? lv.icon : '🔒'}
${lv.name}
${lv.desc}
${'⭐'.repeat(stars)}${'☆'.repeat(3-stars)}
`; }); const totalStars = Object.values(S_TS.stars).reduce((a,b)=>a+b, 0); return `
🔷

TypeScript Quest

Master TypeScript across ${TS_LEVELS.length} levels of increasing difficulty!

⭐ ${totalStars}/${TS_LEVELS.length*3} ✅ ${S_TS.completed.length}/${TS_LEVELS.length} 🏆 ${S_TS.totalScore}
${levelsHTML}
Progress${Math.round(S_TS.completed.length/TS_LEVELS.length*100)}%
`; } function resetTsProgress() { if (confirm('Reset all TypeScript progress?')) { S_TS = { completed: [], stars: {}, unlocked: [1], totalScore: 0 }; saveTsState(); tsGameState = null; render(); } } function startTsLevel(levelId) { const lv = TS_LEVELS.find(l => l.id === levelId); if (!lv) return; const questions = []; for (let i = 0; i < lv.questions; i++) { questions.push(generateQuestion('typescript', 'medium')); } tsGameState = { levelId, level: lv, questions, idx: 0, correct: 0, lives: 3, score: 0, feedback: null, selected: null, combo: 0 }; Audio.play('click'); render(); } function exitTsGame() { tsGameState = null; render(); } function renderTsGame() { if (!tsGameState) return; const g = tsGameState; const lv = g.level; if (g.idx >= g.questions.length) { // Level complete const accuracy = g.questions.length > 0 ? Math.round(g.correct / g.questions.length * 100) : 0; const stars = accuracy >= 90 ? 3 : accuracy >= 70 ? 2 : 1; if (!S_TS.completed.includes(g.levelId)) S_TS.completed.push(g.levelId); if ((S_TS.stars[g.levelId] || 0) < stars) S_TS.stars[g.levelId] = stars; S_TS.totalScore += g.score; // Unlock next levels TS_LEVELS.forEach(l => { if (l.unlock.includes(g.levelId) && !S_TS.unlocked.includes(l.id)) { S_TS.unlocked.push(l.id); } }); // Unlock next sequential level const nextId = g.levelId + 1; if (nextId <= TS_LEVELS.length && !S_TS.unlocked.includes(nextId)) { S_TS.unlocked.push(nextId); } saveTsState(); if (g.lives > 0 && stars >= 2) unlockAchievement('ts_master'); const container = document.getElementById('tabContent') || document.querySelector('.scroll-area'); if (!container) return; container.innerHTML = `
${accuracy >= 80 ? '🏆' : '📘'}

${lv.name} Complete!

${'⭐'.repeat(stars)}${'☆'.repeat(3-stars)}

Correct: ${g.correct}/${g.questions.length} (${accuracy}%)

Score: +${g.score} pts

Lives remaining: ${'❤️'.repeat(g.lives)}

${g.levelId < TS_LEVELS.length ? `` : ''}
`; tsGameState = null; return; } const q = g.questions[g.idx]; const container = document.getElementById('tabContent') || document.querySelector('.scroll-area'); if (!container) return; container.innerHTML = `
🔷 ${lv.name} ${'❤️'.repeat(g.lives)} Q${g.idx+1}/${g.questions.length} ✅ ${g.correct} ${g.combo >= 2 ? `🔥 x${g.combo}` : ''}
TypeScript Difficulty: ${g.levelId <= 2 ? 'Easy' : g.levelId <= 3 ? 'Medium' : 'Hard'}

${q.q}

`; const wrap = document.getElementById('tsOpts'); q.options.forEach((opt, i) => { const btn = document.createElement('button'); btn.className = 'option-btn game-touch'; if (g.selected === i) btn.classList.add(i === q.correct ? 'correct' : 'wrong'); if (g.feedback && i === q.correct) btn.style.boxShadow = '0 0 0 2px #22c55e'; if (g.feedback) btn.classList.add('disabled'); btn.textContent = opt; btn.onclick = () => answerTs(i, q); wrap.appendChild(btn); }); if (g.feedback) { document.getElementById('tsFb').innerHTML = `
${g.feedback}
`; } } function answerTs(i, q) { const g = tsGameState; if (!g || g.feedback) return; g.selected = i; const correct = i === q.correct; recordAnswer('typescript', correct, q.q); if (correct) { g.combo++; const bonus = g.combo >= 3 ? 50 : g.combo >= 2 ? 20 : 0; const pts = 100 + bonus; g.score += pts; g.correct++; g.feedback = `✅ Correct! ${q.explain}${bonus > 0 ? ` 🔥 x${g.combo} combo +${bonus}` : ''}`; Audio.play('win'); } else { g.combo = 0; g.lives--; g.feedback = `❌ "${q.options[q.correct]}". ${q.explain}`; Audio.play('error'); } renderTsGame(); if (g.lives <= 0) { // Game over for this level setTimeout(() => { if (!tsGameState) return; const g2 = tsGameState; const accuracy = g2.correct > 0 ? Math.round(g2.correct / (g2.idx + 1) * 100) : 0; const stars = accuracy >= 90 ? 3 : accuracy >= 70 ? 2 : 1; if ((S_TS.stars[g2.levelId] || 0) < stars) S_TS.stars[g2.levelId] = stars; S_TS.totalScore += g2.score; saveTsState(); const container = document.getElementById('tabContent') || document.querySelector('.scroll-area'); if (!container) return; container.innerHTML = `
💀

Out of Lives!

${'⭐'.repeat(stars)}${'☆'.repeat(3-stars)}

Correct: ${g2.correct}/${g2.idx+1}

Score: +${g2.score} pts

`; tsGameState = null; }, 1500); return; } setTimeout(() => { if (!tsGameState) return; tsGameState.idx++; tsGameState.feedback = null; tsGameState.selected = null; renderTsGame(); }, correct ? 1200 : 2000); } // ---- SETTINGS TAB ---- function renderSettingsTab() { const charHTML = CHARACTERS.map(c => { const owned = c.price === 0 || S.ownedItems.includes('char_' + c.id) || S.character === c.id; return `
${c.emoji}
${c.name}
${!owned ? `
🪙${c.price}
` : ''}
`; }).join(''); return `

🧑 Character

${charHTML}

🎨 Theme

🔊 Audio

Sound Effects
Background Music

♿ Accessibility

Dyslexia-friendly Font
Colorblind Mode
Large Text

🌐 Language / भाषा

📤 Share

💾 Data

CodeQuest Adventure v2.0

Made with ❤️ for learning

`; } function setCharacter(id) { S.character = id; saveState(); Audio.play('click'); unlockAchievement('character_change'); render(); } function setTheme(t) { S.settings.theme = t; saveState(); applySettings(); Audio.play('click'); render(); } function setLang(l) { S.settings.lang = l; saveState(); unlockAchievement('lang_switch'); Audio.play('click'); render(); } function toggleSetting(key) { S.settings[key] = !S.settings[key]; saveState(); if (key === 'music') { S.settings.music ? Audio.startMusic() : Audio.stopMusic(); } applySettings(); Audio.play('click'); render(); } function toggleSound() { toggleSetting('sound'); } function resetAll() { if (confirm('Reset ALL progress? This cannot be undone!')) { S = JSON.parse(JSON.stringify(defaultState)); saveState(); toast('Progress reset!', 'info'); render(); } } // ---- OPEN LEVEL ---- function openLevel(id) { if (!S.unlocked.includes(id)) return; activeLevel = id; hintCount = 3; streak = 0; activeShield = false; activeDouble = false; activeFreeze = false; Audio.play('click'); render(); } function backToMap() { if (window.currentGameCleanup) { window.currentGameCleanup(); window.currentGameCleanup = null; } // Check speedrun completion if (gameMode === 'speedrun' && speedrunStartTime && S.completed.length === LEVELS.length) { const time = Date.now() - speedrunStartTime; if (!S.speedrunBest || time < S.speedrunBest) { S.speedrunBest = time; saveState(); } unlockAchievement('speed_demon'); if (time < 300000) unlockAchievement('speedrun_sub5'); addToLeaderboard('Speedrun', Math.max(0, 600000 - time)); toast(`Speedrun complete! Time: ${formatTime(time)}`, 'success', 5000); gameMode = 'speedrun'; speedrunStartTime = null; } if (gameMode === 'ngplus' && S.completed.length === LEVELS.length) { unlockAchievement('ng_plus'); toast('New Game+ completed!', 'success', 4000); gameMode = 'normal'; } activeLevel = null; render(); } // ---- GAME RENDERING ---- function renderGame() { const level = LEVELS.find(l => l.id === activeLevel); const charEmoji = CHARACTERS.find(c => c.id === S.character)?.emoji || '🧑'; let timerHTML = ''; if (gameMode === 'speedrun' && speedrunStartTime) { const elapsed = Date.now() - speedrunStartTime; timerHTML = `
⏱️ ${formatTime(elapsed)}
`; } app.innerHTML = ` ${timerHTML}

L${level.id}: ${level.name}

${level.topic}
`; // Start speedrun timer interval if (gameMode === 'speedrun' && speedrunStartTime) { const timerEl = document.getElementById('speedTimer'); if (timerEl) { const interval = setInterval(() => { if (!speedrunStartTime || activeLevel === null) { clearInterval(interval); return; } timerEl.textContent = '⏱️ ' + formatTime(Date.now() - speedrunStartTime); }, 1000); } } const body = document.getElementById('gameBody'); // Show story cutscene on first visit if (!S.completed.includes(level.id) && level.id > 1 && gameMode === 'normal') { showCutscene(level, () => startGameForLevel(level, body, charEmoji)); } else { startGameForLevel(level, body, charEmoji); } } function startGameForLevel(level, body, charEmoji) { switch (level.id) { case 1: startPythonForest(body, level, charEmoji); break; case 2: startHtmlCatcher(body, level); break; case 3: startCssWizard(body, level); break; case 4: startJsHunter(body, level); break; case 5: startAiPattern(body, level); break; case 6: startVariableHunt(body, level, charEmoji); break; case 7: startLoopRunner(body, level, charEmoji); break; case 8: startFunctionDefender(body, level); break; case 9: startConditionalQuest(body, level); break; case 10: startFinalBoss(body, level); break; } } // ---- CUTSCENE ---- function showCutscene(level, onComplete) { const overlay = document.createElement('div'); overlay.className = 'cutscene'; overlay.innerHTML = `
${level.emoji}
`; document.body.appendChild(overlay); const textEl = document.getElementById('cutsceneText'); const skipBtn = document.getElementById('cutsceneSkip'); let i = 0; const text = level.story; let typing = true; const typeInterval = setInterval(() => { if (i >= text.length) { clearInterval(typeInterval); typing = false; skipBtn.textContent = 'Continue ▶'; return; } textEl.textContent = text.substring(0, i + 1); i++; }, 40); skipBtn.onclick = () => { clearInterval(typeInterval); if (typing) { textEl.textContent = text; typing = false; skipBtn.textContent = 'Continue ▶'; } else { overlay.remove(); onComplete(); } }; } // ---- HINT & POWERUP MENUS ---- function showHint(levelId) { Audio.play('click'); const level = LEVELS.find(l => l.id === levelId); const hints = getHintsForLevel(levelId); const used = S.hintsUsed[levelId] || 0; const remaining = hintCount - used; const overlay = document.createElement('div'); overlay.className = 'overlay'; overlay.innerHTML = `
💡

Hints (${remaining} left)

${hints[used] || 'No more hints for this level!'}

${remaining > 0 && hints[used] ? `` : ''}
`; document.querySelector('.game-body').appendChild(overlay); document.getElementById('closeHint').onclick = () => overlay.remove(); const useBtn = document.getElementById('useHintBtn'); if (useBtn) { useBtn.onclick = () => { S.hintsUsed[levelId] = (S.hintsUsed[levelId] || 0) + 1; saveState(); Audio.play('hint'); toast(t('hintUsed'), 'info'); overlay.remove(); }; } } function getHintsForLevel(levelId) { const hintMap = { 1: ['Move with arrows/WASD, jump with Space, shoot with F', 'Stomp bugs from above to defeat them without taking damage', 'Keywords glow green — collect 8 to win!'], 2: ['Opening tags are blue, closing tags are pink', 'Match

with

— same tag name, different closing', 'Tap opening tag first, then its matching closing tag'], 3: ['Red + Green = Yellow. Mix to match the target', 'Each button adds 80 to that color channel', 'Target RGB is shown — calculate backwards'], 4: ['Read the buggy code carefully — the error is highlighted', 'Check operators: + vs -, = vs ===', 'Look for missing commas in arrays and objects'], 5: ['Watch the sequence carefully — it flashes one by one', 'Pattern length grows each round', 'Say the colors out loud to remember better'], 6: ['Collect all 4 variables (💎) before reaching the exit (🚪)', 'Walls are 🧱 — you cannot pass through', 'Plan your route to minimize moves'], 7: ['Tap JUMP button or Space to leap over obstacles', 'Speed increases over time — react faster!', 'Rocks are short, bugs are tall — time your jumps'], 8: ['Place turrets NEAR the path but not ON it', 'shoot() is cheap, blast() is powerful, freeze() slows', 'Earn coins by killing enemies, spend on more turrets'], 9: ['Read the variable value carefully before choosing', 'If condition is TRUE, first branch runs; else second', 'Think: what SHOULD happen given the current state?'], 10: ['Correct answer damages boss, wrong answer damages you', 'Build combos for bonus damage', 'All 9 topics are tested — review weak areas first!'], }; return hintMap[levelId] || ['Stay focused and read carefully!']; } function showPowerupMenu() { Audio.play('click'); const overlay = document.createElement('div'); overlay.className = 'overlay'; overlay.innerHTML = `

Power-ups

`; document.querySelector('.game-body').appendChild(overlay); } function usePwr(type) { usePowerup(type); document.querySelectorAll('.overlay').forEach(o => o.remove()); } // ---- COMPLETE LEVEL ---- function completeLevel(levelId, stars, score) { S.completed = S.completed.includes(levelId) ? S.completed : [...S.completed, levelId]; S.stars[levelId] = Math.max(S.stars[levelId] || 0, stars); const nextLevel = levelId + 1; S.unlocked = S.unlocked.includes(nextLevel) ? S.unlocked : [...S.unlocked, nextLevel]; S.stats.wins++; let coinReward = 50 + stars * 20; if (activeDouble) coinReward *= 2; addCoins(coinReward); if (stars === 3) unlockAchievement('no_hints'); saveState(); checkAchievements(); return coinReward; } // ---- WIN/GAME OVER OVERLAYS ---- function showWinOverlay(parent, level, msg, stars, score) { const coins = completeLevel(level.id, stars, score); Audio.play('win'); const ov = document.createElement('div'); ov.className = 'overlay'; ov.innerHTML = `
🏆

${t('win')}

${msg}

${'⭐'.repeat(stars)}${'☆'.repeat(3-stars)}

🪙 +${coins} coins ${activeDouble ? '(2x!)' : ''}

${level.id < 10 ? `` : ''}
`; parent.appendChild(ov); } function showGameOverOverlay(parent, level, msg, onRetry) { S.stats.fails++; saveState(); Audio.play('lose'); checkAchievements(); const ov = document.createElement('div'); ov.className = 'overlay'; ov.innerHTML = `
💀

${t('gameOver')}

${msg}

`; parent.appendChild(ov); document.getElementById('retryBtn').onclick = () => { ov.remove(); onRetry(); }; } function makeHUD(badges) { return `
${badges.map(b => `${b}`).join('')}
`; } function makeStartOverlay(level, body, hintHTML, onStart) { const ov = document.createElement('div'); ov.className = 'overlay'; ov.innerHTML = `
${level.emoji}

${level.name}

${level.description}

${hintHTML}
`; body.appendChild(ov); document.getElementById('startBtn').onclick = () => { ov.remove(); onStart(); }; } // ============================================================ // LEVEL 1: PYTHON FOREST // ============================================================ function startPythonForest(body, level, charEmoji) { body.innerHTML = `
`; const canvas = document.getElementById('pfCanvas'); const ctx = canvas.getContext('2d'); const TARGET = 8; let state = { score: 0, hp: 3, collected: 0, won: false, over: false, started: false, collectedKW: [] }; const keys = {}; const player = { x: 80, y: 300, w: 28, h: 36, vx: 0, vy: 0, onGround: false, facing: 1, shootCd: 0 }; let bullets = [], entities = buildPFLevel(), camera = 0; const world = { width: 3000 }; let animId = null, particles = [], screenShake = 0; function buildPFLevel() { const ents = []; [[0,380,600,40],[700,380,400,40],[1200,320,300,40],[1600,380,500,40],[2200,320,300,40],[2600,380,400,40]].forEach(p => ents.push({x:p[0],y:p[1],w:p[2],h:p[3],vx:0,vy:0,alive:true,type:'platform'})); [[400,290],[900,270],[1400,220],[1900,280],[2400,230]].forEach(p => ents.push({x:p[0],y:p[1],w:100,h:14,vx:0,vy:0,alive:true,type:'platform'})); [[300,350],[850,350],[1300,290],[1750,350],[2300,290],[2750,350]].forEach((b,i) => ents.push({x:b[0],y:b[1],w:28,h:24,vx:i%2===0?-1.2:1.2,vy:0,alive:true,type:'bug',hp:1})); [['print',250,320],['def',450,240],['if',950,220],['else',1300,270],['for',1450,170],['while',1850,230],['return',2350,180],['class',2700,320],['import',2900,320],['True',600,320],['None',1150,270]].forEach(k => ents.push({x:k[2],y:k[3],w:50,h:22,vx:0,vy:0,alive:true,type:'keyword',text:k[0]})); return ents; } function updateHUD() { document.getElementById('pfHUD').innerHTML = makeHUD([`❤️${state.hp}`,`⭐${state.score}`,`🐍${state.collected}/${TARGET}`]); } function addScore(n) { state.score += n; updateHUD(); } function damagePlayer(n) { if (activeShield) { activeShield = false; toast('Shield absorbed damage!', 'success'); return; } state.hp -= n; screenShake = 10; Audio.play('error'); updateHUD(); if (state.hp <= 0) { state.over = true; showGameOverOverlay(document.getElementById('pfWrap'), level, 'Out of hearts!', () => startPythonForest(body, level, charEmoji)); } } function spawnParticles(x, y, color, count) { for (let i = 0; i < count; i++) particles.push({x,y,vx:(Math.random()-0.5)*6,vy:(Math.random()-0.5)*6-2,life:20,color}); } function loop() { if (state.won || state.over) return; const GRAVITY = 0.55, MOVE = 3.4, JUMP = -11; if (keys['arrowleft']||keys['a']) { player.vx = -MOVE; player.facing = -1; } else if (keys['arrowright']||keys['d']) { player.vx = MOVE; player.facing = 1; } else { player.vx *= 0.7; } if ((keys['arrowup']||keys['w']||keys[' ']) && player.onGround) { player.vy = JUMP; player.onGround = false; Audio.play('jump'); } if (player.shootCd > 0) player.shootCd--; if ((keys['f']||keys['j']) && player.shootCd === 0) { bullets.push({x:player.x+(player.facing===1?player.w:0),y:player.y+14,vx:player.facing*9,alive:true}); player.shootCd = 12; Audio.play('shoot'); } player.vy += GRAVITY; player.x += player.vx; player.y += player.vy; if (player.x < 0) player.x = 0; if (player.x + player.w > world.width) player.x = world.width - player.w; if (player.y > 600) { damagePlayer(1); player.x = 80; player.y = 200; player.vy = 0; } player.onGround = false; entities.forEach(e => { if (e.type!=='platform'||!e.alive) return; if (player.xe.x && player.y+player.h>e.y && player.y+player.h=0) { player.y = e.y - player.h; player.vy = 0; player.onGround = true; } }); const targetCam = player.x - 280; camera += (targetCam - camera) * 0.1; if (camera < 0) camera = 0; if (camera > world.width - canvas.width) camera = world.width - canvas.width; bullets.forEach(b => { b.x += b.vx; if (b.x<0||b.x>world.width) b.alive = false; }); bullets = bullets.filter(b => b.alive); entities.forEach(e => { if (e.type!=='bug'||!e.alive) return; e.vy += GRAVITY; e.x += e.vx; e.y += e.vy; let onPlat = false; entities.forEach(pl => { if (pl.type!=='platform'||!pl.alive) return; if (e.xpl.x && e.y+e.h>=pl.y && e.y+e.h=0) { e.y = pl.y - e.h; e.vy = 0; onPlat = true; } }); if (onPlat) { const ahead = entities.find(pl => pl.type==='platform'&&pl.alive&&(e.vx>0?pl.x>e.x&&pl.xe.x-30)); if (!ahead) e.vx *= -1; } }); bullets.forEach(b => { entities.forEach(e => { if (e.type!=='bug'||!e.alive) return; if (b.x>e.x&&b.xe.y&&b.y { if (e.type!=='bug'||!e.alive) return; if (player.xe.x && player.ye.y) { if (player.vy>0 && player.y+player.h-player.vy<=e.y+4) { e.alive = false; player.vy = -7; addScore(30); spawnParticles(e.x-camera, e.y, '#ef4444', 8); Audio.play('collect'); } else { damagePlayer(1); player.x += player.facing*-30; player.vy = -6; } } }); entities.forEach(e => { if (e.type!=='keyword'||!e.alive) return; if (player.xe.x && player.ye.y) { e.alive = false; addScore(100); state.collected++; state.collectedKW.push(e.text); spawnParticles(e.x-camera, e.y, '#34d399', 12); Audio.play('collect'); updateHUD(); if (state.collected >= TARGET) { state.won = true; const stars = state.hp>=3?3:state.hp===2?2:1; showWinOverlay(document.getElementById('pfWrap'), level, `Collected: ${state.collectedKW.join(', ')}`, stars, state.score); } } }); if (screenShake > 0) screenShake--; render(); animId = requestAnimationFrame(loop); } function render() { ctx.save(); if (screenShake > 0) ctx.translate((Math.random()-0.5)*screenShake, (Math.random()-0.5)*screenShake); const grad = ctx.createLinearGradient(0,0,0,canvas.height); grad.addColorStop(0,'#0f2a1a'); grad.addColorStop(1,'#1a3d2a'); ctx.fillStyle = grad; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.fillStyle = 'rgba(20,60,35,0.5)'; for (let i = 0; i < 20; i++) { const x = (i*200-camera*0.4)%(canvas.width+200)-100; ctx.beginPath(); ctx.moveTo(x,200); ctx.lineTo(x-30,380); ctx.lineTo(x+30,380); ctx.closePath(); ctx.fill(); } entities.forEach(e => { if (!e.alive) return; const sx = e.x - camera; if (sx>canvas.width+50||sx+e.w<-50) return; if (e.type==='platform') { ctx.fillStyle='#3a2a1a'; ctx.fillRect(sx,e.y,e.w,e.h); ctx.fillStyle='#4d8c3a'; ctx.fillRect(sx,e.y,e.w,6); } else if (e.type==='bug') { ctx.fillStyle='#ff4444'; ctx.beginPath(); ctx.arc(sx+e.w/2,e.y+e.h/2,e.w/2,0,Math.PI*2); ctx.fill(); ctx.fillStyle='#fff'; ctx.fillRect(sx+6,e.y+6,5,5); ctx.fillRect(sx+17,e.y+6,5,5); ctx.fillStyle='#000'; ctx.fillRect(sx+8,e.y+8,2,2); ctx.fillRect(sx+19,e.y+8,2,2); } else if (e.type==='keyword') { ctx.fillStyle='#064e3b'; ctx.fillRect(sx,e.y,e.w,e.h); ctx.strokeStyle='#34d399'; ctx.lineWidth=2; ctx.strokeRect(sx,e.y,e.w,e.h); ctx.fillStyle='#a7f3d0'; ctx.font='bold 13px monospace'; ctx.textAlign='center'; ctx.fillText(e.text||'',sx+e.w/2,e.y+15); ctx.textAlign='left'; } }); ctx.fillStyle = '#fbbf24'; bullets.forEach(b => { const sx = b.x-camera; if (sx<-10||sx>canvas.width+10) return; ctx.beginPath(); ctx.arc(sx,b.y,4,0,Math.PI*2); ctx.fill(); }); const psx = player.x - camera; ctx.fillStyle = '#3b82f6'; ctx.fillRect(psx,player.y,player.w,player.h); ctx.font = '20px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(charEmoji, psx+player.w/2, player.y+player.h-2); ctx.textAlign = 'left'; ctx.fillStyle = '#374151'; if (player.facing===1) ctx.fillRect(psx+player.w,player.y+14,10,4); else ctx.fillRect(psx-10,player.y+14,10,4); particles.forEach(p => { p.x += p.vx; p.y += p.vy; p.vy += 0.2; p.life--; ctx.globalAlpha = p.life/20; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x,p.y,3,0,Math.PI*2); ctx.fill(); ctx.globalAlpha = 1; }); particles = particles.filter(p => p.life > 0); ctx.font = '20px sans-serif'; ctx.fillText('❤️'.repeat(state.hp), 12, 28); ctx.fillStyle = '#fff'; ctx.font = 'bold 16px monospace'; ctx.fillText(`Score: ${state.score}`, 12, 56); ctx.fillText(`Keywords: ${state.collected}/${TARGET}`, 12, 78); ctx.restore(); } function kd(e) { keys[e.key.toLowerCase()] = true; if ([' ','arrowup','arrowdown','arrowleft','arrowright'].includes(e.key.toLowerCase())) e.preventDefault(); } function ku(e) { keys[e.key.toLowerCase()] = false; } function setupTouch() { const bind = (id, key) => { const el = document.getElementById(id); if (!el) return; el.addEventListener('pointerdown', e => { e.preventDefault(); keys[key] = true; }); el.addEventListener('pointerup', e => { e.preventDefault(); keys[key] = false; }); el.addEventListener('pointerleave', () => keys[key] = false); el.addEventListener('pointercancel', () => keys[key] = false); }; bind('pfLeft','arrowleft'); bind('pfRight','arrowright'); bind('pfFire','f'); bind('pfJump',' '); } function startGame() { state.started = true; document.getElementById('pfTouch').style.display = 'flex'; updateHUD(); window.addEventListener('keydown', kd); window.addEventListener('keyup', ku); setupTouch(); animId = requestAnimationFrame(loop); } window.currentGameCleanup = () => { if (animId) cancelAnimationFrame(animId); window.removeEventListener('keydown', kd); window.removeEventListener('keyup', ku); }; updateHUD(); makeStartOverlay(level, document.getElementById('pfWrap'), '📱 Touch buttons or keyboard (←/→ A/D move, ↑/Space jump, F/J shoot)', startGame); } // ============================================================ // LEVEL 2: HTML CATCHER // ============================================================ function startHtmlCatcher(body, level) { body.innerHTML = `
`; const TAG_SETS = [['',''],['',''],['',''],['

','

'],['

','

'],['
','
'],['',''],['',''],['
    ','
']]; const TARGET = 8; let state = { lives: 3, matched: 0, score: 0, won: false, over: false, started: false }; let tags = [], selected = null, nextId = 0, spawnTimer = null, animTimer = null; const area = document.getElementById('hcArea'), tagsWrap = document.getElementById('hcTags'); function updateHUD() { document.getElementById('hcHUD').innerHTML = makeHUD([`❤️ ${state.lives}`,`⭐ ${state.score}`,`🏰 ${state.matched}/${TARGET}`]); } function spawnTag() { const set = TAG_SETS[Math.floor(Math.random()*TAG_SETS.length)]; const isClosing = Math.random()>0.5; tags.push({id:nextId++,text:isClosing?set[1]:set[0],isClosing,x:10+Math.random()*80,y:-5,vy:0.4+Math.random()*0.5,matched:false}); renderTags(); } function renderTags() { tagsWrap.innerHTML = ''; tags.forEach(t => { const btn = document.createElement('button'); btn.className = 'game-touch'; btn.style.cssText = `position:absolute;font-family:monospace;font-size:14px;padding:8px 12px;border-radius:4px;min-height:36px;min-width:44px;left:${t.x}%;top:${t.y}%;transform:translateX(-50%);border:1px solid;${t.matched?'opacity:0;transform:translateX(-50%) scale(0.5)':''}`; if (t.isClosing) { btn.style.background='rgba(190,24,93,0.8)'; btn.style.color='#fff'; btn.style.borderColor='#f9a8d4'; } else { btn.style.background='rgba(29,78,216,0.8)'; btn.style.color='#fff'; btn.style.borderColor='#93c5fd'; } if (selected && selected.id === t.id) { btn.style.boxShadow='0 0 0 4px #facc15'; btn.style.transform='translateX(-50%) scale(1.1)'; } btn.textContent = t.text; btn.onclick = () => handleClick(t); tagsWrap.appendChild(btn); }); } function handleClick(tag) { if (tag.matched || state.won || state.over) return; if (!selected) { selected = tag; updateHUD(); renderTags(); return; } const sameTag = tag.text.replace('/','') === selected.text.replace('/',''); const isOpenClose = (selected.isClosing && !tag.isClosing) || (!selected.isClosing && tag.isClosing); if (sameTag && isOpenClose) { tag.matched = true; selected.matched = true; state.score += 100; state.matched++; Audio.play('collect'); recordAnswer('html', true, tag.text); if (state.matched >= TARGET) { state.won = true; const stars = state.lives>=3?3:state.lives===2?2:1; showWinOverlay(area, level, `Matched ${state.matched} pairs!`, stars, state.score); } } else { state.score = Math.max(0, state.score - 20); Audio.play('error'); recordAnswer('html', false, tag.text); } selected = null; updateHUD(); renderTags(); } function animFall() { tags.forEach(t => t.y += t.vy); const missed = tags.filter(t => t.y > 100 && !t.matched); if (missed.length > 0) { state.lives -= missed.length; updateHUD(); if (state.lives <= 0) { state.over = true; showGameOverOverlay(area, level, 'Too many missed!', () => startHtmlCatcher(body, level)); } } tags = tags.filter(t => t.y <= 100 && !t.matched); renderTags(); } function startGame() { state.started = true; updateHUD(); spawnTimer = setInterval(spawnTag, 1200); animTimer = setInterval(animFall, 50); } window.currentGameCleanup = () => { if (spawnTimer) clearInterval(spawnTimer); if (animTimer) clearInterval(animTimer); }; updateHUD(); makeStartOverlay(level, area, `Match opening (blue) with closing (pink) tags. Match ${TARGET} pairs!`, startGame); } // ============================================================ // LEVEL 3: CSS WIZARD // ============================================================ function startCssWizard(body, level) { body.innerHTML = `
`; const TARGETS = [{name:'Red',css:'red',rgb:{r:255,g:0,b:0}},{name:'Green',css:'green',rgb:{r:0,g:128,b:0}},{name:'Blue',css:'blue',rgb:{r:0,g:0,b:255}},{name:'Yellow',css:'yellow',rgb:{r:255,g:255,b:0}},{name:'Cyan',css:'cyan',rgb:{r:0,g:255,b:255}},{name:'Magenta',css:'magenta',rgb:{r:255,g:0,b:255}},{name:'Orange',css:'orange',rgb:{r:255,g:165,b:0}},{name:'Purple',css:'purple',rgb:{r:128,g:0,b:128}},{name:'Lime',css:'lime',rgb:{r:0,g:255,b:0}},{name:'Pink',css:'pink',rgb:{r:255,g:192,b:203}}]; const TARGET_ROUNDS = 6; let state = { round: 0, target: TARGETS[0], r: 0, g: 0, b: 0, score: 0, lives: 3, matched: 0, won: false, over: false, started: false, feedback: '' }; const card = document.getElementById('cwCard'); function updateHUD() { document.getElementById('cwHUD').innerHTML = makeHUD([`❤️ ${state.lives}`,`⭐ ${state.score}`,`🎨 ${state.matched}/${TARGET_ROUNDS}`]); } function renderCard() { const t = state.target, cur = `rgb(${state.r}, ${state.g}, ${state.b})`; card.innerHTML = `

Match this color:

${t.name}
color: ${t.css};
rgb(${t.rgb.r}, ${t.rgb.g}, ${t.rgb.b})

Your mix:

${cur}

Add colors:

`; document.getElementById('cwR').onclick = () => addColor('r'); document.getElementById('cwG').onclick = () => addColor('g'); document.getElementById('cwB').onclick = () => addColor('b'); document.getElementById('cwReset').onclick = () => { state.r = state.g = state.b = 0; renderCard(); }; document.getElementById('cwSubmit').onclick = submit; if (state.feedback) document.getElementById('cwFb').innerHTML = ``; } function addColor(c) { const d = c==='r'?{r:80,g:0,b:0}:c==='g'?{r:0,g:80,b:0}:{r:0,g:0,b:80}; state.r=Math.min(255,state.r+d.r); state.g=Math.min(255,state.g+d.g); state.b=Math.min(255,state.b+d.b); renderCard(); } function submit() { const diff = Math.abs(state.r-state.target.rgb.r)+Math.abs(state.g-state.target.rgb.g)+Math.abs(state.b-state.target.rgb.b); if (diff <= 50) { const points = Math.max(50, 200-diff*2); state.score += points; state.matched++; state.feedback = `✨ Perfect! rgb(${state.r},${state.g},${state.b}) ≈ ${state.target.name}`; recordAnswer('css', true, state.target.name); updateHUD(); if (state.matched >= TARGET_ROUNDS) { state.won = true; const stars = state.lives>=3?3:state.lives===2?2:1; setTimeout(() => showWinOverlay(card, level, `Matched ${state.matched} colors!`, stars, state.score), 800); } else { setTimeout(() => { state.round++; state.target = TARGETS[state.round%TARGETS.length]; state.r = state.g = state.b = 0; state.feedback = ''; renderCard(); }, 1200); } } else { state.lives--; state.feedback = `❌ Off by ${diff}. Target: rgb(${state.target.rgb.r},${state.target.rgb.g},${state.target.rgb.b})`; recordAnswer('css', false, state.target.name); updateHUD(); if (state.lives <= 0) { state.over = true; setTimeout(() => showGameOverOverlay(card, level, 'Out of lives!', () => startCssWizard(body, level)), 800); } } renderCard(); } function startGame() { state.started = true; state.target = TARGETS[0]; updateHUD(); renderCard(); } window.currentGameCleanup = () => {}; updateHUD(); makeStartOverlay(level, card, `Mix R/G/B to match the target. Match ${TARGET_ROUNDS} colors!`, startGame); } // ============================================================ // LEVEL 4: JS HUNTER // ============================================================ function startJsHunter(body, level) { body.innerHTML = `
`; const PUZZLES = [ {prompt:'Add two numbers. Fix the bug!',concept:'Arithmetic',buggyCode:'function add(a, b) {\n return a - b;\n}',options:[{code:'return a + b;',correct:true,explain:'✓ Use + to add'},{code:'return a * b;',correct:false,explain:'✗ Multiplies'},{code:'return a / b;',correct:false,explain:'✗ Divides'},{code:'return a b;',correct:false,explain:'✗ Missing operator'}]}, {prompt:'Loop should print 1-5. Fix it!',concept:'For Loop',buggyCode:'for (let i = 0; i < 5; i--) {\n console.log(i);\n}',options:[{code:'i++',correct:true,explain:'✓ Increment to count up'},{code:'i += 5',correct:false,explain:'✗ Jumps by 5'},{code:'i = 0',correct:false,explain:'✗ Infinite loop'},{code:'i *= 2',correct:false,explain:'✗ Wrong direction'}]}, {prompt:'Fix the array syntax!',concept:'Arrays',buggyCode:'let fruits = ["apple" "banana" "cherry"];',options:[{code:'["apple", "banana", "cherry"]',correct:true,explain:'✓ Commas separate items'},{code:'["apple" "banana" "cherry"]',correct:false,explain:'✗ No commas'},{code:'(apple, banana, cherry)',correct:false,explain:'✗ Wrong brackets'},{code:'{apple; banana; cherry}',correct:false,explain:'✗ Wrong syntax'}]}, {prompt:'Fix this if-statement!',concept:'Conditionals',buggyCode:'if x > 10 {\n console.log("big");\n}',options:[{code:'if (x > 10) {',correct:true,explain:'✓ Needs parentheses'},{code:'if [x > 10] {',correct:false,explain:'✗ Wrong brackets'},{code:'when x > 10 {',correct:false,explain:'✗ Not valid JS'},{code:'if x > 10 then',correct:false,explain:'✗ Not JS'}]}, {prompt:'Fix the equality check!',concept:'Equality',buggyCode:'if (x = 5) {\n console.log("five");\n}',options:[{code:'if (x === 5)',correct:true,explain:'✓ === compares values'},{code:'if (x = 5)',correct:false,explain:'✗ = is assignment'},{code:'if (x == 5 =)',correct:false,explain:'✗ Syntax error'},{code:'if (x is 5)',correct:false,explain:'✗ Not valid JS'}]}, {prompt:'Fix the object!',concept:'Objects',buggyCode:'let user = {\n name: "Ada"\n age: 30\n};',options:[{code:'name: "Ada",\nage: 30',correct:true,explain:'✓ Commas separate properties'},{code:'name: "Ada"; age: 30',correct:false,explain:'✗ Wrong separator'},{code:'name = "Ada", age = 30',correct:false,explain:'✗ Use : not ='},{code:'name "Ada" age 30',correct:false,explain:'✗ Missing colons'}]}, {prompt:'Fix this arrow function!',concept:'Arrow Functions',buggyCode:'const double = (n) => n 2;',options:[{code:'const double = (n) => n * 2;',correct:true,explain:'✓ Multiply with *'},{code:'const double = (n) => n + 2;',correct:false,explain:'✗ Adds 2'},{code:'const double = (n) => n ^ 2;',correct:false,explain:'✗ ^ is XOR'},{code:'const double = (n) => 2n;',correct:false,explain:'✗ Missing operator'}]}, ]; const TARGET = 6; let state = { idx: 0, score: 0, lives: 3, correct: 0, won: false, over: false, started: false, feedback: null, selectedOpt: null, shuffled: PUZZLES[0].options.slice() }; const card = document.getElementById('jhCard'); function shuffle(arr) { const a = arr.slice(); for (let i = a.length-1; i > 0; i--) { const j = Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]]; } return a; } function updateHUD() { document.getElementById('jhHUD').innerHTML = makeHUD([`❤️ ${state.lives}`,`⭐ ${state.score}`,`⚙️ ${state.correct}/${TARGET}`,PUZZLES[state.idx].concept]); } function renderCard() { const p = PUZZLES[state.idx]; card.innerHTML = `
🐛

${p.prompt}

// Buggy code:\n${p.buggyCode}

Choose the correct fix:

`; const wrap = document.getElementById('jhOpts'); state.shuffled.forEach((opt, i) => { const btn = document.createElement('button'); btn.className = 'option-btn game-touch'; if (state.selectedOpt === i) btn.classList.add(opt.correct?'correct':'wrong'); if (state.feedback && opt.correct && state.selectedOpt !== i) btn.style.boxShadow = '0 0 0 2px #22c55e'; if (state.feedback) btn.classList.add('disabled'); btn.textContent = opt.code; btn.onclick = () => handleAnswer(i); wrap.appendChild(btn); }); if (state.feedback) document.getElementById('jhFb').innerHTML = ``; } function handleAnswer(i) { if (state.feedback) return; state.selectedOpt = i; const opt = state.shuffled[i]; if (opt.correct) { state.score += 100; state.feedback = opt.explain; state.correct++; recordAnswer('js', true, opt.code); updateHUD(); if (state.correct >= TARGET) { state.won = true; setTimeout(() => { const stars = state.lives>=3?3:state.lives===2?2:1; showWinOverlay(card, level, `Squashed ${state.correct} bugs!`, stars, state.score); }, 1200); } else { setTimeout(() => { state.idx = (state.idx+1)%PUZZLES.length; state.shuffled = shuffle(PUZZLES[state.idx].options); state.feedback = null; state.selectedOpt = null; renderCard(); }, 1500); } } else { if (activeShield) { activeShield = false; toast('Shield saved you!', 'success'); state.feedback = '🛡️ Shield used! Try again.'; state.selectedOpt = null; setTimeout(() => { state.feedback = null; renderCard(); }, 1000); return; } state.lives--; state.feedback = `❌ ${opt.explain}`; recordAnswer('js', false, opt.code); updateHUD(); if (state.lives <= 0) { state.over = true; setTimeout(() => showGameOverOverlay(card, level, 'Out of lives!', () => startJsHunter(body, level)), 1500); } else { setTimeout(() => { state.feedback = null; state.selectedOpt = null; renderCard(); }, 1800); } } renderCard(); } function startGame() { state.started = true; state.shuffled = shuffle(PUZZLES[0].options); updateHUD(); renderCard(); } window.currentGameCleanup = () => {}; updateHUD(); makeStartOverlay(level, card, `Read buggy code, pick the correct fix. Fix ${TARGET} bugs!`, startGame); } // ============================================================ // LEVEL 5: AI PATTERN // ============================================================ function startAiPattern(body, level) { body.innerHTML = `
`; const NODES = [{id:0,color:'#ef4444',label:'🔴'},{id:1,color:'#22c55e',label:'🟢'},{id:2,color:'#3b82f6',label:'🔵'},{id:3,color:'#eab308',label:'🟡'},{id:4,color:'#a855f7',label:'🟣'},{id:5,color:'#06b6d4',label:'🩵'}]; const TARGET_ROUND = 6; let state = { sequence: [], userInput: [], showing: null, phase: 'idle', round: 0, accuracy: 100, score: 0, lives: 3, epoch: 0, won: false, over: false, started: false }; let timers = []; const card = document.getElementById('apCard'); function clearTimers() { timers.forEach(t => clearTimeout(t)); timers = []; } function updateHUD() { document.getElementById('apHUD').innerHTML = makeHUD([`❤️ ${state.lives}`,`⭐ ${state.score}`,`🧠 E${state.epoch}/${TARGET_ROUND}`,`🎯 ${state.accuracy}%`]); } function renderCard() { const phaseText = {training:'🤖 Watch carefully!',input:'🎮 Your turn!',correct:'✅ Correct!',wrong:'❌ Mismatch!',idle:'⏳ Loading...'}[state.phase]; card.innerHTML = `
${phaseText}
Len: ${state.sequence.length} · Inputs: ${state.userInput.length}
Training Progress
`; const nodesWrap = document.getElementById('apNodes'); NODES.forEach(node => { const btn = document.createElement('button'); btn.className = 'pattern-node game-touch'; btn.style.background = node.color; const isActive = state.showing === node.id || (state.phase === 'input' && state.userInput[state.userInput.length-1] === node.id); if (isActive) btn.classList.add('active'); if (state.phase !== 'input') btn.disabled = true; btn.textContent = node.label; btn.onclick = () => handleClick(node.id); nodesWrap.appendChild(btn); }); const seqWrap = document.getElementById('apSeq'); state.sequence.forEach((s, i) => { const d = document.createElement('div'); const filled = i < state.userInput.length; d.style.cssText = `width:24px;height:24px;border-radius:4px;font-size:11px;display:flex;align-items:center;justify-content:center;background:${filled?(state.userInput[i]===s?'#16a34a':'#dc2626'):'#334155'};color:${filled?'#fff':'#94a3b8'}`; d.textContent = filled ? NODES[state.userInput[i]].label : '?'; seqWrap.appendChild(d); }); } function train(seq) { state.phase = 'training'; clearTimers(); renderCard(); seq.forEach((nodeId, i) => { const showTime = 600+i*800; timers.push(setTimeout(() => { state.showing = nodeId; renderCard(); Audio.play('hint'); }, showTime)); timers.push(setTimeout(() => { state.showing = null; renderCard(); }, showTime+500)); }); timers.push(setTimeout(() => { state.phase = 'input'; renderCard(); }, 600+seq.length*800+200)); } function nextRound(r) { const len = 3+r; const newSeq = Array.from({length:len}, () => Math.floor(Math.random()*NODES.length)); state.sequence = newSeq; state.userInput = []; state.epoch++; updateHUD(); train(newSeq); } function handleClick(id) { if (state.phase !== 'input') return; const newInput = [...state.userInput, id]; state.userInput = newInput; const expected = state.sequence[newInput.length-1]; if (id !== expected) { state.phase = 'wrong'; state.accuracy = Math.max(0, state.accuracy-15); state.lives--; recordAnswer('ai', false, 'pattern'); updateHUD(); renderCard(); if (state.lives <= 0) { state.over = true; setTimeout(() => showGameOverOverlay(card, level, 'Training failed!', () => startAiPattern(body, level)), 1000); } else { setTimeout(() => { state.userInput = []; train(state.sequence); }, 1500); } return; } Audio.play('collect'); renderCard(); if (newInput.length === state.sequence.length) { state.phase = 'correct'; const points = state.sequence.length*50; state.score += points; state.round++; recordAnswer('ai', true, 'pattern'); updateHUD(); renderCard(); if (state.round >= TARGET_ROUND) { state.won = true; setTimeout(() => { const stars = state.lives>=3?3:state.lives===2?2:1; showWinOverlay(card, level, `Memorized ${state.round} patterns!`, stars, state.score); }, 1200); } else { setTimeout(() => { state.phase = 'idle'; nextRound(state.round); }, 1500); } } } function startGame() { state.started = true; state.round = 0; state.epoch = 0; updateHUD(); setTimeout(() => nextRound(0), 100); } window.currentGameCleanup = () => clearTimers(); updateHUD(); makeStartOverlay(level, card, `Watch the pattern, then repeat it. ${TARGET_ROUND} epochs!`, startGame); } // ============================================================ // LEVEL 6: VARIABLE VAULT (Maze) // ============================================================ function startVariableHunt(body, level, charEmoji) { body.innerHTML = `
`; const MAZE = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,0,0,0,1,0,0,0,0,0,1,0,0,0,1],[1,0,1,0,1,0,1,1,1,0,1,0,1,0,1],[1,0,1,0,0,0,1,2,1,0,0,0,1,0,1],[1,0,1,1,1,0,1,0,1,1,1,0,1,0,1],[1,0,0,0,1,0,0,0,0,0,1,0,0,0,1],[1,1,1,0,1,1,1,1,1,0,1,1,1,0,1],[1,0,0,0,0,0,0,2,0,0,0,0,0,0,1],[1,0,1,1,1,0,1,1,1,1,1,0,1,1,1],[1,0,1,2,1,0,0,0,0,0,1,0,0,0,1],[1,0,1,0,1,1,1,1,1,0,1,1,1,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,0,0,1],[1,1,1,1,1,0,1,0,1,1,1,1,1,0,1],[1,0,0,0,0,0,1,0,0,0,0,2,0,3,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]; const VARIABLES = [{name:'x',value:'5',desc:'integer'},{name:'name',value:'"Ada"',desc:'string'},{name:'is_ready',value:'True',desc:'boolean'},{name:'pi',value:'3.14',desc:'float'}]; let state = { maze: MAZE.map(r => r.slice()), pos: {x:1,y:1}, collected: [], score: 0, moves: 0, won: false, started: false, feedback: null }; let cellSize = 32; const wrap = document.getElementById('vhWrap'); function calcCellSize() { const vw = window.innerWidth; if (vw >= 768) cellSize = 32; else if (vw >= 480) cellSize = 24; else cellSize = 20; } function updateHUD() { document.getElementById('vhHUD').innerHTML = makeHUD([`⭐ ${state.score}`,`💎 ${state.collected.length}/${VARIABLES.length}`,`👣 ${state.moves}`]); } function render() { calcCellSize(); const fontSize = cellSize < 24 ? '10px' : '14px'; let mazeHTML = ''; state.maze.forEach((row, y) => row.forEach((cell, x) => { const isPlayer = state.pos.x===x&&state.pos.y===y; let bg = '#0f172a', content = ''; if (cell===1) { bg = '#334155'; content = '🧱'; } if (cell===3) { bg = '#b45309'; content = '🚪'; } if (cell===2) { bg = '#155e75'; content = '💎'; } if (isPlayer) content = charEmoji; mazeHTML += `
${content}
`; })); let varsHTML = state.collected.length === 0 ? '

No variables yet

' : state.collected.map(v => `
${v.name} = ${v.value} (${v.desc})
`).join(''); wrap.innerHTML = `
${mazeHTML}

Variables Collected:

${varsHTML}
`; document.getElementById('vhUp').onclick = () => tryMove(0,-1); document.getElementById('vhDown').onclick = () => tryMove(0,1); document.getElementById('vhLeft').onclick = () => tryMove(-1,0); document.getElementById('vhRight').onclick = () => tryMove(1,0); if (state.feedback) document.getElementById('vhFb').innerHTML = `
${state.feedback}
`; } function tryMove(dx, dy) { if (state.won) return; const nx = state.pos.x+dx, ny = state.pos.y+dy; if (ny<0||ny>=state.maze.length||nx<0||nx>=state.maze[0].length) return; const cell = state.maze[ny][nx]; if (cell===1) return; state.moves++; state.pos = {x:nx,y:ny}; if (cell===2) { const v = VARIABLES[state.collected.length]; if (v) { state.collected.push(v); state.score += 100; state.feedback = `✨ ${v.name} = ${v.value} (${v.desc})`; state.maze[ny][nx] = 0; Audio.play('collect'); recordAnswer('variables', true, v.name); updateHUD(); setTimeout(() => { state.feedback = null; render(); }, 2500); } } else if (cell===3) { if (state.collected.length >= VARIABLES.length) { state.won = true; state.score += 500; const stars = state.moves<50?3:state.moves<80?2:1; showWinOverlay(wrap, level, `Escaped in ${state.moves} moves!`, stars, state.score); } else { state.feedback = `🚪 Need all ${VARIABLES.length} variables! (${state.collected.length}/${VARIABLES.length})`; setTimeout(() => { state.feedback = null; render(); }, 2500); } } updateHUD(); render(); } function kd(e) { const k = e.key.toLowerCase(); if (['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d'].includes(k)) { e.preventDefault(); if (k==='arrowup'||k==='w') tryMove(0,-1); if (k==='arrowdown'||k==='s') tryMove(0,1); if (k==='arrowleft'||k==='a') tryMove(-1,0); if (k==='arrowright'||k==='d') tryMove(1,0); } } function startGame() { state.started = true; updateHUD(); render(); window.addEventListener('keydown', kd); window.addEventListener('resize', render); } window.currentGameCleanup = () => { window.removeEventListener('keydown', kd); window.removeEventListener('resize', render); }; updateHUD(); makeStartOverlay(level, wrap, `Collect all ${VARIABLES.length} variables (💎), reach exit (🚪). Arrow keys or D-pad.`, startGame); } // ============================================================ // LEVEL 7: LOOP RUNNER // ============================================================ function startLoopRunner(body, level, charEmoji) { body.innerHTML = `
`; const canvas = document.getElementById('lrCanvas'), ctx = canvas.getContext('2d'); const TARGET_DIST = 1500, groundY = 280; let state = { dist: 0, speed: 4, over: false, won: false, jumps: 0, iter: 0, started: false }; const player = { x: 80, y: 240, w: 26, h: 36, vy: 0, onGround: true }; let obstacles = [], nextObstacleX = 400, animId = null; function updateHUD() { document.getElementById('lrHUD').innerHTML = makeHUD([`🏃 ${Math.floor(state.dist)}/${TARGET_DIST}`,`⚡ ${state.speed.toFixed(1)}`,`🔁 i=${state.iter}`,`⬆️ ${state.jumps}`]); } function jump() { if (player.onGround) { player.vy = -12; player.onGround = false; state.jumps++; Audio.play('jump'); updateHUD(); } } function loop() { if (state.over || state.won) return; const GRAVITY = 0.6; player.vy += GRAVITY; player.y += player.vy; if (player.y + player.h >= groundY) { player.y = groundY - player.h; player.vy = 0; player.onGround = true; } state.speed = Math.min(8, 4 + state.dist/500); state.dist += state.speed; state.iter = Math.floor(state.dist/50); updateHUD(); while (nextObstacleX < state.dist + canvas.width + 100) { const type = Math.random()>0.5?'rock':'bug'; const h = type==='rock'?24:30; const w = type==='rock'?22:30; obstacles.push({x:nextObstacleX,y:groundY-h,w,h,type}); nextObstacleX += 200+Math.random()*200; } obstacles = obstacles.filter(o => o.x > state.dist - canvas.width); const px = player.x + state.dist; for (const o of obstacles) { if (px < o.x+o.w && px+player.w > o.x && player.y < o.y+o.h && player.y+player.h > o.y) { state.over = true; Audio.play('lose'); showGameOverOverlay(document.getElementById('lrWrap'), level, `Hit at ${Math.floor(state.dist)}m`, () => startLoopRunner(body, level, charEmoji)); return; } } if (state.dist >= TARGET_DIST) { state.won = true; const stars = state.jumps<8?3:state.jumps<12?2:1; showWinOverlay(document.getElementById('lrWrap'), level, `Ran ${Math.floor(state.dist)}m, ${state.jumps} jumps!`, stars, Math.floor(state.dist)); return; } render(); animId = requestAnimationFrame(loop); } function render() { const grad = ctx.createLinearGradient(0,0,0,canvas.height); grad.addColorStop(0,'#1a0a0a'); grad.addColorStop(1,'#3a1a1a'); ctx.fillStyle = grad; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.fillStyle = 'rgba(80,40,40,0.6)'; for (let i = 0; i < 8; i++) { const x = (i*200-state.dist*0.2)%(canvas.width+400)-200; ctx.beginPath(); ctx.moveTo(x,200); ctx.lineTo(x+100,100); ctx.lineTo(x+200,200); ctx.closePath(); ctx.fill(); } ctx.fillStyle = '#5a2a1a'; ctx.fillRect(0,groundY,canvas.width,canvas.height-groundY); ctx.fillStyle = '#7a3a2a'; ctx.fillRect(0,groundY,canvas.width,6); ctx.fillStyle = 'rgba(255,200,200,0.15)'; ctx.font = 'bold 80px monospace'; ctx.fillText(`i=${state.iter}`, canvas.width-250, 120); obstacles.forEach(o => { const sx = o.x-state.dist; if (sx<-50||sx>canvas.width+50) return; if (o.type==='rock') { ctx.fillStyle = '#888'; ctx.beginPath(); ctx.moveTo(sx,groundY); ctx.lineTo(sx+o.w/2,groundY-o.h); ctx.lineTo(sx+o.w,groundY); ctx.closePath(); ctx.fill(); } else { ctx.fillStyle = '#ff4444'; ctx.beginPath(); ctx.arc(sx+o.w/2,o.y+o.h/2,o.w/2,0,Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.fillRect(sx+8,o.y+6,5,5); ctx.fillStyle = '#000'; ctx.fillRect(sx+10,o.y+8,2,2); } }); ctx.fillStyle = '#3b82f6'; ctx.fillRect(player.x,player.y,player.w,player.h); ctx.font = '20px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(charEmoji, player.x+player.w/2, player.y+player.h-2); ctx.textAlign = 'left'; ctx.fillStyle = '#1e40af'; if (player.onGround) { const step = Math.floor(state.dist/10)%2; ctx.fillRect(player.x+4,player.y+player.h,6,6); ctx.fillRect(player.x+16,player.y+player.h-(step?4:0),6,6); } else { ctx.fillRect(player.x+4,player.y+player.h-4,6,6); ctx.fillRect(player.x+16,player.y+player.h-4,6,6); } ctx.fillStyle = '#fff'; ctx.font = 'bold 16px monospace'; ctx.fillText(`Distance: ${Math.floor(state.dist)}/${TARGET_DIST}`, 12, 28); ctx.fillText(`Speed: ${state.speed.toFixed(1)}`, 12, 50); ctx.fillText(`for(i=0; i<${state.iter}; i++)`, 12, 72); } function kd(e) { const k = e.key.toLowerCase(); if (k===' '||k==='arrowup'||k==='w') { e.preventDefault(); jump(); } } function startGame() { state.started = true; document.getElementById('lrJump').style.display = 'block'; document.getElementById('lrJump').addEventListener('pointerdown', e => { e.preventDefault(); jump(); }); window.addEventListener('keydown', kd); updateHUD(); animId = requestAnimationFrame(loop); } window.currentGameCleanup = () => { if (animId) cancelAnimationFrame(animId); window.removeEventListener('keydown', kd); }; updateHUD(); makeStartOverlay(level, document.getElementById('lrWrap'), `Reach ${TARGET_DIST}m. Tap JUMP or Space!`, startGame); } // ============================================================ // LEVEL 8: FUNCTION DEFENDER // ============================================================ function startFunctionDefender(body, level) { body.innerHTML = `
`; const canvas = document.getElementById('fdCanvas'), ctx = canvas.getContext('2d'); const PATH = [{x:0,y:100},{x:200,y:100},{x:200,y:200},{x:400,y:200},{x:400,y:80},{x:600,y:80},{x:600,y:250},{x:750,y:250}]; const TURRET_TYPES = [{name:'shoot()',cost:50,damage:1,range:100,cooldown:30,color:'#2563eb'},{name:'blast()',cost:100,damage:3,range:80,cooldown:60,color:'#ea580c'},{name:'freeze()',cost:80,damage:0.5,range:70,cooldown:40,color:'#0891b2'}]; const TARGET_WAVE = 5; let state = { coins: 150, hp: 10, wave: 0, over: false, won: false, started: false, selectedTurret: 0, enemiesLeft: 0 }; let enemies = [], bullets = [], turrets = []; let waveRef = 0, enemiesToSpawn = 0, spawnTimer = 0, betweenWaves = 0, nextId = 0, animId = null; function updateHUD() { document.getElementById('fdHUD').innerHTML = makeHUD([`🏠 HP:${state.hp}`,`🪙 ${state.coins}`,`🌊 ${state.wave}/${TARGET_WAVE}`,`👾 ${state.enemiesLeft}`]); } function renderTurretBar() { const bar = document.getElementById('fdTurrets'); bar.innerHTML = ''; TURRET_TYPES.forEach((t, i) => { const btn = document.createElement('button'); btn.className = 'turret-btn game-touch'+(state.selectedTurret===i?' selected':''); if (state.coins < t.cost) btn.disabled = true; btn.style.background = state.selectedTurret===i?t.color:'#334155'; btn.innerHTML = `${t.name}🪙${t.cost}`; btn.onclick = () => { state.selectedTurret = i; renderTurretBar(); }; bar.appendChild(btn); }); } function dist(a, b) { return Math.hypot(a.x-b.x, a.y-b.y); } function pathPos(t) { let total = 0; for (let i = 0; i < PATH.length-1; i++) total += dist(PATH[i],PATH[i+1]); let target = t*total; for (let i = 0; i < PATH.length-1; i++) { const segLen = dist(PATH[i],PATH[i+1]); if (target <= segLen) { const r = target/segLen; return {x:PATH[i].x+(PATH[i+1].x-PATH[i].x)*r, y:PATH[i].y+(PATH[i+1].y-PATH[i].y)*r}; } target -= segLen; } return PATH[PATH.length-1]; } function findTFromPos(x, y) { let minDist = Infinity, bestT = 0; for (let t = 0; t <= 1; t += 0.005) { const p = pathPos(t); const d = Math.hypot(p.x-x, p.y-y); if (d < minDist) { minDist = d; bestT = t; } } return minDist > 20 ? null : bestT; } function placeTurret(x, y) { if (state.selectedTurret === null) return; const type = TURRET_TYPES[state.selectedTurret]; if (state.coins < type.cost) return; for (let t = 0; t <= 1; t += 0.02) { const p = pathPos(t); if (Math.hypot(p.x-x, p.y-y) < 30) return; } for (const t of turrets) { if (Math.hypot(t.x-x, t.y-y) < 30) return; } turrets.push({id:nextId++,x,y,name:type.name,cooldown:0,range:type.range,damage:type.damage,color:type.color}); state.coins -= type.cost; Audio.play('coin'); updateHUD(); renderTurretBar(); } function startNextWave() { waveRef++; state.wave = waveRef; enemiesToSpawn = 4+waveRef*2; spawnTimer = 0; state.enemiesLeft = enemiesToSpawn; updateHUD(); } function loop() { if (state.over || state.won) return; if (enemiesToSpawn > 0) { spawnTimer--; if (spawnTimer <= 0) { const speed = 0.4+waveRef*0.1; enemies.push({id:nextId++,x:PATH[0].x,y:PATH[0].y,hp:2+waveRef,maxHp:2+waveRef,speed}); enemiesToSpawn--; spawnTimer = 60; } } else if (enemies.length === 0 && waveRef < TARGET_WAVE) { if (betweenWaves === 0) { betweenWaves = 180; state.coins += 50; updateHUD(); renderTurretBar(); } else { betweenWaves--; if (betweenWaves === 0) startNextWave(); } } else if (enemies.length === 0 && waveRef >= TARGET_WAVE) { state.won = true; const stars = state.hp>=8?3:state.hp>=5?2:1; showWinOverlay(document.getElementById('fdWrap'), level, `Base HP: ${state.hp}`, stars, state.coins); return; } enemies.forEach(e => { const t = findTFromPos(e.x, e.y); if (t === null) return; const nt = Math.min(1, t+e.speed*0.001); const np = pathPos(nt); e.x = np.x; e.y = np.y; if (nt >= 1) { state.hp--; updateHUD(); if (state.hp <= 0) { state.over = true; showGameOverOverlay(document.getElementById('fdWrap'), level, 'Base destroyed!', () => startFunctionDefender(body, level)); return; } } }); enemies = enemies.filter(e => { const t = findTFromPos(e.x, e.y); return t !== null && t < 1 && e.hp > 0; }); state.enemiesLeft = enemies.length + enemiesToSpawn; updateHUD(); turrets.forEach(t => { if (t.cooldown > 0) t.cooldown--; let bestEnemy = null, bestT = -1; enemies.forEach(e => { if (dist(t, e) <= t.range) { const et = findTFromPos(e.x, e.y); if (et !== null && et > bestT) { bestT = et; bestEnemy = e; } } }); if (bestEnemy && t.cooldown === 0) { bullets.push({x:t.x,y:t.y,tx:bestEnemy.x,ty:bestEnemy.y,dmg:t.damage,speed:6}); t.cooldown = TURRET_TYPES.find(tt => tt.name === t.name).cooldown; Audio.play('shoot'); } }); bullets.forEach(b => { const dx = b.tx-b.x, dy = b.ty-b.y; const len = Math.hypot(dx, dy); if (len < b.speed) { enemies.forEach(e => { if (Math.hypot(e.x-b.tx, e.y-b.ty) < 20) { e.hp -= b.dmg; if (e.hp <= 0) { state.coins += 15; updateHUD(); renderTurretBar(); Audio.play('coin'); } } }); b.x = -100; } else { b.x += (dx/len)*b.speed; b.y += (dy/len)*b.speed; } }); bullets = bullets.filter(b => b.x > -50); render(); animId = requestAnimationFrame(loop); } function render() { ctx.fillStyle = '#1a2a2a'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.strokeStyle = 'rgba(255,255,255,0.05)'; for (let x = 0; x < canvas.width; x += 40) { ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += 40) { ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(canvas.width,y); ctx.stroke(); } ctx.strokeStyle = '#3a4a3a'; ctx.lineWidth = 24; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); PATH.forEach((p,i) => { if (i===0) ctx.moveTo(p.x,p.y); else ctx.lineTo(p.x,p.y); }); ctx.stroke(); ctx.strokeStyle = '#5a6a5a'; ctx.lineWidth = 2; ctx.setLineDash([6,6]); ctx.beginPath(); PATH.forEach((p,i) => { if (i===0) ctx.moveTo(p.x,p.y); else ctx.lineTo(p.x,p.y); }); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = '#22c55e'; ctx.fillRect(PATH[0].x-4, PATH[0].y-30, 4, 30); ctx.font = '14px sans-serif'; ctx.fillText('🚩', PATH[0].x-4, PATH[0].y-32); const end = PATH[PATH.length-1]; ctx.fillStyle = '#ef4444'; ctx.fillRect(end.x-4, end.y-30, 30, 30); ctx.fillText('🏠', end.x, end.y-10); turrets.forEach(t => { ctx.fillStyle = t.color; ctx.beginPath(); ctx.arc(t.x, t.y, 14, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.font = '8px monospace'; ctx.fillText(t.name, t.x-14, t.y+28); ctx.strokeStyle = 'rgba(255,255,255,0.15)'; ctx.beginPath(); ctx.arc(t.x, t.y, t.range, 0, Math.PI*2); ctx.stroke(); }); enemies.forEach(e => { ctx.fillStyle = '#ef4444'; ctx.beginPath(); ctx.arc(e.x, e.y, 10, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#000'; ctx.fillRect(e.x-12, e.y-18, 24, 4); ctx.fillStyle = '#22c55e'; ctx.fillRect(e.x-12, e.y-18, 24*(e.hp/e.maxHp), 4); }); ctx.fillStyle = '#fbbf24'; bullets.forEach(b => { ctx.beginPath(); ctx.arc(b.x, b.y, 3, 0, Math.PI*2); ctx.fill(); }); ctx.fillStyle = '#fff'; ctx.font = 'bold 14px monospace'; ctx.fillText(`Wave ${waveRef}/${TARGET_WAVE}`, 12, 24); ctx.fillText(`HP: ${state.hp}`, 12, 44); ctx.fillText(`Coins: ${state.coins}`, 12, 64); if (betweenWaves > 0) { ctx.fillStyle = '#fbbf24'; ctx.fillText(`Next wave in ${Math.ceil(betweenWaves/60)}s`, canvas.width/2-80, 30); } } function handleClick(e) { if (state.over || state.won) return; const rect = canvas.getBoundingClientRect(); const x = (e.clientX-rect.left)*(800/rect.width); const y = (e.clientY-rect.top)*(400/rect.height); placeTurret(x, y); } function startGame() { state.started = true; updateHUD(); renderTurretBar(); betweenWaves = 120; canvas.addEventListener('click', handleClick); animId = requestAnimationFrame(loop); } window.currentGameCleanup = () => { if (animId) cancelAnimationFrame(animId); canvas.removeEventListener('click', handleClick); }; updateHUD(); renderTurretBar(); makeStartOverlay(level, document.getElementById('fdWrap'), `Place turrets to defend! Survive ${TARGET_WAVE} waves. Tap canvas to place.`, startGame); } // ============================================================ // LEVEL 9: CONDITIONAL QUEST // ============================================================ function startConditionalQuest(body, level) { body.innerHTML = `
`; const SCENARIOS = [ {title:'The Locked Gate',context:'Gate opens only if player has key.',code:'if (has_key) {\n open_gate();\n} else {\n // ?\n}',variable:'has_key = false',choices:[{label:'Force open',correct:false,explain:'✗ Gate is steel'},{label:'Find another route',correct:true,explain:'✓ Go around without key'},{label:'Wait forever',correct:false,explain:'✗ Infinite loop!'}]}, {title:'Health Potion',context:'Potion heals only if HP < 50.',code:'if (hp < 50) {\n drink_potion();\n}',variable:'hp = 30',choices:[{label:'Drink potion',correct:true,explain:'✓ HP 30 < 50 — heal!'},{label:'Skip potion',correct:false,explain:'✗ Need healing'},{label:'Sell potion',correct:false,explain:'✗ Heal now!'}]}, {title:'Torch or Dark Path',context:'Take lit or dark path.',code:'if (has_torch) {\n take_dark_path();\n} else {\n take_lit_path();\n}',variable:'has_torch = true',choices:[{label:'Dark path (torch)',correct:true,explain:'✓ Torch lights way'},{label:'Lit path',correct:false,explain:'✗ Waste torch'},{label:'Sit and cry',correct:false,explain:'✗ No time!'}]}, {title:'Monster Battle',context:'Fight if level high enough.',code:'if (player_level >= monster_level) {\n fight();\n} else {\n flee();\n}',variable:'player=3, monster=5',choices:[{label:'Fight anyway',correct:false,explain:'✗ Underleveled — flee!'},{label:'Flee to safety',correct:true,explain:'✓ 3 < 5 — live!'},{label:'Beg for mercy',correct:false,explain:'✗ No mercy'}]}, {title:'Treasure Chest',context:'Open only if not trapped.',code:'if (!is_trapped) {\n open_chest();\n} else {\n disarm_trap();\n}',variable:'is_trapped = true',choices:[{label:'Open chest',correct:false,explain:'✗ Trapped! Boom!'},{label:'Disarm trap first',correct:true,explain:'✓ Disarm, then open'},{label:'Walk away',correct:false,explain:'✗ Free loot!'}]}, {title:'Day or Night',context:'Vampires attack at night.',code:'if (is_night) {\n hide();\n} else {\n explore();\n}',variable:'is_night = true',choices:[{label:'Explore freely',correct:false,explain:'✗ Vampires!'},{label:'Hide until dawn',correct:true,explain:'✓ Hide at night'},{label:'Become vampire',correct:false,explain:'✗ Not how it works'}]}, ]; const TARGET = 5; let state = { idx: 0, hp: 3, score: 0, correctCount: 0, won: false, over: false, started: false, feedback: null, selectedChoice: null }; const card = document.getElementById('cqCard'); function updateHUD() { document.getElementById('cqHUD').innerHTML = makeHUD([`❤️ ${state.hp}`,`⭐ ${state.score}`,`🕳️ ${state.correctCount}/${TARGET}`]); } function renderCard() { const s = SCENARIOS[state.idx]; card.innerHTML = `
🕳️

${s.title}

${s.context}

Current state:
${s.variable}
${s.code}

Choose your action:

`; const wrap = document.getElementById('cqOpts'); s.choices.forEach((c, i) => { const btn = document.createElement('button'); btn.className = 'option-btn game-touch'; if (state.selectedChoice === i) btn.classList.add(c.correct?'correct':'wrong'); if (state.feedback) btn.classList.add('disabled'); btn.textContent = c.label; btn.onclick = () => handleChoice(i); wrap.appendChild(btn); }); if (state.feedback) document.getElementById('cqFb').innerHTML = ``; } function handleChoice(i) { if (state.feedback) return; state.selectedChoice = i; const s = SCENARIOS[state.idx]; const c = s.choices[i]; if (c.correct) { state.score += 100; state.feedback = `✅ ${c.explain}`; state.correctCount++; recordAnswer('conditionals', true, c.label); updateHUD(); if (state.correctCount >= TARGET) { state.won = true; setTimeout(() => { const stars = state.hp>=3?3:state.hp===2?2:1; showWinOverlay(card, level, `${state.correctCount} smart decisions!`, stars, state.score); }, 1500); } else { setTimeout(() => { state.idx = (state.idx+1)%SCENARIOS.length; state.feedback = null; state.selectedChoice = null; renderCard(); }, 1800); } } else { if (activeShield) { activeShield = false; toast('Shield saved you!', 'success'); state.feedback = '🛡️ Shield used!'; state.selectedChoice = null; setTimeout(() => { state.feedback = null; renderCard(); }, 1000); return; } state.hp--; state.feedback = `❌ ${c.explain}`; recordAnswer('conditionals', false, c.label); updateHUD(); if (state.hp <= 0) { state.over = true; setTimeout(() => showGameOverOverlay(card, level, 'Lost in caves!', () => startConditionalQuest(body, level)), 1500); } else { setTimeout(() => { state.feedback = null; state.selectedChoice = null; renderCard(); }, 2000); } } renderCard(); } function startGame() { state.started = true; updateHUD(); renderCard(); } window.currentGameCleanup = () => {}; updateHUD(); makeStartOverlay(level, card, `Read code, check variable, choose action. ${TARGET} correct!`, startGame); } // ============================================================ // LEVEL 10: FINAL BOSS // ============================================================ function startFinalBoss(body, level) { body.innerHTML = `
`; const QUESTIONS = [ {topic:'Python',q:'Valid Python variable name?',options:['2name','name_2','name-2','name 2'],correct:1,explain:"Can't start with digit or have spaces"}, {topic:'HTML',q:'Largest heading tag?',options:['
','','

','
'],correct:2,explain:'

is largest'}, {topic:'CSS',q:'Make text bold?',options:['font-style: bold','text: bold','font-weight: bold','bold: true'],correct:2,explain:'Use font-weight: bold'}, {topic:'JavaScript',q:'What does === check?',options:['Only value','Only type','Value AND type','Assignment'],correct:2,explain:'Strict equality'}, {topic:'AI',q:'What adjusts during training?',options:['Colors','Weights','HTML','Files'],correct:1,explain:'Weights and biases'}, {topic:'Variables',q:'x = 5; x = x + 3; x is?',options:['5','3','8','53'],correct:2,explain:'5 + 3 = 8'}, {topic:'Loops',q:'for i in range(5) runs?',options:['4','5','6','0'],correct:1,explain:'0,1,2,3,4 — five times'}, {topic:'Functions',q:"Function's main benefit?",options:['More memory','Reuses code','Slower','Adds bugs'],correct:1,explain:'Code reuse!'}, {topic:'Conditionals',q:'if (7 > 5 and 7 < 10)?',options:['True','False','Error','7'],correct:0,explain:'Both true → True'}, {topic:'Master',q:'Which runs in browsers natively?',options:['Python','C++','JavaScript','Java'],correct:2,explain:'JS is native to browsers'}, ]; let state = { qIdx: 0, bossHp: 100, playerHp: 100, combo: 0, won: false, over: false, started: false, feedback: null, selected: null, shakeBoss: false, shakePlayer: false }; let particles = [], animId = null; const card = document.getElementById('fbCard'); const BOSS_MAX = 100, PLAYER_MAX = 100, DMG_TO_BOSS = 18, DMG_TO_PLAYER = 22; function updateHUD() { document.getElementById('fbHUD').innerHTML = makeHUD([`🧑‍💻 ${state.playerHp}/100`,`👑 ${state.bossHp}/100`,`🔥 x${state.combo}`]); } function renderCard() { const q = QUESTIONS[state.qIdx]; card.innerHTML = `
${q.topic}Q${(state.qIdx%QUESTIONS.length)+1}

${q.q}

`; const wrap = document.getElementById('fbOpts'); q.options.forEach((opt, i) => { const btn = document.createElement('button'); btn.className = 'option-btn game-touch'; if (state.selected === i) btn.classList.add(i===q.correct?'correct':'wrong'); if (state.feedback && i === q.correct) btn.style.boxShadow = '0 0 0 2px #22c55e'; if (state.feedback) btn.classList.add('disabled'); btn.textContent = opt; btn.onclick = () => handleAnswer(i); wrap.appendChild(btn); }); if (state.feedback) document.getElementById('fbFb').innerHTML = ``; startCanvas(); } function startCanvas() { const canvas = document.getElementById('fbCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); if (animId) cancelAnimationFrame(animId); function render() { ctx.clearRect(0,0,canvas.width,canvas.height); const grad = ctx.createRadialGradient(canvas.width/2,canvas.height/2,0,canvas.width/2,canvas.height/2,canvas.width); grad.addColorStop(0,'#3a1a1a'); grad.addColorStop(1,'#0a0a0a'); ctx.fillStyle = grad; ctx.fillRect(0,0,canvas.width,canvas.height); const bossX = canvas.width/2, bossY = 130; const shake = state.shakeBoss?(Math.random()-0.5)*12:0; ctx.fillStyle = `rgba(220,38,38,${0.2+0.1*Math.sin(Date.now()/200)})`; ctx.beginPath(); ctx.arc(bossX+shake, bossY, 90, 0, Math.PI*2); ctx.fill(); ctx.font = '80px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('👑', bossX+shake, bossY+25); ctx.fillStyle = '#ef4444'; ctx.font = '24px sans-serif'; ctx.fillText('👁️', bossX-20+shake, bossY+50); ctx.fillText('👁️', bossX+20+shake, bossY+50); ctx.textAlign = 'left'; ctx.fillStyle = '#000'; ctx.fillRect(canvas.width/2-100, 30, 200, 16); ctx.fillStyle = '#dc2626'; ctx.fillRect(canvas.width/2-100, 30, 200*(state.bossHp/BOSS_MAX), 16); ctx.fillStyle = '#fff'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`CODE MASTER ${state.bossHp}/${BOSS_MAX}`, canvas.width/2, 42); ctx.textAlign = 'left'; ctx.fillStyle = '#000'; ctx.fillRect(canvas.width/2-100, canvas.height-50, 200, 16); ctx.fillStyle = '#22c55e'; ctx.fillRect(canvas.width/2-100, canvas.height-50, 200*(state.playerHp/PLAYER_MAX), 16); ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.fillText(`YOU ${state.playerHp}/${PLAYER_MAX}`, canvas.width/2, canvas.height-38); ctx.textAlign = 'left'; const pShake = state.shakePlayer?(Math.random()-0.5)*10:0; ctx.font = '40px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🧑‍💻', canvas.width/2+pShake, canvas.height-60); ctx.textAlign = 'left'; particles.forEach(p => { p.x += p.vx; p.y += p.vy; p.vy += 0.2; p.life--; ctx.globalAlpha = Math.max(0, p.life/30); ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, Math.PI*2); ctx.fill(); ctx.globalAlpha = 1; }); particles = particles.filter(p => p.life > 0); animId = requestAnimationFrame(render); } render(); } function spawnParticles(x, y, color, count) { for (let i = 0; i < count; i++) particles.push({x,y,vx:(Math.random()-0.5)*8,vy:(Math.random()-0.5)*8-2,life:25+Math.random()*10,color}); } function handleAnswer(i) { if (state.feedback) return; state.selected = i; const q = QUESTIONS[state.qIdx]; const canvas = document.getElementById('fbCanvas'); const cx = canvas.width/2; if (i === q.correct) { state.combo++; const dmg = DMG_TO_BOSS+Math.min(10, state.combo*2); state.bossHp = Math.max(0, state.bossHp-dmg); state.shakeBoss = true; spawnParticles(cx, 130, '#fbbf24', 25); state.feedback = `✅ ${q.explain} (-${dmg} HP!)`; recordAnswer('master', true, q.q); Audio.play('win'); updateHUD(); setTimeout(() => state.shakeBoss = false, 400); if (state.bossHp <= 0) { state.won = true; setTimeout(() => { const stars = state.playerHp>=80?3:state.playerHp>=40?2:1; showWinOverlay(card, level, `🎉 CodeQuest Complete! 🎉`, stars, state.bossHp); }, 1500); } else { setTimeout(() => { state.feedback = null; state.selected = null; state.qIdx = (state.qIdx+1)%QUESTIONS.length; renderCard(); }, 2200); } } else { if (activeShield) { activeShield = false; toast('Shield saved you!', 'success'); state.feedback = '🛡️ Shield used!'; state.selected = null; setTimeout(() => { state.feedback = null; renderCard(); }, 1000); return; } state.combo = 0; state.playerHp = Math.max(0, state.playerHp-DMG_TO_PLAYER); state.shakePlayer = true; spawnParticles(cx, canvas.height-60, '#ef4444', 20); state.feedback = `❌ Correct: "${q.options[q.correct]}". ${q.explain} (-${DMG_TO_PLAYER} HP!)`; recordAnswer('master', false, q.q); Audio.play('error'); updateHUD(); setTimeout(() => state.shakePlayer = false, 400); if (state.playerHp <= 0) { state.over = true; setTimeout(() => showGameOverOverlay(card, level, 'Code Master laughs...', () => startFinalBoss(body, level)), 1500); } else { setTimeout(() => { state.feedback = null; state.selected = null; state.qIdx = (state.qIdx+1)%QUESTIONS.length; renderCard(); }, 2800); } } renderCard(); } function startGame() { state.started = true; updateHUD(); renderCard(); } window.currentGameCleanup = () => { if (animId) cancelAnimationFrame(animId); }; updateHUD(); makeStartOverlay(level, card, `Answer correctly to damage boss. Wrong = you take damage. Build combos!`, startGame); } // ---- PWA SERVICE WORKER (inline registration for offline) ---- if ('serviceWorker' in navigator) { const swCode = ` const CACHE = 'codequest-v2'; self.addEventListener('install', e => { self.skipWaiting(); }); self.addEventListener('activate', e => { e.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', e => { e.respondWith( caches.match(e.request).then(r => r || fetch(e.request).then(resp => { if (e.request.method === 'GET' && e.request.url.startsWith(self.location.origin)) { const clone = resp.clone(); caches.open(CACHE).then(c => c.put(e.request, clone)); } return resp; }).catch(() => r)) ); }); `; const blob = new Blob([swCode], { type: 'application/javascript' }); const swUrl = URL.createObjectURL(blob); navigator.serviceWorker.register(swUrl).catch(() => {}); } // ---- INIT ---- checkDaily(); checkAchievements(); if (S.settings.music) Audio.startMusic(); document.addEventListener('click', function initAudio() { Audio.init(); if (S.settings.music) Audio.startMusic(); document.removeEventListener('click', initAudio); }, { once: true }); window.addEventListener('resize', () => { if (activeLevel === null) render(); }); render();