Traditional spelling drills can become repetitive for young ESL learners. To make spelling practice more engaging and interactive, I created a customizable HTML spelling game using HTML, CSS, and JavaScript.
This project is designed for teachers who want a fun classroom activity that combines movement, vocabulary recall, sound effects, and gamification.
The game can be customized easily by changing vocabulary words, images, sounds, and gameplay settings.
PART 1 — How to Play the Game
🎮 Main Gameplay
Students control a character and move up or down to collect the correct floating letters across the screen.
Each correct letter fills one spelling box. Wrong letters reduce hearts or HP. Once the whole word is completed correctly, the player earns a point and proceeds to the next vocabulary word.
✨ Main Features
- Solo Mode
- Team VS Mode
- Character Selection Screen
- Floating Letter Mechanics
- Background Music and Sound Effects
- Editable Vocabulary Settings
- Image-Based Vocabulary Questions
- Save and Load JSON Lesson Files
- Mobile-Friendly Controls
- Adjustable Speed and Timer
📚 Educational Benefits
- Improves spelling recognition
- Encourages active vocabulary recall
- Increases classroom engagement
- Supports visual learners through images
- Promotes healthy classroom competition
📝 How to Play
- Open the spelling game.
- Go to the Settings menu.
- Add vocabulary words and hints.
- Select Solo or Team Mode.
- Choose characters.
- Collect the correct letters in order.
- Complete the word before time runs out.
💡 Classroom Tips
You can organize vocabulary by category such as:
- Animals
- Food
- School Supplies
- Colors
- Transportation
- Phonics Patterns
For beginners, use shorter words and slower speed settings. For advanced learners, increase the game speed and use longer vocabulary words.
PART 2 — AI Prompt Used to Generate the Game
Below is the detailed AI prompt used to generate the same style, mechanics, and overall design of the spelling game.
The prompt includes:
- Gameplay mechanics
- Visual design requirements
- Educational features
- Teacher customization tools
- Sound and animation effects
🤖 Complete AI Prompt
📁 Required Files
- background image.webp
- background music.mp3
- correct answer.mp3
- wrong.mp3
- next word.mp3
- ting.mp3
- chime.mp3
- pikachu.png
- mario.gif
- Luigi.gif
- Sir Axiom.gif
- character3.gif
💻 Complete HTML Game Code
Paste your complete spelling game HTML code below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Teacher Khen Learning Hub [Spelling Game]</title>
<style>
:root { --bg: #F0F7EE; --dark: #776871; --accent: rgba(196, 215, 242, 0.8); --sec: #91A8A4; --win: #27ae60; }
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
body, html { margin: 0; padding: 0; width: 100%; height: 100%; font-family: Arial, Helvetica, sans-serif; overflow: hidden; }
.main-bg { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: url('background image.webp') no-repeat center center; background-size: cover; z-index: -2; }
.overlay-tint { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.2); z-index: -1; }
.menu-header { text-align: center; margin-bottom: 30px; z-index: 5; }
.main-title-gothic { font-size: 80px; color: white; text-shadow: 4px 4px 10px black; margin: 0; font-weight: normal; }
.sub-title-clean { font-size: 24px; color: #FFD700; text-shadow: 2px 2px 4px black; margin-top: -10px; font-weight: bold; }
.screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; flex-direction: column; align-items: center; justify-content: center; z-index: 10; }
.active { display: flex !important; }
.menu-btn { background: var(--dark); border: 3px solid white; padding: 12px 25px; font-size: 18px; color: white; border-radius: 50px; cursor: pointer; margin: 5px; font-weight: bold; position: relative; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
.shine { position: absolute; top: 0; left: -100%; width: 50%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); transition: 0.5s; }
.menu-btn:hover .shine { left: 150%; }
#central-scoreboard { position: fixed; top: 35%; left: 50%; transform: translate(-50%, -50%); display: none; background: rgba(255,255,255,0.9); padding: 5px 25px; border-radius: 50px; border: 4px solid var(--dark); z-index: 1000; backdrop-filter: blur(5px); box-shadow: 0 0 20px rgba(0,0,0,0.5); }
.score-num { font-weight: 900; font-size: 40px; color: var(--dark); margin: 0 15px; }
#gameplay-screen { flex-direction: row; display: none; background: transparent; }
.game-half { position: relative; flex: 1; height: 100%; border-right: 2px solid rgba(255,255,255,0.3); display: flex; flex-direction: column; overflow: hidden; }
.hud { background: rgba(0,0,0,0.5); color: white; padding: 10px 20px; display: flex; align-items: center; min-height: 50px; }
.hud-t1 { justify-content: space-between; }
.hud-t2 { justify-content: flex-end; gap: 20px; }
.single-score-ui { font-size: 24px; font-weight: bold; background: var(--win); padding: 2px 12px; border-radius: 10px; border: 2px solid white; }
.question-area { height: 35%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.4); padding: 10px; border-bottom: 3px solid white; backdrop-filter: blur(3px); position: relative; }
.question-text { font-size: 30px; font-weight: bold; color: white; text-align: center; text-shadow: 2px 2px 4px black; }
.question-img { max-height: 100px; border: 3px solid white; border-radius: 8px; margin-bottom: 5px; }
.boxes { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin-top: 10px; }
.box { width: 42px; height: 42px; background: rgba(255,255,255,0.9); border: 2px solid var(--dark); display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 26px; border-radius: 8px; color: var(--dark); }
.completion-layer { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); display: none; z-index: 200; }
.final-word { font-size: 48px; color: #FFD700; font-weight: 900; text-shadow: 2px 2px 10px rgba(0,0,0,0.8); }
.lane { height: 65%; position: relative; width: 100%; background: transparent; overflow: hidden; }
.fixed-controls { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 15px; z-index: 50; }
.arrow-btn { width: 80px; height: 80px; background: rgba(255,255,255,0.2); border: 3px solid white; border-radius: 15px; font-size: 40px; cursor: pointer; color: white; display: flex; align-items: center; justify-content: center; }
.char-container { position: absolute; left: 100px; z-index: 100; pointer-events: none; }
.paused { filter: brightness(1.3) drop-shadow(0 0 20px gold); }
.char { width: 110px; height: 110px; object-fit: contain; }
/* Comic Sans Font for Letters */
.letter { position: absolute; width: 65px; height: 65px; background: radial-gradient(circle, #fff, var(--sec)); border: 3px solid var(--dark); color: var(--dark); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 38px; font-weight: bold; z-index: 60; box-shadow: 0 4px 8px rgba(0,0,0,0.3); font-family: "Comic Sans MS", "Comic Sans", cursive; }
.settings-content { background: white; width: 95%; height: 95%; padding: 25px; border-radius: 20px; border: 8px solid var(--dark); overflow-y: auto; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.s-box { background: #eef2f7; padding: 12px; border-radius: 12px; display: flex; justify-content: space-between; align-items: center; font-weight: bold; color: var(--dark); border: 1px solid #ccc; }
.editor-table { width: 100%; border-collapse: collapse; background: white; margin-top: 20px; }
.editor-table th, .editor-table td { border: 1px solid #ddd; padding: 10px; text-align: center; }
.game-over-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); color: white; z-index: 500; display: none; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
</style>
</head>
<body onpaste="handlePaste(event)">
<div class="main-bg"></div>
<div class="overlay-tint"></div>
<audio id="bgMusic" loop><source src="background music.mp3" type="audio/mpeg"></audio>
<audio id="sfxCorrect"><source src="correct answer.mp3" type="audio/mpeg"></audio>
<audio id="sfxWrong"><source src="wrong.mp3" type="audio/mpeg"></audio>
<audio id="sfxNext"><source src="next word.mp3" type="audio/mpeg"></audio>
<audio id="sfxTing"><source src="ting.mp3" type="audio/mpeg"></audio>
<audio id="sfxChime"><source src="chime.mp3" type="audio/mpeg"></audio>
<div id="central-scoreboard">
<span id="t1-score-top" class="score-num">0</span>
<span style="font-size:32px; color:var(--dark);">-</span>
<span id="t2-score-top" class="score-num">0</span>
</div>
<div id="main-menu" class="screen active">
<div class="menu-header">
<h1 class="main-title-gothic">𝔖𝔭𝔢𝔩𝔩𝔦𝔫𝔤 𝔊𝔞𝔪𝔢</h1>
<div class="sub-title-clean">Teacher Khen Learning Hub</div>
</div>
<button class="menu-btn" onclick="playSfx('sfxTing'); showScreen('mode-screen')">START GAME<div class="shine"></div></button>
<button class="menu-btn" onclick="playSfx('sfxTing'); showScreen('settings-screen')" style="background:var(--sec)">SETTINGS<div class="shine"></div></button>
</div>
<div id="mode-screen" class="screen">
<h2 style="color:white; text-shadow: 2px 2px black;">SELECT MODE</h2>
<button class="menu-btn" onclick="setMode('single')">SOLO MODE<div class="shine"></div></button>
<button class="menu-btn" onclick="setMode('team')">TEAM VS MODE<div class="shine"></div></button>
</div>
<div id="char-screen" class="screen">
<h2 id="char-title-display" style="color:white; text-shadow: 2px 2px black; font-size: 40px;">PLAYER 1 SELECT</h2>
<div id="char-list" style="display:flex; gap:15px; margin-bottom:20px; flex-wrap:wrap; justify-content:center;"></div>
<button class="menu-btn" onclick="confirmChar()">CONFIRM CHOICE<div class="shine"></div></button>
</div>
<div id="settings-screen" class="screen">
<div class="menu-header" style="margin-bottom:10px">
<h1 class="main-title-gothic" style="font-size:40px">𝔖𝔭𝔢𝔩𝔩𝔦𝔫𝔤 𝔊𝔞𝔪𝔢</h1>
<div class="sub-title-clean" style="font-size:16px">Teacher Khen Learning Hub</div>
</div>
<div class="settings-content">
<div class="grid">
<div class="s-box">Timer: <input type="number" id="set-timer" value="60" style="width:70px"></div>
<div class="s-box">Speed: <select id="set-speed"><option value="3">Slow</option><option value="5" selected>Normal</option><option value="8">Fast</option></select></div>
<div class="s-box">Sync Qs: <input type="checkbox" id="set-sync" checked></div>
<div class="s-box">HP Hearts: <input type="number" id="set-hp" value="5" style="width:50px"></div>
<div class="s-box">Music: <input type="checkbox" id="set-music" checked onchange="updateAudio()"></div>
<div class="s-box">Vol: <input type="range" id="set-vol" min="0" max="1" step="0.1" value="0.5" onchange="updateAudio()"></div>
<div class="s-box">SFX: <input type="checkbox" id="set-sfx" checked></div>
<div class="s-box" style="background:#e74c3c; color:white;">Clear App: <button onclick="clearAllData()" style="padding:5px 10px; cursor:pointer; font-weight:bold;">ERASE DATA</button></div>
</div>
<div style="margin: 20px 0; display:flex; gap:10px;">
<button class="menu-btn" style="font-size:14px; background:#3498db" onclick="saveToFile()">💾 SAVE FILE</button>
<button class="menu-btn" style="font-size:14px; background:#9b59b6" onclick="document.getElementById('f-in').click()">📂 LOAD FILE</button>
<input type="file" id="f-in" style="display:none" onchange="loadFromFile(event)">
</div>
<table class="editor-table">
<thead><tr><th>Hint / Image</th><th>Answer</th><th>Del</th></tr></thead>
<tbody id="q-body"></tbody>
</table>
<button class="menu-btn" style="font-size:14px; background:#C4D7F2; color:#776871; margin-top:15px;" onclick="addRow()">+ ADD WORD</button>
<button id="final-save-btn" class="menu-btn" style="background:var(--win); width:100%; margin-top:20px; height: 60px;" onclick="saveAndStart()">SAVE & START</button>
</div>
</div>
<div id="gameplay-screen" class="screen">
<div id="t1-side" class="game-half">
<div class="hud hud-t1">
<div id="t1-hp" style="font-size:22px"></div>
<div style="display:flex; align-items:center; gap:20px;">
<div id="t1-single-score" class="single-score-ui" style="display:none">0</div>
<div id="t1-clock" style="font-size:24px; font-weight:bold;">60</div>
</div>
</div>
<div class="question-area" id="t1-qarea">
<div id="t1-hint"></div>
<div id="t1-boxes" class="boxes"></div>
<div id="t1-next-layer" class="completion-layer">
<button class="menu-btn" style="background:var(--win); padding: 10px 15px; font-size: 14px;" onclick="proceedNext(0)">NEXT ➔</button>
</div>
</div>
<div id="t1-lane" class="lane">
<div class="fixed-controls">
<button class="arrow-btn" onmousedown="startMove(0,-1)" onmouseup="stopMove(0)" ontouchstart="startMove(0,-1)" ontouchend="stopMove(0)">▲</button>
<button class="arrow-btn" onmousedown="startMove(0,1)" onmouseup="stopMove(0)" ontouchstart="startMove(0,1)" ontouchend="stopMove(0)">▼</button>
</div>
<div id="t1-cont" class="char-container" style="top:40%"><img src="" id="t1-char" class="char"></div>
</div>
<div id="t1-over" class="game-over-layer"><h1>GAME OVER</h1><h2 id="t1-final-score"></h2><button class="menu-btn" onclick="resetSpecificTeam(0)">PLAY AGAIN</button><button class="menu-btn" style="background:#e74c3c" onclick="location.reload()">EXIT</button></div>
</div>
<div id="t2-side" class="game-half" style="display:none">
<div class="hud hud-t2">
<div id="t2-clock" style="font-size:24px; font-weight:bold;">60</div><div id="t2-hp" style="font-size:22px"></div>
</div>
<div class="question-area" id="t2-qarea">
<div id="t2-hint"></div>
<div id="t2-boxes" class="boxes"></div>
<div id="t2-next-layer" class="completion-layer">
<button class="menu-btn" style="background:var(--win); padding: 10px 15px; font-size: 14px;" onclick="proceedNext(1)">NEXT ➔</button>
</div>
</div>
<div id="t2-lane" class="lane">
<div class="fixed-controls">
<button class="arrow-btn" onmousedown="startMove(1,-1)" onmouseup="stopMove(1)" ontouchstart="startMove(1,-1)" ontouchend="stopMove(1)">▲</button>
<button class="arrow-btn" onmousedown="startMove(1,1)" onmouseup="stopMove(1)" ontouchstart="startMove(1,1)" ontouchend="stopMove(1)">▼</button>
</div>
<div id="t2-cont" class="char-container" style="top:40%"><img src="" id="t2-char" class="char"></div>
</div>
<div id="t2-over" class="game-over-layer"><h1>GAME OVER</h1><h2 id="t2-final-score"></h2><button class="menu-btn" onclick="resetSpecificTeam(1)">PLAY AGAIN</button><button class="menu-btn" style="background:#e74c3c" onclick="location.reload()">EXIT</button></div>
</div>
</div>
<script>
const CHARS = ['pikachu.png', 'mario.gif', 'Luigi.gif', 'Sir Axiom.gif', 'character3.gif'];
const ALPHA_UP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const ALPHA_LOW = "abcdefghijklmnopqrstuvwxyz";
let state = { mode:'single', pIndex:0, selected:[null, null], questions:[{q:'Fruit', a:'Apple', img:null}], timer:60, speed:5, sync:true, music:true, vol:0.5, sfx:true, hp:5 };
let game = { active:false, teams:[{hp:5, score:0, time:60, qIdx:0, typed:"", qList:[], alive:false, dy:0, letters:[], isPaused: false},{hp:5, score:0, time:60, qIdx:0, typed:"", qList:[], alive:false, dy:0, letters:[], isPaused: false}], lastFocus: null };
function playSfx(id) {
if(state.sfx) {
const s = document.getElementById(id);
if(!s) return;
s.volume = (id === 'sfxChime') ? 1.0 : state.vol;
s.currentTime = 0; s.play().catch(()=>{});
}
}
async function handlePaste(e) {
if(!document.getElementById('settings-screen').classList.contains('active')) return;
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
for (let item of items) {
if (item.type.indexOf("image") !== -1) {
const reader = new FileReader();
reader.onload = async (ev) => {
const idx = game.lastFocus !== null ? game.lastFocus : state.questions.length - 1;
state.questions[idx].img = ev.target.result; renderTable(); autoSave();
};
reader.readAsDataURL(item.getAsFile());
}
}
}
function renderTable() {
const b = document.getElementById('q-body'); b.innerHTML = '';
state.questions.forEach((q, i) => {
const tr = document.createElement('tr');
tr.innerHTML = `<td><input type="text" value="${q.q}" onfocus="game.lastFocus=${i}" oninput="state.questions[${i}].q=this.value;autoSave()">${q.img ? `<br><img src="${q.img}" style="height:40px;">` : ''}</td>
<td><input type="text" value="${q.a}" onfocus="game.lastFocus=${i}" oninput="state.questions[${i}].a=this.value;autoSave()"></td>
<td><button class="menu-btn" style="padding:5px 10px; background:#e74c3c" onclick="state.questions.splice(${i},1);renderTable();autoSave()">X</button></td>`;
b.appendChild(tr);
});
}
function addRow() { state.questions.push({q:'', a:'', img:null}); renderTable(); autoSave(); }
function autoSave() { try { localStorage.setItem('teacherKhen_Spelling_Pro', JSON.stringify(state)); } catch(e) {} }
function autoLoad() { const s = localStorage.getItem('teacherKhen_Spelling_Pro'); if(s) { state = JSON.parse(s); renderTable(); syncUI(); } }
function syncUI() {
document.getElementById('set-timer').value = state.timer; document.getElementById('set-speed').value = state.speed;
document.getElementById('set-sync').checked = state.sync; document.getElementById('set-hp').value = state.hp || 5;
document.getElementById('set-music').checked = state.music; document.getElementById('set-vol').value = state.vol;
document.getElementById('set-sfx').checked = state.sfx;
}
function saveAndStart() {
state.timer = parseInt(document.getElementById('set-timer').value) || 60;
state.speed = parseInt(document.getElementById('set-speed').value) || 5;
state.sync = document.getElementById('set-sync').checked;
state.hp = parseInt(document.getElementById('set-hp').value) || 5;
state.sfx = document.getElementById('set-sfx').checked;
autoSave(); showScreen('main-menu');
}
function showScreen(id){
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(id).classList.add('active');
if(id === 'gameplay-screen') {
document.getElementById('central-scoreboard').style.display = (state.mode === 'team') ? 'flex' : 'none';
document.getElementById('t1-single-score').style.display = (state.mode === 'team') ? 'none' : 'block';
} else { document.getElementById('central-scoreboard').style.display = 'none'; }
}
function setMode(m){
state.mode=m; state.pIndex=0; state.selected=[null, null];
document.getElementById('char-title-display').innerText = "PLAYER 1 SELECT";
renderChars(); showScreen('char-screen');
}
function renderChars(){
const l = document.getElementById('char-list'); l.innerHTML = '';
CHARS.forEach(c => {
const i = document.createElement('img'); i.src = c; i.style.width = '100px';
i.style.border = (state.selected[state.pIndex]===c?'6px solid var(--win)':'2px solid white');
i.style.borderRadius = "15px"; i.style.padding = "5px"; i.style.background = "rgba(255,255,255,0.3)";
i.onclick = () => { playSfx('sfxChime'); state.selected[state.pIndex]=c; renderChars(); };
l.appendChild(i);
});
}
function confirmChar(){
if(!state.selected[state.pIndex]) return;
playSfx('sfxTing');
if(state.mode==='team' && state.pIndex===0) {
state.pIndex=1;
document.getElementById('char-title-display').innerText = "PLAYER 2 SELECT";
renderChars();
}
else { showScreen('gameplay-screen'); startGame(); }
}
function startGame(){
game.active = true;
document.getElementById('t2-side').style.display = (state.mode === 'team') ? 'flex' : 'none';
initTeam(0); if(state.mode==='team') initTeam(1);
updateAudio(); tick(); startClock();
spawnLoop(0); if(state.mode==='team') spawnLoop(1);
}
function initTeam(idx){
const t = game.teams[idx]; t.hp=state.hp; t.score=0; t.time=state.timer; t.qIdx=0; t.typed=""; t.alive=true; t.letters=[]; t.isPaused = false;
t.qList = [...state.questions];
if(!state.sync && idx===1) t.qList.sort(()=>Math.random()-0.8); else t.qList.sort(()=>Math.random()-0.5);
document.getElementById(`t${idx+1}-score-top`).innerText = '0';
if(idx===0) document.getElementById('t1-single-score').innerText = '0';
document.getElementById(`t${idx+1}-char`).src = state.selected[idx];
document.getElementById(`t${idx+1}-over`).style.display = 'none';
document.getElementById(`t${idx+1}-next-layer`).style.display = 'none';
updateHUD(idx); nextQ(idx);
}
function updateHUD(idx){
const hp = document.getElementById(`t${idx+1}-hp`); hp.innerHTML = '';
for(let i=0; i<game.teams[idx].hp; i++) hp.innerHTML += '❤️';
document.getElementById(`t${idx+1}-clock`).innerText = game.teams[idx].time;
}
function nextQ(idx){
const t = game.teams[idx];
if(t.qIdx >= t.qList.length) { endGame(idx); return; }
const q = t.qList[t.qIdx], hint = document.getElementById(`t${idx+1}-hint`);
hint.innerHTML = q.img ? `<img src="${q.img}" class="question-img">` : `<div class="question-text">${q.q}</div>`;
const b = document.getElementById(`t${idx+1}-boxes`); b.innerHTML = '';
for(let i=0; i<q.a.length; i++) b.innerHTML += '<div class="box"></div>';
}
function spawnLoop(idx){
if(!game.active || !game.teams[idx].alive) return;
if(!game.teams[idx].isPaused){
const lane = document.getElementById(`t${idx+1}-lane`);
const el = document.createElement('div'); el.className = 'letter';
const targetWord = game.teams[idx].qList[game.teams[idx].qIdx].a;
const nextChar = targetWord[game.teams[idx].typed.length];
if (Math.random() < 0.35) {
el.innerText = nextChar;
} else {
const pool = (nextChar === nextChar.toUpperCase() && nextChar !== nextChar.toLowerCase()) ? ALPHA_UP : ALPHA_LOW;
el.innerText = pool[Math.floor(Math.random()*pool.length)];
}
const startX = lane.offsetWidth; const startY = Math.random() * (lane.offsetHeight - 70);
el.style.left = startX + 'px'; el.style.top = startY + 'px';
lane.appendChild(el);
game.teams[idx].letters.push({ dom: el, x: startX, y: startY, char: el.innerText });
}
// ADJUSTED: Wait longer between spawns to give more space for the character to move
setTimeout(() => spawnLoop(idx), 1800);
}
function tick(){
if(!game.active) return;
game.teams.forEach((t, i) => {
if(!t.alive) return;
const cont = document.getElementById(`t${i+1}-cont`);
if(!t.isPaused && t.dy !== 0){
let top = cont.offsetTop + t.dy;
cont.style.top = Math.max(0, Math.min(top, cont.parentElement.offsetHeight - 110)) + 'px';
}
const charRect = cont.getBoundingClientRect();
for(let j=t.letters.length-1; j>=0; j--){
const letObj = t.letters[j];
if(!t.isPaused) letObj.x -= state.speed;
letObj.dom.style.left = letObj.x + 'px';
const letRect = letObj.dom.getBoundingClientRect();
if(!t.isPaused && letRect.left < charRect.right && letRect.right > charRect.left && letRect.top < charRect.bottom && letRect.bottom > charRect.top){
const target = t.qList[t.qIdx].a;
if(letObj.char === target[t.typed.length]){
playSfx('sfxCorrect');
document.getElementById(`t${i+1}-boxes`).children[t.typed.length].innerText = letObj.char;
t.typed += letObj.char;
if(t.typed === target){
t.isPaused = true; cont.classList.add('paused');
document.getElementById(`t${i+1}-boxes`).innerHTML = `<div class="final-word">${target}</div>`;
playSfx('sfxNext'); t.score++;
document.getElementById(`t${i+1}-score-top`).innerText = t.score;
if(i===0) document.getElementById('t1-single-score').innerText = t.score;
document.getElementById(`t${i+1}-next-layer`).style.display = 'flex';
}
} else { playSfx('sfxWrong'); t.hp--; updateHUD(i); if(t.hp <= 0) endGame(i); }
letObj.dom.remove(); t.letters.splice(j, 1);
} else if(letObj.x < -80) { letObj.dom.remove(); t.letters.splice(j, 1); }
}
});
requestAnimationFrame(tick);
}
function startClock(){
const int = setInterval(() => {
if(!game.active) return clearInterval(int);
game.teams.forEach((t, i) => { if(t.alive && !t.isPaused) { t.time--; updateHUD(i); if(t.time <= 0) endGame(i); } });
}, 1000);
}
function endGame(idx){
game.teams[idx].alive = false;
document.getElementById(`t${idx+1}-over`).style.display = 'flex';
document.getElementById(`t${idx+1}-final-score`).innerText = "Final Score: " + game.teams[idx].score;
}
function startMove(idx, dir) { if(!game.teams[idx].isPaused && game.teams[idx].alive) game.teams[idx].dy = dir * 28; }
function stopMove(idx) { game.teams[idx].dy = 0; }
function proceedNext(idx) {
playSfx('sfxTing');
const t = game.teams[idx];
document.getElementById(`t${idx+1}-cont`).classList.remove('paused');
document.getElementById(`t${idx+1}-next-layer`).style.display = 'none';
t.isPaused = false; t.qIdx++; t.typed=""; nextQ(idx);
}
function updateAudio(){
const m=document.getElementById('bgMusic'); m.volume = state.vol = document.getElementById('set-vol').value;
state.music = document.getElementById('set-music').checked;
if(state.music && game.active) m.play().catch(()=>{}); else m.pause();
}
function saveToFile(){ const b=new Blob([JSON.stringify(state)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(b); a.download='Lesson.json'; a.click(); }
function loadFromFile(e){ const r=new FileReader(); r.onload=(ev)=>{ state=JSON.parse(ev.target.result); renderTable(); syncUI(); autoSave(); }; r.readAsText(e.target.files[0]); }
function clearAllData() { if(confirm("Erase all data?")) { localStorage.removeItem('teacherKhen_Spelling_Pro'); location.reload(); } }
autoLoad();
</script>
</body>
</html>
0 Comments