// ===============================
// BODMAS MAIN JS (FULL UPDATED - FIXED)
// ===============================
// --- MESSAGES ---
const successMessages = [
"Absolute Legend!",
"Human Calculator Verified!",
"Pure Genius Status!",
];
const failMessages = [
"Oof! That was a tough one.",
"So close, yet so far!",
"Math is hard, isn't it?",
];
// -------------------------------
// ✅ GLOBAL AUDIO (ONLY ONCE)
// -------------------------------
const audio = document.getElementById("backgroundMusic");
let musicStarted = false;
// ✅ Run game + try music after HTML loads
document.addEventListener("DOMContentLoaded", () => {
initMathGame();
startMusic(); // try autoplay
updateMusicIcon();
});
// ✅ If browser blocks autoplay, start music on first click anywhere
document.addEventListener(
"click",
() => {
if (!musicStarted) startMusic();
},
{ once: true }
);
// -------------------------------
// 1) Math Game
// -------------------------------
function initMathGame() {
const questionEl = document.getElementById("math-question");
const optionsEl = document.getElementById("math-options");
// ✅ Safety check (prevents errors)
if (!questionEl || !optionsEl) return;
const n1 = Math.floor(Math.random() * 8) + 12; // 12-19
const n2 = Math.floor(Math.random() * 6) + 4; // 4-9
const n3 = Math.floor(Math.random() * 35) + 15; // 15-50
const expression = `(${n1} × ${n2}) - ${n3}`;
const answer = n1 * n2 - n3;
let opts = new Set([answer]);
opts.add(answer + 10);
opts.add(answer - (Math.random() > 0.5 ? 1 : 2));
opts.add(n1 * n2 + n3);
while (opts.size < 4) {
opts.add(answer + Math.floor(Math.random() * 20) - 10);
}
let finalOptions = Array.from(opts).sort(() => Math.random() - 0.5);
questionEl.innerText = expression;
optionsEl.innerHTML = "";
finalOptions.forEach((opt, i) => {
let btn = document.createElement("div");
btn.className =
"bg-white text-indigo-700 p-5 rounded-2xl candy-btn border-b-4 border-indigo-200 text-center text-xl font-bold cursor-pointer hover:bg-indigo-50 select-none math-pop-anim opacity-0";
btn.style.animationDelay = `${i * 0.1}s`;
btn.innerText = opt;
btn.onclick = function () {
const allBtns = optionsEl.children;
for (let b of allBtns) b.style.pointerEvents = "none";
if (opt === answer) {
btn.className =
"bg-green-500 text-white p-5 rounded-2xl border-b-4 border-green-700 text-center text-xl font-bold shadow-xl transform scale-105";
triggerConfetti();
setTimeout(() => showMathResult(true, answer), 1000);
} else {
btn.className =
"bg-red-500 text-white p-5 rounded-2xl border-b-4 border-red-700 text-center text-xl font-bold math-shake-anim";
setTimeout(() => showMathResult(false, answer), 1000);
}
};
optionsEl.appendChild(btn);
});
}
function showMathResult(isWin, realAnswer) {
const overlay = document.getElementById("math-result-overlay");
const title = document.getElementById("math-title");
const msg = document.getElementById("math-msg");
const resultImg = document.getElementById("math-result-img");
if (!overlay || !title || !msg) return;
overlay.classList.remove("opacity-0", "pointer-events-none");
overlay.classList.add("opacity-100", "pointer-events-auto");
// ✅ show result image + pop effect
if (resultImg) {
resultImg.classList.remove("hidden");
// ✅ change image
resultImg.src = isWin ? "images/right.jpg" : "images/wrong.jpg";
// ✅ restart animation every time
resultImg.classList.remove("pop-zoom");
void resultImg.offsetWidth; // force reflow (animation restart)
resultImg.classList.add("pop-zoom");
}
if (isWin) {
const randomMsg =
successMessages[Math.floor(Math.random() * successMessages.length)];
title.innerHTML = `Victory!
`;
title.className = "text-5xl font-black mb-2 text-yellow-400 drop-shadow-lg";
msg.innerHTML = `${randomMsg}`;
} else {
const randomMsg =
failMessages[Math.floor(Math.random() * failMessages.length)];
title.innerText = "Knocked Out!";
title.className = "text-5xl font-black mb-2 text-red-400 drop-shadow-lg";
msg.innerHTML = `Correct answer: ${realAnswer}
${randomMsg}`;
}
}
function triggerConfetti() {
const box = document.getElementById("math-confetti");
if (!box) return;
box.innerHTML = "";
const colors = ["#FCD34D", "#F87171", "#60A5FA", "#34D399"];
for (let i = 0; i < 60; i++) {
let c = document.createElement("div");
c.style.cssText = `position:absolute; width:12px; height:12px; border-radius:50%; top:-10px; left:${
Math.random() * 100
}%; background:${colors[Math.floor(Math.random() * 4)]}; transition: top 3s ease-in, transform 3s linear; opacity: 0.8;`;
box.appendChild(c);
setTimeout(() => {
c.style.top = "120%";
c.style.transform = `rotate(${Math.random() * 720}deg) translateX(${
Math.random() * 50 - 25
}px)`;
}, 50);
}
}
// -------------------------------
// 2) Modals (Info / Rules / Leaderboard)
// -------------------------------
function openModal(modalId) {
document.getElementById(modalId)?.classList.add("active");
}
function closeAnyModal(modalId) {
document.getElementById(modalId)?.classList.remove("active");
}
// -------------------------------
// 3) Info Tabs
// -------------------------------
function openInfoModal(tabName) {
openModal("infoModal");
switchInfoTab(tabName);
}
function switchInfoTab(tabName) {
document
.querySelectorAll(".info-content")
.forEach((el) => el.classList.add("hidden"));
document
.querySelectorAll(".tab-btn")
.forEach((el) => el.classList.remove("active"));
document.getElementById("content-" + tabName)?.classList.remove("hidden");
document.getElementById("tab-" + tabName)?.classList.add("active");
}
// -------------------------------
// 5) AUTO MUSIC + ONE BUTTON (Mute/Unmute)
// -------------------------------
function startMusic() {
if (!audio) return;
audio.muted = false;
audio.volume = 0.7;
audio.loop = true;
const tryPlay = () => {
audio
.play()
.then(() => {
musicStarted = true;
updateMusicIcon();
})
.catch(() => {
updateMusicIcon();
console.log("Autoplay blocked. Music will start on first click.");
});
};
// ✅ Wait for audio to be ready (reduces buffering)
if (audio.readyState >= 2) {
tryPlay();
} else {
audio.addEventListener("canplaythrough", tryPlay, { once: true });
audio.load();
}
}
// ✅ One button = Mute/Unmute
function toggleMusic() {
if (!audio) return;
audio.muted = !audio.muted;
updateMusicIcon();
}
function updateMusicIcon() {
const icon1 = document.getElementById("musicIcon");
const icon2 = document.getElementById("musicIconFooter");
const musicBtn = document.getElementById("musicBtn");
if (!audio) return;
const newIcon = audio.muted ? "music_off" : "music_note";
if (icon1) icon1.textContent = newIcon;
if (icon2) icon2.textContent = newIcon;
if (musicBtn) musicBtn.classList.toggle("opacity-60", audio.muted);
}
// ✅ error handler
if (audio) {
audio.addEventListener("error", () => {
console.log("Audio failed to load. Check bg.mp3 path.");
});
}
// -------------------------------
// 6) Page Switching (Home <-> Start)
// -------------------------------
function showStartPage() {
document.getElementById("page-home")?.classList.remove("active");
document.getElementById("page-start")?.classList.add("active");
// ✅ Music continues naturally
if (!musicStarted) startMusic();
}
function showHomePage() {
document.getElementById("page-start")?.classList.remove("active");
document.getElementById("page-home")?.classList.add("active");
}
function goHome() {
showHomePage();
}
// -------------------------------
// 7) QUESTIONS BANK + GAME LOGIC
// -------------------------------
const questionsByLevel = {
1: [
{ question: "21 - 12 / 3 * 2", choiceA: "15", choiceB: "13", choiceC: "16", choiceD: "12", correct: "B" },
{ question: "16 + 8 / 4 - 2 * 3", choiceA: "12", choiceB: "11", choiceC: "16", choiceD: "15", correct: "A" },
{ question: "30 * 2 / 3 - 10", choiceA: "15", choiceB: "20", choiceC: "10", choiceD: "23", correct: "C" },
{ question: "15 / 3 * 2 - 10", choiceA: "8", choiceB: "5", choiceC: "1", choiceD: "0", correct: "D" },
{ question: "30 - 10 * 8 + 60", choiceA: "10", choiceB: "5", choiceC: "10", choiceD: "11", correct: "C" },
{ question: "18 + 12 / 3 * 2", choiceA: "26", choiceB: "22", choiceC: "18", choiceD: "20", correct: "A" },
{ question: "40 - 6 * 5 + 10", choiceA: "20", choiceB: "10", choiceC: "30", choiceD: "40", correct: "A" },
{ question: "9 + 6 / 2 * 3", choiceA: "18", choiceB: "12", choiceC: "15", choiceD: "16", correct: "C" },
{ question: "50 / 5 + 6 * 2", choiceA: "22", choiceB: "20", choiceC: "18", choiceD: "24", correct: "A" },
{ question: "24 - 8 / 4 + 6", choiceA: "28", choiceB: "22", choiceC: "24", choiceD: "26", correct: "B" },
],
2: [
{ question: "25 + (2 * 3) - 5", choiceA: "10", choiceB: "26", choiceC: "15", choiceD: "20", correct: "B" },
{ question: "13 - (12 - 6 / 3)", choiceA: "3", choiceB: "1", choiceC: "6", choiceD: "5", correct: "A" },
{ question: "3 * 5 + (10 / 5 - 2)", choiceA: "11", choiceB: "20", choiceC: "15", choiceD: "23", correct: "C" },
{ question: "(10 - 8) * 2 + 10", choiceA: "18", choiceB: "15", choiceC: "16", choiceD: "14", correct: "D" },
{ question: "10 + (2 * 5) - 10 / 2", choiceA: "28", choiceB: "19", choiceC: "15", choiceD: "11", correct: "C" },
{ question: "18 + (12 / 3) * 2", choiceA: "26", choiceB: "24", choiceC: "22", choiceD: "20", correct: "A" },
{ question: "30 - (10 + 5) * 2", choiceA: "10", choiceB: "0", choiceC: "5", choiceD: "15", correct: "B" },
{ question: "(20 - 4) / 2 + 6", choiceA: "14", choiceB: "10", choiceC: "16", choiceD: "12", correct: "B" },
{ question: "12 + (18 / 6) * 5", choiceA: "27", choiceB: "20", choiceC: "22", choiceD: "25", correct: "A" },
{ question: "40 - (12 / 3 + 2) * 4", choiceA: "20", choiceB: "12", choiceC: "24", choiceD: "18", correct: "A" },
],
3: [
{ question: "19 - [4 + {16 - (12 - 2)}]", choiceA: "15", choiceB: "9", choiceC: "12", choiceD: "11", correct: "B" },
{ question: "36 - [18 - {14 - (15 - 4 / 2 * 2)}]", choiceA: "21", choiceB: "18", choiceC: "12", choiceD: "11", correct: "A" },
{ question: "15 + 10 - [{10 - 1} / (2 + 1)]", choiceA: "10", choiceB: "20", choiceC: "22", choiceD: "23", correct: "C" },
{ question: "30 + [{(20 + 1) * 5 - (2 + 1) / 3}]", choiceA: "80", choiceB: "115", choiceC: "120", choiceD: "134", correct: "D" },
{ question: "19 - [4 + {16 - (12 - 8)}]", choiceA: "8", choiceB: "9", choiceC: "3", choiceD: "11", correct: "C" },
{ question: "50 - [10 + {20 - (6 * 2)}]", choiceA: "32", choiceB: "28", choiceC: "26", choiceD: "30", correct: "A" },
{ question: "10 + [8 * {6 - (4 / 2)}]", choiceA: "42", choiceB: "50", choiceC: "34", choiceD: "26", correct: "A" },
{ question: "60 / [3 * (4 + 1)]", choiceA: "5", choiceB: "4", choiceC: "3", choiceD: "6", correct: "A" },
{ question: "100 - [{(30 - 10) * 2} + 20]", choiceA: "40", choiceB: "20", choiceC: "60", choiceD: "50", correct: "A" },
{ question: "[18 - (6 + 3)] * 2", choiceA: "12", choiceB: "18", choiceC: "9", choiceD: "15", correct: "A" },
],
4: [
{ question: "{40 - [12 + (6 * 2)]} / 2", choiceA: "8", choiceB: "10", choiceC: "12", choiceD: "14", correct: "A" },
{ question: "80 - {20 + [10 * (3 + 2)]}", choiceA: "10", choiceB: "20", choiceC: "30", choiceD: "40", correct: "B" },
{ question: "50 + (20 / 5) * {6 - 2}", choiceA: "62", choiceB: "66", choiceC: "70", choiceD: "74", correct: "B" },
{ question: "[100 / (4 * 5)] + {18 - 6}", choiceA: "10", choiceB: "16", choiceC: "17", choiceD: "20", correct: "B" },
{ question: "90 - [ {30 - 10} * (2 + 1) ]", choiceA: "30", choiceB: "40", choiceC: "50", choiceD: "60", correct: "B" },
{ question: "{72 / [3 * (4 + 2)]} + 5", choiceA: "9", choiceB: "7", choiceC: "11", choiceD: "13", correct: "A" },
{ question: "120 - [{(15 * 4) + 20} * 1]", choiceA: "40", choiceB: "20", choiceC: "60", choiceD: "30", correct: "A" },
{ question: "[60 - (12 * 3)] + (18 / 6)", choiceA: "27", choiceB: "25", choiceC: "24", choiceD: "22", correct: "A" },
{ question: "{(9 + 6) * 2} - [8 / 4]", choiceA: "28", choiceB: "30", choiceC: "32", choiceD: "26", correct: "B" },
{ question: "100 - [{(20 + 10) * 2} / 5]", choiceA: "88", choiceB: "80", choiceC: "90", choiceD: "84", correct: "D" },
],
};
const bossTemplates = [
{ expr: "({a} + {b}) * {c} - {d}", type: "mix" },
{ expr: "{a} + ({b} * {c}) - {d} / {e}", type: "mix2" },
{ expr: "{a} - [{b} + ({c} - {d})] * {e}", type: "hard" },
{ expr: "{a} + [{b} * ({c} + {d})] / {e}", type: "hard2" },
];
const POINTS_PER_QUESTION = 500;
const unlockRules = {
1: { needCorrect: 4, total: 5, unlockNext: 2 },
2: { needCorrect: 3, total: 5, unlockNext: 3 },
3: { needCorrect: 3, total: 5, unlockNext: 4 },
4: { needCorrect: 3, total: 5, unlockNext: 5 },
5: { needCorrect: null, total: null, unlockNext: null },
};
let player = {
points: 0,
unlockedLevel: 1,
stars: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
// ✅ NEW: replay tracking
playedLevels: { 1: false, 2: false, 3: false, 4: false, 5: false },
levelScore: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
};
const STORAGE_KEY = "bodmas_island_progress_v1";
function saveProgress() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(player));
}
function loadProgress() {
const data = localStorage.getItem(STORAGE_KEY);
if (data) {
try {
player = JSON.parse(data);
// ✅ safety if old data exists
if (!player.playedLevels) {
player.playedLevels = { 1: false, 2: false, 3: false, 4: false, 5: false };
}
if (!player.levelScore) {
player.levelScore = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
}
} catch (e) {}
}
}
function updateLevelButtonText() {
for (let lvl = 1; lvl <= 5; lvl++) {
const card = document.getElementById(`card-level-${lvl}`);
if (!card) continue;
const btn = card.querySelector("button");
if (!btn) continue;
if (player.playedLevels?.[lvl]) {
btn.textContent = lvl === 5 ? "REPLAY BOSS" : "REPLAY";
}
}
}
// Game modal elements
const gameModal = document.getElementById("gameModal");
const modalTitle = document.getElementById("modalTitle");
const questionText = document.getElementById("questionText");
const progressBadge = document.getElementById("progressBadge");
const scoreBadge = document.getElementById("scoreBadge");
const nextBtn = document.getElementById("nextBtn");
const timerText = document.getElementById("timerText");
const optA = document.getElementById("optA");
const optB = document.getElementById("optB");
const optC = document.getElementById("optC");
const optD = document.getElementById("optD");
let currentLevel = 1;
let currentSet = [];
let qIndex = 0;
let correctCount = 0;
let answered = false;
// ✅ TIMER VARIABLES
const QUESTION_TIME = 15;
let timeLeft = QUESTION_TIME;
let timerInterval = null;
function shuffleArray(arr) {
return arr.sort(() => Math.random() - 0.5);
}
function pick5RandomQuestions(level) {
const pool = [...(questionsByLevel[level] || [])];
shuffleArray(pool);
return pool.slice(0, 5);
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function generateBossQuestion() {
const t = bossTemplates[Math.floor(Math.random() * bossTemplates.length)];
const a = rand(10, 60),
b = rand(3, 25),
c = rand(2, 10),
d = rand(5, 30),
e = rand(2, 6);
const expr = t.expr
.replace("{a}", a)
.replace("{b}", b)
.replace("{c}", c)
.replace("{d}", d)
.replace("{e}", e);
let answerValue = 0;
try {
answerValue = Math.round(
Function("return " + expr.replace(/\[/g, "(").replace(/\]/g, ")"))()
);
} catch (err) {
answerValue = a;
}
const correctAns = answerValue;
const options = new Set([correctAns]);
while (options.size < 4) {
const offset = rand(-12, 12);
options.add(correctAns + offset);
}
const opts = Array.from(options);
shuffleArray(opts);
const map = { A: opts[0], B: opts[1], C: opts[2], D: opts[3] };
const correctKey = Object.keys(map).find((k) => map[k] === correctAns);
return {
question: expr,
choiceA: String(map.A),
choiceB: String(map.B),
choiceC: String(map.C),
choiceD: String(map.D),
correct: correctKey,
};
}
function openGameModal() {
gameModal?.classList.add("active");
}
function closeGameModal() {
stopTimer();
gameModal?.classList.remove("active");
}
function resetOptions() {
[optA, optB, optC, optD].forEach((btn) => {
btn?.classList.remove("correct", "wrong");
if (btn) btn.disabled = false;
});
if (nextBtn) nextBtn.disabled = true;
answered = false;
}
// ✅ TIMER FUNCTIONS
function stopTimer() {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
}
function startTimer() {
stopTimer();
timeLeft = QUESTION_TIME;
if (timerText) timerText.textContent = timeLeft;
timerInterval = setInterval(() => {
timeLeft--;
if (timerText) timerText.textContent = timeLeft;
if (timeLeft <= 0) {
stopTimer();
autoWrongTimeout();
}
}, 1000);
}
function autoWrongTimeout() {
if (answered) return;
answered = true;
const q = currentSet[qIndex];
const correct = q.correct;
const btnMap = { A: optA, B: optB, C: optC, D: optD };
// highlight correct answer
btnMap[correct]?.classList.add("correct");
disableOptions();
if (nextBtn) nextBtn.disabled = true;
// Boss mode ends on timeout
if (currentLevel === 5) {
setTimeout(() => {
alert(`⏳ Time Up! Boss Mode ended!\n🔥 Total Points: ${player.points}`);
closeGameModal();
saveProgress();
updateLocksUI();
}, 900);
return;
}
setTimeout(() => {
if (nextBtn) nextBtn.disabled = false;
}, 600);
}
function renderQuestion() {
const q = currentSet[qIndex];
if (modalTitle) modalTitle.textContent = `Level ${currentLevel}`;
if (scoreBadge) scoreBadge.textContent = `Score: ${player.points}`;
if (progressBadge) {
if (currentLevel === 5) {
progressBadge.textContent = `Boss Mode 🔥 (Keep Going!) | Correct: ${correctCount}`;
} else {
progressBadge.textContent = `Q ${qIndex + 1}/5 | Correct: ${correctCount}`;
}
}
if (questionText) questionText.textContent = q.question;
if (optA) optA.textContent = "A) " + q.choiceA;
if (optB) optB.textContent = "B) " + q.choiceB;
if (optC) optC.textContent = "C) " + q.choiceC;
if (optD) optD.textContent = "D) " + q.choiceD;
resetOptions();
startTimer();
}
function startLevel(level) {
if (level > player.unlockedLevel) {
alert("🔒 This level is locked! Clear previous level first.");
return;
}
currentLevel = level;
qIndex = 0;
correctCount = 0;
// ✅ replay = remove old score first
if (player.playedLevels?.[level]) {
player.points -= (player.levelScore?.[level] || 0);
if (player.points < 0) player.points = 0;
// reset stored score for this run
player.levelScore[level] = 0;
updatePointsUI();
if (scoreBadge) scoreBadge.textContent = `Score: ${player.points}`;
}
// reset stars for new attempt
player.stars[level] = 0;
renderStars(level);
saveProgress();
if (level === 5) {
currentSet = [generateBossQuestion()];
} else {
currentSet = pick5RandomQuestions(level);
}
openGameModal();
renderQuestion();
// ✅ keep music playing
if (!musicStarted) startMusic();
}
function disableOptions() {
[optA, optB, optC, optD].forEach((btn) => {
if (btn) btn.disabled = true;
});
}
function checkAnswer(selected) {
if (answered) return;
answered = true;
stopTimer();
const q = currentSet[qIndex];
const correct = q.correct;
const btnMap = { A: optA, B: optB, C: optC, D: optD };
if (selected === correct) {
btnMap[selected]?.classList.add("correct");
correctCount++;
player.points += POINTS_PER_QUESTION;
// ✅ store points earned inside this level
player.levelScore[currentLevel] =
(player.levelScore[currentLevel] || 0) + POINTS_PER_QUESTION;
updatePointsUI();
// ✅ Stars glow = correct answers
player.stars[currentLevel] = Math.min(correctCount, 5);
renderStars(currentLevel);
saveProgress();
} else {
btnMap[selected]?.classList.add("wrong");
btnMap[correct]?.classList.add("correct");
// Boss mode ends immediately on wrong
if (currentLevel === 5) {
disableOptions();
if (nextBtn) nextBtn.disabled = true;
setTimeout(() => {
alert(
`❌ Wrong Answer! Boss Mode ended!\n🔥 You scored: ${player.points} points`
);
closeGameModal();
saveProgress();
updateLocksUI();
}, 900);
return;
}
}
disableOptions();
if (nextBtn) nextBtn.disabled = false;
if (scoreBadge) scoreBadge.textContent = `Score: ${player.points}`;
saveProgress();
}
function nextQuestion() {
stopTimer();
if (currentLevel === 5) {
currentSet.push(generateBossQuestion());
qIndex++;
renderQuestion();
return;
}
qIndex++;
if (qIndex >= 5) {
finishLevel();
return;
}
renderQuestion();
}
function finishLevel() {
stopTimer();
const rule = unlockRules[currentLevel];
const passed = correctCount >= rule.needCorrect;
if (passed) {
alert(
`✅ Level ${currentLevel} Cleared!\n⭐ Correct: ${correctCount}/5\n🎉 Next level unlocked!`
);
if (rule.unlockNext) {
player.unlockedLevel = Math.max(player.unlockedLevel, rule.unlockNext);
}
if (currentLevel === 1) {
const badge = document.getElementById("lvl1-badge");
if (badge) badge.textContent = "COMPLETED";
}
} else {
alert(
`❌ Level ${currentLevel} Failed!\n⭐ Correct: ${correctCount}/5\nTry Again!`
);
}
// ✅ mark played and update card text
player.playedLevels[currentLevel] = true;
updateLevelButtonText();
saveProgress();
updateLocksUI();
closeGameModal();
}
// ✅ Stars system
function renderStars(level) {
const container = document.getElementById(`stars-${level}`);
if (!container) return;
container.innerHTML = "";
const filled = Math.min(player.stars[level] || 0, 5);
for (let i = 1; i <= 5; i++) {
const s = document.createElement("span");
s.className = "star " + (i <= filled ? "filled" : "");
s.textContent = "⭐";
container.appendChild(s);
}
}
function updateLocksUI() {
for (let lvl = 1; lvl <= 5; lvl++) {
const card = document.getElementById(`card-level-${lvl}`);
if (!card) continue;
if (lvl > player.unlockedLevel) {
card.classList.add("locked-state");
} else {
card.classList.remove("locked-state");
}
}
}
function updatePointsUI() {
const counter = document.getElementById("points-counter");
if (counter) counter.textContent = player.points;
}
function resetGame() {
const confirmReset = confirm(
"🔁 Reset Everything?\nPoints will go to 0 and all levels will lock again!"
);
if (!confirmReset) return;
// ✅ reset player object (FULL RESET)
player = {
points: 0,
unlockedLevel: 1,
stars: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
playedLevels: { 1: false, 2: false, 3: false, 4: false, 5: false },
levelScore: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
};
// ✅ clear localStorage progress
localStorage.removeItem(STORAGE_KEY);
// ✅ update UI
updatePointsUI();
updateLocksUI();
for (let i = 1; i <= 5; i++) renderStars(i);
// ✅ reset buttons back
// (your default HTML already shows PLAY/BOSS so we reload by refreshing)
// but since you asked not to change HTML, we just keep replay off until played again
updateLevelButtonText();
// ✅ close modal if open
closeGameModal();
// ✅ go home page
showHomePage();
alert("✅ Game Reset Successfully!");
}
// -------------------------------
// ✅ INIT GAME STATE
// -------------------------------
loadProgress();
updatePointsUI();
updateLocksUI();
for (let i = 1; i <= 5; i++) renderStars(i);
updateLevelButtonText();
// -------------------------------
// ✅ Make functions global for onclick=""
// -------------------------------
window.openModal = openModal;
window.closeModal = closeAnyModal; // for normal modals
window.openInfoModal = openInfoModal;
window.switchInfoTab = switchInfoTab;
window.showStartPage = showStartPage;
window.showHomePage = showHomePage;
window.goHome = goHome;
window.startLevel = startLevel;
window.checkAnswer = checkAnswer;
window.nextQuestion = nextQuestion;
window.resetGame = resetGame;
// ✅ game modal close function
window.closeGameModal = closeGameModal;
// ✅ music controls
window.toggleMusic = toggleMusic;
window.startMusic = startMusic;
// ===================================================
// ✅ PRACTICE MODE (HOME PAGE WORLDS - UNLIMITED)
// ✅ Result shown in SAME POPUP ✅
// ===================================================
let pWorld = 1;
let pScore = 0;
let pCorrect = 0;
let pDifficulty = 0;
let pAnswered = false;
let pTimer = null;
let pTimeLeft = 15;
let pCurrentQ = null;
const PRACTICE_POINTS = 500;
// ✅ practice modal elements (must exist in HTML)
const practiceModal = document.getElementById("practiceModal");
const practiceQuestion = document.getElementById("practiceQuestion");
const practiceProgress = document.getElementById("practiceProgress");
const practiceTimer = document.getElementById("practiceTimer");
const practiceScore = document.getElementById("practiceScore");
const pA = document.getElementById("pA");
const pB = document.getElementById("pB");
const pC = document.getElementById("pC");
const pD = document.getElementById("pD");
// ✅ result inside same popup
const practiceResultBox = document.getElementById("practiceResultBox");
const practiceResultReason = document.getElementById("practiceResultReason");
const finalScore = document.getElementById("finalScore");
const finalCorrect = document.getElementById("finalCorrect");
const practiceResultImg = document.getElementById("practiceResultImg");
const practiceResultTitle = document.getElementById("practiceResultTitle");
const optionGrid = document.querySelector("#practiceModal .opt-grid");
const timerBadge = document.getElementById("practiceTimerBadge");
// ✅ safe random for practice mode
function pRand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// ✅ generate question per world + harder every time
function pGenerateQuestion(world) {
let a = pRand(5 + pDifficulty * 2, 15 + pDifficulty * 3);
let b = pRand(2, 10);
let c = pRand(2, 7);
let d = pRand(5 + pDifficulty, 25 + pDifficulty * 2);
let e = pRand(2, 6);
let expr = "";
let ans = 0;
// ✅ World 1: only add/sub
if (world === 1) {
expr = `${a} + ${d} - ${b}`;
ans = a + d - b;
}
// ✅ World 2: only division & multiplication
if (world === 2) {
let x = a * b;
expr = `${x} ÷ ${b} × ${c}`;
ans = (x / b) * c;
}
// ✅ World 3: Full BODMAS mixed
if (world === 3) {
expr = `${a} + ${d} ÷ ${b} × ${c}`;
ans = Math.floor(a + (d / b) * c);
}
// ✅ World 4: PRO BODMAS
if (world === 4) {
expr = `(${a} + ${b}) × ${c} - ${d} ÷ ${e}`;
ans = Math.floor((a + b) * c - d / e);
}
ans = Math.round(ans);
// ✅ options
let options = new Set([ans]);
while (options.size < 4) {
options.add(ans + pRand(-12, 12));
}
options = Array.from(options).sort(() => Math.random() - 0.5);
const map = { A: options[0], B: options[1], C: options[2], D: options[3] };
const correctKey = Object.keys(map).find((k) => map[k] === ans);
return {
question: expr,
choiceA: map.A,
choiceB: map.B,
choiceC: map.C,
choiceD: map.D,
correct: correctKey,
};
}
// ✅ open practice popup
function startWorldMode(w) {
pWorld = w;
pScore = 0;
pCorrect = 0;
pDifficulty = 0;
pAnswered = false;
openPractice();
pHideResult();
pLoadQuestion();
}
// ✅ open/close modal
function openPractice() {
if (!practiceModal) return;
practiceModal.classList.add("active");
}
function closePractice() {
pStopTimer();
practiceModal?.classList.remove("active");
}
// ✅ timer gets harder (less time)
function pStartTimer() {
pStopTimer();
let maxTime = Math.max(5, 15 - Math.floor(pDifficulty / 2));
pTimeLeft = maxTime;
if (practiceTimer) practiceTimer.textContent = pTimeLeft;
pTimer = setInterval(() => {
pTimeLeft--;
if (practiceTimer) practiceTimer.textContent = pTimeLeft;
if (pTimeLeft <= 0) {
pStopTimer();
pEndPractice("⏳ Time Up!");
}
}, 1000);
}
function pStopTimer() {
if (pTimer) clearInterval(pTimer);
pTimer = null;
}
// ✅ load question
function pLoadQuestion() {
pAnswered = false;
pHideResult();
pCurrentQ = pGenerateQuestion(pWorld);
if (practiceQuestion) practiceQuestion.textContent = pCurrentQ.question;
if (pA) pA.textContent = "A) " + pCurrentQ.choiceA;
if (pB) pB.textContent = "B) " + pCurrentQ.choiceB;
if (pC) pC.textContent = "C) " + pCurrentQ.choiceC;
if (pD) pD.textContent = "D) " + pCurrentQ.choiceD;
if (practiceProgress) practiceProgress.textContent = `✅ Correct: ${pCorrect}`;
if (practiceScore) practiceScore.textContent = `Score: ${pScore}`;
[pA, pB, pC, pD].forEach((btn) => {
if (!btn) return;
btn.classList.remove("correct", "wrong");
btn.disabled = false;
});
pStartTimer();
}
// ✅ answer click
function practiceAnswer(selected) {
if (pAnswered) return;
pAnswered = true;
pStopTimer();
const correctKey = pCurrentQ.correct;
const btnMap = { A: pA, B: pB, C: pC, D: pD };
if (selected === correctKey) {
btnMap[selected]?.classList.add("correct");
pCorrect++;
pScore += PRACTICE_POINTS;
pDifficulty++;
[pA, pB, pC, pD].forEach((btn) => {
if (btn) btn.disabled = true;
});
setTimeout(() => {
pLoadQuestion();
}, 550);
} else {
btnMap[selected]?.classList.add("wrong");
btnMap[correctKey]?.classList.add("correct");
[pA, pB, pC, pD].forEach((btn) => {
if (btn) btn.disabled = true;
});
setTimeout(() => {
pEndPractice("❌ Wrong Answer!");
}, 650);
}
}
// ✅ end practice (result inside SAME POPUP)
function pEndPractice(reason) {
pStopTimer();
pShowResult(reason);
}
function pShowResult(reason) {
if (!practiceResultBox) return;
practiceResultBox.classList.remove("hidden");
// ✅ show wrong image (like Daily Challenge)
if (practiceResultImg) {
practiceResultImg.classList.remove("hidden");
practiceResultImg.src = "images/wrong.jpg";
// restart animation
practiceResultImg.classList.remove("pop-zoom");
void practiceResultImg.offsetWidth;
practiceResultImg.classList.add("pop-zoom");
}
// ✅ title styling
if (practiceResultTitle) {
practiceResultTitle.textContent = "GAME OVER!";
practiceResultTitle.className =
"bubble-text text-5xl font-black mb-3 text-red-400 drop-shadow-lg";
}
if (practiceResultReason) practiceResultReason.textContent = reason;
if (finalScore) finalScore.textContent = pScore;
if (finalCorrect) finalCorrect.textContent = pCorrect;
// ✅ hide question UI
practiceQuestion?.classList.add("hidden");
optionGrid?.classList.add("hidden");
practiceProgress?.classList.add("hidden");
practiceScore?.classList.add("hidden");
timerBadge?.classList.add("hidden");
}
function pHideResult() {
practiceResultBox?.classList.add("hidden");
// ✅ hide image when restarting
if (practiceResultImg) {
practiceResultImg.classList.add("hidden");
practiceResultImg.src = "";
}
practiceQuestion?.classList.remove("hidden");
optionGrid?.classList.remove("hidden");
practiceProgress?.classList.remove("hidden");
practiceScore?.classList.remove("hidden");
timerBadge?.classList.remove("hidden");
}
// ✅ replay inside same popup
function replayPractice() {
pStopTimer();
pScore = 0;
pCorrect = 0;
pDifficulty = 0;
pHideResult();
pLoadQuestion();
}
// ✅ expose functions to HTML buttons
window.startWorldMode = startWorldMode;
window.practiceAnswer = practiceAnswer;
window.replayPractice = replayPractice;
window.closePractice = closePractice;