Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>AI+ L&D 2D Escape Quiz</title> | |
| <style> | |
| :root { color-scheme: dark; } | |
| html, body { height:100%; margin:0; background:#0b1020; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; } | |
| canvas { display:block; margin:0 auto; background: radial-gradient(1200px 800px at 50% 40%, #0a0f28 0%, #070d22 60%, #050a1c 100%); image-rendering: pixelated; } | |
| #hud, #hint, #overlay, #quiz, #win { position: fixed; left:0; right:0; margin:0 auto; color:#eaf2ff; } | |
| #hud { top:12px; text-align:center; font-weight:700; text-shadow:0 2px 10px rgba(0,0,0,.6); pointer-events:none; } | |
| #hint { bottom: 12px; text-align:center; color:#bcd3ff; text-shadow:0 2px 10px rgba(0,0,0,.6); } | |
| #hint.pulse { animation: pulse 1.4s ease-in-out infinite; } | |
| @keyframes pulse { 0%{opacity:.6} 50%{opacity:1} 100%{opacity:.6} } | |
| .panel-wrap { display:flex; align-items:center; justify-content:center; top:0; bottom:0; } | |
| .panel { max-width: 840px; margin: 16px; padding: 26px; background: rgba(18,24,52,.72); border:1px solid rgba(180,200,255,.25); border-radius:18px; backdrop-filter: blur(6px); box-shadow: 0 20px 60px rgba(0,0,0,.45); } | |
| h1 { margin:0 0 .25rem; font-size: 1.9rem; letter-spacing:.3px; } | |
| h2 { margin:.25rem 0 .75rem; font-size: 1.25rem; color:#cfe3ff; } | |
| p, li { line-height:1.55; } | |
| .btn { display:inline-block; margin-top: 12px; padding: 12px 18px; border-radius: 12px; background: linear-gradient(135deg,#6aa4ff,#7ef0ff); color:#071225; font-weight:700; border:none; cursor:pointer; } | |
| .btn.secondary { background: transparent; border:1px solid rgba(180,200,255,.35); color:#eaf2ff; } | |
| #quiz.hidden, #overlay.hidden, #win.hidden { display:none; } | |
| .choices { display:grid; gap:10px; margin-top:10px; } | |
| .choice { text-align:left; padding:10px 12px; border-radius:12px; border:1px solid rgba(180,200,255,.3); background:rgba(255,255,255,.05); cursor:pointer; } | |
| .choice.correct { border-color:#36e29f; background: rgba(54,226,159,.16); } | |
| .choice.wrong { border-color:#ff6b6b; background: rgba(255,107,107,.16); } | |
| #footerLink { margin-top:10px; font-size:.9rem; opacity:.9; } | |
| a { color:#8ad4ff; } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="game" width="1152" height="648" aria-label="AI+ L&D 2D Escape Quiz"></canvas> | |
| <div id="hud">Unlock terminals: <span id="progress">0</span>/<span id="total">0</span></div> | |
| <div id="hint">Press <b>Enter the Maze</b> to begin</div> | |
| <!-- Start Panel --> | |
| <div id="overlay" class="panel-wrap"> | |
| <div class="panel"> | |
| <h1>AI+ L&D 2D Escape Quiz</h1> | |
| <p>Navigate a neon maze inspired by the white paper: <em>AI-driven personalization, predictive analytics, fair teacher evaluation, and ethical data practices</em>.<br/>Unlock glowing terminals by answering questions. Open the exit by completing them all.</p> | |
| <h2>How to Play</h2> | |
| <ul style="text-align:left; margin:0 auto; max-width:640px"> | |
| <li><b>W / A / S / D</b> or <b>Arrow Keys</b> to move.</li> | |
| <li>Walk onto a glowing terminal and press <b>E</b> to answer.</li> | |
| <li>Unlock all terminals to open the exit, then step through it.</li> | |
| </ul> | |
| <div id="footerLink"> | |
| Source: <a target="_blank" href="https://www.linkedin.com/pulse/artificial-intelligence-learning-development-michael-lively-welze/"> | |
| Artificial Intelligence in Learning & Development – Michael Lively</a> | |
| </div> | |
| <button id="startBtn" class="btn">Enter the Maze</button> | |
| </div> | |
| </div> | |
| <!-- Quiz Panel --> | |
| <div id="quiz" class="panel-wrap hidden"> | |
| <div class="panel"> | |
| <h2 id="qTitle">Challenge</h2> | |
| <div id="qText"></div> | |
| <div class="choices" id="choices"></div> | |
| <div id="explain" class="meta"></div> | |
| <div style="display:flex; gap:10px; justify-content:flex-end; margin-top:12px;"> | |
| <button id="closeQuiz" class="btn secondary">Close</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Win Panel --> | |
| <div id="win" class="panel-wrap hidden"> | |
| <div class="panel"> | |
| <h1>Exit Unlocked</h1> | |
| <p>You’ve mastered personalization, predictive analytics, teacher growth, and ethical data. Step through the exit portal!</p> | |
| <button id="resume" class="btn">Resume</button> | |
| </div> | |
| </div> | |
| <script> | |
| /* ======================== GAME DATA ======================== */ | |
| const QUESTIONS = [ | |
| { | |
| key:'personalization', | |
| title:'Personalization at the Core', | |
| text:'What best describes AI-driven personalization in the classroom?', | |
| choices:[ | |
| 'Delivering the same lesson faster to all students', | |
| 'Adjusting pace, difficulty, and materials to each learner in real time', | |
| 'Replacing teachers with chatbots for all instruction', | |
| 'Focusing solely on standardized tests for progress' | |
| ], | |
| correct:1, | |
| explain:'Personalization adapts pace, difficulty, and content to each learner, ensuring challenge for advanced students and support for those who need it.' | |
| }, | |
| { | |
| key:'predictive', | |
| title:'Predictive Analytics & Foresight', | |
| text:'How do predictive analytics most usefully assist teachers?', | |
| choices:[ | |
| 'By guessing final grades at the end of term only', | |
| 'By forecasting disengagement and surfacing timely, targeted interventions', | |
| 'By removing the need for any formative assessment', | |
| 'By replacing lesson planning entirely' | |
| ], | |
| correct:1, | |
| explain:'Predictive models surface early warning signs (e.g., declining engagement) and suggest targeted interventions so teachers can act in time.' | |
| }, | |
| { | |
| key:'teacherPD', | |
| title:'Personalized Programs for Teachers', | |
| text:'Which is a key benefit of AI for teacher professional development?', | |
| choices:[ | |
| 'One-size-fits-all workshops for consistency', | |
| 'Real-time coaching and adaptive learning pathways tied to classroom practice', | |
| 'Eliminating peer collaboration', | |
| 'Annual compliance videos only' | |
| ], | |
| correct:1, | |
| explain:'AI enables adaptive PD—simulations, micro-lessons, and feedback aligned to each teacher’s goals and classroom data.' | |
| }, | |
| { | |
| key:'evaluation', | |
| title:'Fixing Teacher Evaluation', | |
| text:'Why can AI-supported evaluation be fairer than traditional systems?', | |
| choices:[ | |
| 'It relies on a single standardized measure', | |
| 'It hides how decisions are made', | |
| 'It integrates many signals and emphasizes growth rather than punitive ratings', | |
| 'It replaces human judgment with automation' | |
| ], | |
| correct:2, | |
| explain:'A holistic, data-rich model highlights strengths, identifies needs, and supports growth instead of punishing with one score.' | |
| }, | |
| { | |
| key:'ethics', | |
| title:'Ethics & Data Protection', | |
| text:'Which approach best captures ethical use of learner/teacher data?', | |
| choices:[ | |
| 'Collect everything without telling users', | |
| 'Use encryption, minimize collection, ensure transparency, allow human override', | |
| 'Share raw data with vendors by default', | |
| 'Keep models secret and un-audited' | |
| ], | |
| correct:1, | |
| explain:'Ethical practice requires transparency, consent/minimization, encryption, access controls, bias checks, and humans in the loop.' | |
| } | |
| ]; | |
| // Level legend: # = wall, . = floor, S = spawn, G = terminal, E = exit (door) | |
| const LEVEL = [ | |
| "#####################", | |
| "#S .# . G .# ..#", | |
| "#.###.###.###.###E.#", | |
| "#.. G .# .. # ...##", | |
| "###.###.#.###.#.#..#", | |
| "#.... #.#...#.#.#..#", | |
| "#.### #.### #.#.#..#", | |
| "#.. # #...# #.. #..#", | |
| "### # ### # #####..#", | |
| "#.. # .# # . #..#", | |
| "# ### # # ### # ####", | |
| "# # .# .# .#", | |
| "#####################" | |
| ]; | |
| const TILE = 48; // pixels per tile | |
| const ROWS = LEVEL.length; | |
| const COLS = LEVEL[0].length; | |
| const W = COLS * TILE; | |
| const H = ROWS * TILE; | |
| // Canvas + context | |
| const canvas = document.getElementById('game'); | |
| canvas.width = W; canvas.height = H; | |
| const ctx = canvas.getContext('2d'); | |
| // HUD & panels | |
| const overlay = document.getElementById('overlay'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const hintEl = document.getElementById('hint'); | |
| const hudProgress = document.getElementById('progress'); | |
| const hudTotal = document.getElementById('total'); | |
| const quizWrap = document.getElementById('quiz'); | |
| const qTitle = document.getElementById('qTitle'); | |
| const qText = document.getElementById('qText'); | |
| const choicesEl = document.getElementById('choices'); | |
| const explainEl = document.getElementById('explain'); | |
| const closeQuiz = document.getElementById('closeQuiz'); | |
| const winWrap = document.getElementById('win'); | |
| const resumeBtn = document.getElementById('resume'); | |
| const state = { | |
| started:false, | |
| solved:new Set(), | |
| exitOpen:false, | |
| player:{x:0,y:0,px:0,py:0, speed:180}, // px/py in pixels | |
| terminals:[], // {x,y,key,index,active} | |
| exit:{x:0,y:0,open:false} | |
| }; | |
| // Parse level | |
| for (let r=0;r<ROWS;r++){ | |
| for (let c=0;c<COLS;c++){ | |
| const ch = LEVEL[r][c]; | |
| if (ch==='S'){ state.player.x=c; state.player.y=r; } | |
| if (ch==='G'){ state.terminals.push({x:c,y:r,key:null,index:0,active:true}); } | |
| if (ch==='E'){ state.exit.x=c; state.exit.y=r; } | |
| } | |
| } | |
| state.player.px = state.player.x*TILE + TILE/2; | |
| state.player.py = state.player.y*TILE + TILE/2; | |
| // Assign questions round-robin | |
| state.terminals.forEach((t,i)=>{ | |
| t.key = QUESTIONS[i % QUESTIONS.length].key; | |
| t.index = i % QUESTIONS.length; | |
| }); | |
| hudTotal.textContent = String(state.terminals.length); | |
| // Input | |
| const keys = {}; | |
| window.addEventListener('keydown', e=>{ keys[e.code]=true; if(e.code==='KeyE') tryInteract();}); | |
| window.addEventListener('keyup', e=> keys[e.code]=false); | |
| // Start | |
| startBtn.addEventListener('click', ()=>{ | |
| overlay.classList.add('hidden'); | |
| state.started = true; | |
| hint("Use WASD/Arrows to move • E to interact at terminals", true); | |
| }); | |
| // Quiz handling | |
| function openQuiz(terminal){ | |
| const q = QUESTIONS.find(x=>x.key===terminal.key) || QUESTIONS[terminal.index]; | |
| qTitle.textContent = q.title; | |
| qText.textContent = q.text; | |
| explainEl.textContent = ''; | |
| choicesEl.innerHTML = ''; | |
| q.choices.forEach((c,i)=>{ | |
| const btn = document.createElement('button'); | |
| btn.className = 'choice'; | |
| btn.textContent = c; | |
| btn.onclick = ()=>{ | |
| if(i===q.correct){ | |
| btn.classList.add('correct'); | |
| explainEl.textContent = q.explain; | |
| terminal.active = false; | |
| state.solved.add(q.key); | |
| hudProgress.textContent = String(state.solved.size); | |
| setTimeout(()=>{ quizWrap.classList.add('hidden'); checkWin(); }, 850); | |
| } else { | |
| btn.classList.add('wrong'); | |
| explainEl.textContent = 'Try again: ' + q.explain; | |
| } | |
| }; | |
| choicesEl.appendChild(btn); | |
| }); | |
| quizWrap.classList.remove('hidden'); | |
| } | |
| closeQuiz.addEventListener('click', ()=> quizWrap.classList.add('hidden')); | |
| resumeBtn.addEventListener('click', ()=> winWrap.classList.add('hidden')); | |
| function tryInteract(){ | |
| if(!state.started) return; | |
| // If on a terminal tile, open quiz | |
| const {cx,cy} = playerCell(); | |
| const term = state.terminals.find(t=>t.active && t.x===cx && t.y===cy); | |
| if (term){ | |
| openQuiz(term); | |
| return; | |
| } | |
| // If on exit tile and open, show win | |
| if (state.exitOpen && cx===state.exit.x && cy===state.exit.y){ | |
| winWrap.classList.remove('hidden'); | |
| } | |
| } | |
| function checkWin(){ | |
| const remaining = state.terminals.filter(t=>t.active).length; | |
| if (remaining===0){ | |
| state.exitOpen = true; | |
| hint("Exit is open! Find the green portal.", true); | |
| } | |
| } | |
| /* ======================== RENDERING ======================== */ | |
| function draw(){ | |
| ctx.clearRect(0,0,W,H); | |
| // Background grid glow | |
| drawGridGlow(); | |
| // Draw level tiles | |
| for (let r=0;r<ROWS;r++){ | |
| for (let c=0;c<COLS;c++){ | |
| const ch = LEVEL[r][c]; | |
| const x = c*TILE, y = r*TILE; | |
| if (ch === '#') { | |
| drawWall(x,y); | |
| } else { | |
| drawFloor(x,y); | |
| } | |
| } | |
| } | |
| // Draw terminals | |
| state.terminals.forEach(t=>{ | |
| if (!t.active) { drawInactiveTerminal(t.x,t.y); } | |
| else { drawActiveTerminal(t.x,t.y); } | |
| }); | |
| // Draw exit | |
| drawExit(state.exit.x, state.exit.y, state.exitOpen); | |
| // Draw player | |
| drawPlayer(state.player.px, state.player.py); | |
| // Debug / hints | |
| if (!state.started){ | |
| hint("Press Enter the Maze to begin", true); | |
| } | |
| } | |
| function drawGridGlow(){ | |
| const step = TILE; | |
| ctx.save(); | |
| ctx.globalAlpha = .06; | |
| ctx.strokeStyle = '#6ba3ff'; | |
| ctx.lineWidth = 1; | |
| for (let x=0; x<=W; x+=step){ | |
| ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); | |
| } | |
| for (let y=0; y<=H; y+=step){ | |
| ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| } | |
| function drawWall(x,y){ | |
| const g = ctx.createLinearGradient(x,y,x,y+TILE); | |
| g.addColorStop(0, '#1d2a5a'); | |
| g.addColorStop(1, '#111c43'); | |
| ctx.fillStyle = g; | |
| ctx.fillRect(x,y,TILE,TILE); | |
| // edge | |
| ctx.strokeStyle = 'rgba(255,255,255,.06)'; | |
| ctx.strokeRect(x+.5,y+.5,TILE-1,TILE-1); | |
| } | |
| function drawFloor(x,y){ | |
| const g = ctx.createLinearGradient(x,y,x+TILE,y+TILE); | |
| g.addColorStop(0, '#0e1738'); | |
| g.addColorStop(1, '#0b122f'); | |
| ctx.fillStyle = g; | |
| ctx.fillRect(x,y,TILE,TILE); | |
| // soft bloom dots | |
| ctx.fillStyle = 'rgba(80,120,255,.08)'; | |
| ctx.beginPath(); ctx.arc(x+TILE*0.3, y+TILE*0.3, 3, 0, Math.PI*2); ctx.fill(); | |
| ctx.beginPath(); ctx.arc(x+TILE*0.75, y+TILE*0.7, 2.5, 0, Math.PI*2); ctx.fill(); | |
| } | |
| function drawActiveTerminal(cx,cy){ | |
| const x = cx*TILE + TILE/2, y = cy*TILE + TILE/2; | |
| // glow | |
| ctx.save(); | |
| ctx.shadowBlur = 18; ctx.shadowColor = '#63ccff'; | |
| ctx.fillStyle = '#2df0ff'; | |
| ctx.beginPath(); ctx.arc(x,y,9,0,Math.PI*2); ctx.fill(); | |
| ctx.restore(); | |
| // ring | |
| ctx.strokeStyle = 'rgba(99,204,255,.6)'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); ctx.arc(x,y,14,0,Math.PI*2); ctx.stroke(); | |
| } | |
| function drawInactiveTerminal(cx,cy){ | |
| const x = cx*TILE + TILE/2, y = cy*TILE + TILE/2; | |
| ctx.fillStyle = '#3c8b92'; | |
| ctx.beginPath(); ctx.arc(x,y,8,0,Math.PI*2); ctx.fill(); | |
| ctx.strokeStyle = 'rgba(60,139,146,.6)'; ctx.lineWidth=2; | |
| ctx.beginPath(); ctx.arc(x,y,13,0,Math.PI*2); ctx.stroke(); | |
| } | |
| function drawExit(cx,cy,open){ | |
| const x = cx*TILE, y = cy*TILE; | |
| const g = ctx.createLinearGradient(x,y,x+TILE,y+TILE); | |
| if (open) { g.addColorStop(0, '#21d08a'); g.addColorStop(1, '#0c8f60'); } | |
| else { g.addColorStop(0, '#165d42'); g.addColorStop(1, '#0a3b29'); } | |
| ctx.fillStyle = g; ctx.fillRect(x,y,TILE,TILE); | |
| ctx.strokeStyle = open ? 'rgba(40,255,160,.8)' : 'rgba(80,180,140,.6)'; | |
| ctx.lineWidth = 3; | |
| ctx.strokeRect(x+4, y+4, TILE-8, TILE-8); | |
| } | |
| function drawPlayer(px,py){ | |
| // shadow | |
| ctx.fillStyle = 'rgba(0,0,0,.35)'; | |
| ctx.beginPath(); ctx.ellipse(px, py+6, 8, 4, 0, 0, Math.PI*2); ctx.fill(); | |
| // body | |
| const g = ctx.createRadialGradient(px-4,py-4,2, px,py,14); | |
| g.addColorStop(0, '#8ad4ff'); | |
| g.addColorStop(1, '#3b79ff'); | |
| ctx.fillStyle = g; | |
| ctx.beginPath(); ctx.arc(px,py,10,0,Math.PI*2); ctx.fill(); | |
| // face-line | |
| ctx.strokeStyle = 'rgba(255,255,255,.8)'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); ctx.arc(px,py,6, Math.PI*.05, Math.PI*.95); ctx.stroke(); | |
| } | |
| /* ======================== GAME LOOP ======================== */ | |
| let last = performance.now(); | |
| function loop(now){ | |
| const dt = Math.min((now-last)/1000, 0.033); | |
| last = now; | |
| update(dt); | |
| draw(); | |
| requestAnimationFrame(loop); | |
| } | |
| requestAnimationFrame(loop); | |
| function update(dt){ | |
| if (!state.started) return; | |
| const p = state.player; | |
| const speed = (keys['ShiftLeft'] || keys['ShiftRight']) ? p.speed*1.5 : p.speed; | |
| let dx=0, dy=0; | |
| if (keys['KeyW'] || keys['ArrowUp']) dy -= speed*dt; | |
| if (keys['KeyS'] || keys['ArrowDown']) dy += speed*dt; | |
| if (keys['KeyA'] || keys['ArrowLeft']) dx -= speed*dt; | |
| if (keys['KeyD'] || keys['ArrowRight']) dx += speed*dt; | |
| // attempt move with collision | |
| tryMove(dx, 0); | |
| tryMove(0, dy); | |
| } | |
| function isWallAt(px,py){ | |
| const c = Math.floor(px / TILE); | |
| const r = Math.floor(py / TILE); | |
| if (r<0||r>=ROWS||c<0||c>=COLS) return true; | |
| return LEVEL[r][c] === '#'; | |
| } | |
| function tryMove(dx,dy){ | |
| const p = state.player; | |
| const nx = p.px + dx; | |
| const ny = p.py + dy; | |
| // collision radius | |
| const rad = 12; | |
| // test 4 corners | |
| const testPoints = [ | |
| [nx - rad, ny - rad], | |
| [nx + rad, ny - rad], | |
| [nx - rad, ny + rad], | |
| [nx + rad, ny + rad] | |
| ]; | |
| for (const [tx,ty] of testPoints){ | |
| if (isWallAt(tx,ty)) return; // abort move on collision | |
| } | |
| p.px = nx; p.py = ny; | |
| } | |
| function playerCell(){ | |
| return { cx: Math.floor(state.player.px / TILE), | |
| cy: Math.floor(state.player.py / TILE) }; | |
| } | |
| /* ======================== UX HELPERS ======================== */ | |
| function hint(text, pulse=false){ | |
| hintEl.textContent = text || ''; | |
| hintEl.classList.toggle('pulse', !!pulse); | |
| } | |
| /* ======================== INTERACTION ======================== */ | |
| function near(a,b){ return a.cx===b.x && a.cy===b.y; } | |
| </script> | |
| </body> | |
| </html> | |