// Game State let gameState = { difficulty: 1, currentProblem: null, points: 0, totalAnswers: 0 }; // Difficulty configurations (will be updated with translations) let difficultyConfig = { 1: { num1Min: 1, num1Max: 9, num2Min: 1, num2Max: 9, descriptionKey: 'difficulty1' }, 2: { num1Min: 10, num1Max: 99, num2Min: 1, num2Max: 9, descriptionKey: 'difficulty2' }, 3: { num1Min: 10, num1Max: 99, num2Min: 10, num2Max: 99, descriptionKey: 'difficulty3' }, 4: { num1Min: 100, num1Max: 999, num2Min: 10, num2Max: 99, descriptionKey: 'difficulty4' }, 5: { num1Min: 10000, num1Max: 99999, num2Min: 100, num2Max: 999, descriptionKey: 'difficulty5' } }; // DOM Elements const difficultySlider = document.getElementById('difficulty-slider'); const difficultyDisplay = document.getElementById('difficulty-display'); const difficultyDescription = document.getElementById('difficulty-description'); const problemDisplay = document.getElementById('problem-display'); const submitBtn = document.getElementById('submit-btn'); const feedbackDiv = document.getElementById('feedback'); const pointsScoreDisplay = document.getElementById('points-score'); const newProblemBtn = document.getElementById('new-problem-btn'); const victoryModal = document.getElementById('victory-modal'); const playAgainBtn = document.getElementById('play-again-btn'); // Event Listeners difficultySlider.addEventListener('change', handleDifficultyChange); submitBtn.addEventListener('click', handleSubmitAnswer); newProblemBtn.addEventListener('click', generateNewProblem); playAgainBtn.addEventListener('click', resetGame); // Initialize window.addEventListener('load', async () => { await i18n.initialize(); initializeLanguageSelector(); updateUIText(); generateNewProblem(); }); /** * Handle difficulty slider change */ function handleDifficultyChange() { gameState.difficulty = parseInt(difficultySlider.value); document.getElementById('difficulty-display').textContent = gameState.difficulty; const config = difficultyConfig[gameState.difficulty]; if (config && config.descriptionKey) { difficultyDescription.textContent = i18n.t(config.descriptionKey); } generateNewProblem(); } /** * Generate a random number between min and max (inclusive) */ function getRandomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } /** * Generate a new multiplication problem */ function generateNewProblem() { const config = difficultyConfig[gameState.difficulty]; const num1 = getRandomNumber(config.num1Min, config.num1Max); const num2 = getRandomNumber(config.num2Min, config.num2Max); const answer = num1 * num2; gameState.currentProblem = { num1, num2, answer }; displayProblem(); clearFeedback(); } /** * Calculate intermediate steps for vertical multiplication */ function calculateIntermediateSteps(num1, num2) { const num2Str = num2.toString(); const steps = []; // Calculate partial products for each digit of num2 for (let i = num2Str.length - 1; i >= 0; i--) { const digit = parseInt(num2Str[i]); const partialProduct = num1 * digit; const shiftAmount = num2Str.length - 1 - i; steps.push({ digit, product: partialProduct, shift: shiftAmount }); } return steps; } /** * Display the multiplication problem */ function displayProblem() { const { num1, num2 } = gameState.currentProblem; const difficulty = gameState.difficulty; if (difficulty === 1) { // Horizontal layout for 1x1 with result const answer = gameState.currentProblem.answer.toString(); let resultHTML = '
'; for (let i = 0; i < answer.length; i++) { resultHTML += ``; } resultHTML += '
'; problemDisplay.innerHTML = `
${num1} × ${num2} = ${resultHTML}
`; // Add event listeners to result digit inputs setTimeout(() => { addResultDigitInputListeners(); }, 0); } else { // Vertical layout for 2+ digits const num1Str = num1.toString(); const num2Str = num2.toString(); // Calculate intermediate steps for reference const steps = calculateIntermediateSteps(num1, num2); gameState.currentProblem.steps = steps; // Create input fields for intermediate steps only if num2 has more than 1 digit let intermediateHTML = ''; if (num2 > 9) { // Calculate the maximum width needed for intermediate steps const maxResultWidth = gameState.currentProblem.answer.toString().length; for (let i = 0; i < steps.length; i++) { const stepProduct = steps[i].product.toString(); const totalWidth = maxResultWidth; intermediateHTML += '
'; // Add input fields for each digit for (let j = 0; j < stepProduct.length; j++) { intermediateHTML += ``; } // Add trailing empty spaces on the right const trailingSpaces = i; for (let e = 0; e < trailingSpaces; e++) { intermediateHTML += ``; } intermediateHTML += '
'; } } // Create input fields for final result const answer = gameState.currentProblem.answer.toString(); let resultHTML = '
'; for (let i = 0; i < answer.length; i++) { resultHTML += ``; } resultHTML += '
'; let html = `
${num1Str}
× ${num2Str}
`; if (num2 > 9) { html += `
${intermediateHTML}
`; } html += `
${resultHTML}
`; problemDisplay.innerHTML = html; // Add event listeners to digit inputs setTimeout(() => { addDigitInputListeners(); addResultDigitInputListeners(); }, 0); } } /** * Add event listeners to digit input fields */ function addDigitInputListeners() { const digitInputs = document.querySelectorAll('.digit-input:not(.result-input)'); digitInputs.forEach((input, index) => { input.addEventListener('input', (e) => { // Only allow digits if (!/^\d?$/.test(e.target.value)) { e.target.value = ''; return; } // Move to next input if digit entered if (e.target.value && index < digitInputs.length - 1) { digitInputs[index + 1].focus(); } }); input.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && !e.target.value && index > 0) { digitInputs[index - 1].focus(); } }); }); } /** * Add event listeners to result digit input fields * Navigation: left after filling, wrap to rightmost of next line */ function addResultDigitInputListeners() { const resultInputs = document.querySelectorAll('.result-input'); resultInputs.forEach((input, index) => { input.addEventListener('input', (e) => { // Only allow digits if (!/^\d?$/.test(e.target.value)) { e.target.value = ''; return; } // Move to LEFT if digit entered if (e.target.value) { if (index > 0) { // Move to previous input (left) resultInputs[index - 1].focus(); } else if (index === 0) { // At the leftmost position, wrap to rightmost of previous line const allLines = document.querySelectorAll('#result-steps .line, #intermediate-steps .line'); if (allLines.length > 1) { const prevLineInputs = allLines[allLines.length - 2].querySelectorAll('input'); if (prevLineInputs.length > 0) { prevLineInputs[prevLineInputs.length - 1].focus(); } } } } }); input.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && !e.target.value && index < resultInputs.length - 1) { resultInputs[index + 1].focus(); } }); }); } /** * Find which digits are incorrect in the answer */ function findIncorrectDigits(userAnswer, correctAnswer) { const userStr = userAnswer.toString(); const correctStr = correctAnswer.toString(); const incorrectPositions = []; // Pad the shorter string with leading zeros for comparison const maxLen = Math.max(userStr.length, correctStr.length); const userPadded = userStr.padStart(maxLen, '0'); const correctPadded = correctStr.padStart(maxLen, '0'); for (let i = 0; i < maxLen; i++) { if (userPadded[i] !== correctPadded[i]) { incorrectPositions.push(i); } } return incorrectPositions; } /** * Display answer with highlighted incorrect digits */ function displayAnswerWithErrors(userAnswer, correctAnswer) { const incorrectPositions = findIncorrectDigits(userAnswer, correctAnswer); const userStr = userAnswer.toString(); const correctStr = correctAnswer.toString(); const maxLen = Math.max(userStr.length, correctStr.length); const userPadded = userStr.padStart(maxLen, '0'); let html = '
Your answer: '; for (let i = 0; i < maxLen; i++) { const digit = userPadded[i]; if (incorrectPositions.includes(i)) { html += `${digit}`; } else { html += digit; } } html += `
Correct answer: ${correctStr}
`; return html; } /** * Get user answer from input boxes */ function getUserAnswer() { const resultInputs = document.querySelectorAll('.result-input'); let answer = ''; resultInputs.forEach(input => { answer += input.value; }); return answer === '' ? null : parseInt(answer); } /** * Get user intermediate steps from input boxes */ function getUserIntermediateSteps() { const steps = []; const intermediateLines = document.querySelectorAll('#intermediate-steps .line'); intermediateLines.forEach((line, lineIndex) => { const inputs = line.querySelectorAll('input'); let stepValue = ''; inputs.forEach(input => { stepValue += input.value; }); if (stepValue) { steps.push(parseInt(stepValue)); } }); return steps; } /** * Handle answer submission */ function handleSubmitAnswer() { const userAnswer = getUserAnswer(); const correctAnswer = gameState.currentProblem.answer; if (userAnswer === null) { showFeedback('Please enter an answer!', 'incorrect'); return; } gameState.totalAnswers++; // Check intermediate steps if they exist const intermediateStepsDiv = document.getElementById('intermediate-steps'); let intermediateStepsCorrect = true; let intermediateErrorMessage = ''; if (intermediateStepsDiv) { const steps = gameState.currentProblem.steps; const intermediateLines = document.querySelectorAll('#intermediate-steps .line'); intermediateLines.forEach((line, lineIndex) => { const inputs = line.querySelectorAll('input'); let userStepValue = ''; inputs.forEach(input => { userStepValue += input.value; }); if (userStepValue) { const userStepNum = parseInt(userStepValue); const correctStepNum = steps[lineIndex].product; if (userStepNum !== correctStepNum) { intermediateStepsCorrect = false; intermediateErrorMessage += `
Step ${lineIndex + 1}: You wrote ${userStepNum}, correct is ${correctStepNum}`; // Highlight incorrect digits in this step const correctStr = correctStepNum.toString(); const userStr = userStepValue; inputs.forEach((input, digitIndex) => { if (digitIndex < userStr.length && userStr[digitIndex] !== correctStr[correctStr.length - userStr.length + digitIndex]) { input.classList.add('error'); } else if (digitIndex < userStr.length) { input.classList.add('correct'); } }); } } }); } if (userAnswer === correctAnswer && intermediateStepsCorrect) { gameState.points += 1; showFeedback('🎉 Correct! +1 point', 'correct'); updateScore(); // Check if reached 10 points if (gameState.points >= 10) { showVictoryModal(); return; } setTimeout(() => { generateNewProblem(); }, 1500); } else { gameState.points = Math.max(0, gameState.points - 2); // Mark incorrect result boxes with red color if (userAnswer !== correctAnswer) { const resultInputs = document.querySelectorAll('.result-input'); const userStr = userAnswer.toString(); const correctStr = correctAnswer.toString(); const maxLen = Math.max(userStr.length, correctStr.length); const userPadded = userStr.padStart(maxLen, '0'); const correctPadded = correctStr.padStart(maxLen, '0'); resultInputs.forEach((input, index) => { if (userPadded[index] !== correctPadded[index]) { input.classList.add('error'); } }); } feedbackDiv.textContent = '❌ Your answer is wrong, check the red boxes'; feedbackDiv.className = 'feedback incorrect'; updateScore(); } } /** * Show feedback message */ function showFeedback(message, type) { feedbackDiv.textContent = message; feedbackDiv.className = `feedback ${type}`; } /** * Clear feedback message */ function clearFeedback() { feedbackDiv.textContent = ''; feedbackDiv.className = 'feedback empty'; } /** * Update score display */ function updateScore() { pointsScoreDisplay.textContent = `${gameState.points}/10`; } /** * Show victory modal and create confetti */ function showVictoryModal() { victoryModal.classList.add('show'); createConfetti(); } /** * Create confetti animation */ function createConfetti() { const colors = ['#2563eb', '#7c3aed', '#dc2626', '#16a34a', '#ea580c', '#f59e0b']; const confettiCount = 50; for (let i = 0; i < confettiCount; i++) { const confetti = document.createElement('div'); confetti.className = 'confetti'; confetti.style.left = Math.random() * 100 + '%'; confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; confetti.style.delay = Math.random() * 0.5 + 's'; confetti.style.animationDuration = (Math.random() * 2 + 2.5) + 's'; document.body.appendChild(confetti); // Remove confetti element after animation setTimeout(() => { confetti.remove(); }, 3500); } } /** * Reset game for new round */ function resetGame() { gameState.points = 0; gameState.totalAnswers = 0; victoryModal.classList.remove('show'); updateScore(); generateNewProblem(); } /** * Initialize language selector */ function initializeLanguageSelector() { const languageFlagsContainer = document.getElementById('language-flags'); const languages = i18n.getSupportedLanguages(); languageFlagsContainer.innerHTML = ''; Object.entries(languages).forEach(([langCode, langInfo]) => { const flagButton = document.createElement('button'); flagButton.className = 'language-flag'; flagButton.textContent = langInfo.flag; flagButton.title = langInfo.name; flagButton.setAttribute('data-tooltip', langInfo.name); if (langCode === i18n.getCurrentLanguage()) { flagButton.classList.add('active'); } flagButton.addEventListener('click', () => { changeLanguage(langCode); }); languageFlagsContainer.appendChild(flagButton); }); } /** * Change language and update UI */ function changeLanguage(langCode) { i18n.setLanguage(langCode); // Update active flag document.querySelectorAll('.language-flag').forEach(flag => { flag.classList.remove('active'); }); const languages = i18n.getSupportedLanguages(); const flagButtons = document.querySelectorAll('.language-flag'); const langCodes = Object.keys(languages); flagButtons.forEach((btn, index) => { if (langCodes[index] === langCode) { btn.classList.add('active'); } }); updateUIText(); } /** * Update all UI text based on current language */ function updateUIText() { // Update header document.getElementById('title').textContent = '🎓 ' + i18n.t('title'); document.getElementById('subtitle').textContent = i18n.t('subtitle'); // Update difficulty label const difficultyLabel = document.querySelector('.difficulty-label'); if (difficultyLabel) { difficultyLabel.innerHTML = i18n.t('difficultyLevel') + ' ' + gameState.difficulty + ''; } // Update difficulty text labels const difficultyTexts = document.querySelectorAll('.difficulty-text'); if (difficultyTexts.length >= 2) { difficultyTexts[0].textContent = i18n.t('easy'); difficultyTexts[1].textContent = i18n.t('hard'); } // Update difficulty description const config = difficultyConfig[gameState.difficulty]; if (config && config.descriptionKey) { document.getElementById('difficulty-description').textContent = i18n.t(config.descriptionKey); } // Update buttons document.getElementById('submit-btn').textContent = i18n.t('checkAnswer'); document.getElementById('new-problem-btn').textContent = i18n.t('newProblem'); // Update score label const scoreLabel = document.querySelector('.score-label'); if (scoreLabel) { scoreLabel.textContent = i18n.t('points'); } // Update modal const modalTitle = document.querySelector('.modal-content h2'); const modalText = document.querySelector('.modal-content p'); const playAgainButton = document.getElementById('play-again-btn'); if (modalTitle) { modalTitle.textContent = i18n.t('congratulations'); } if (modalText) { modalText.textContent = i18n.t('youReached10Points'); } if (playAgainButton) { playAgainButton.textContent = i18n.t('playAgain'); } }