const charset = 'abcdefghijklmnopqrstuvwxyz'; const maxChar = ; // 候選字母選項 const cacheKey = "practice_0_0"; var nums = ; var num = 0; var toRight = 0; var toWrong = 0; var Score = 0; var Integral = 0; var wrongRecord = []; var gameOver = 0; var reTry = 0; function testQuests(nums) { if(typeof reTestQuests != 'undefined') return reTestQuests; let retString = localStorage.getItem(cacheKey); if( !retString || typeof retString === 'undefined'){ allQuests = allQuests.sort(function(){return Math.random()>0.5?-1:1;}); let tmpQquest=[]; for (var i = 0; i < nums; i++) { tmpQquest[i] = allQuests[i]; selectChar = allQuests[i][0] + generateRandomLetterSequence(charset,maxChar-(allQuests[i][0].length)); tmpQquest[i].push(generateRandomLetterSequence(selectChar)); } localStorage.setItem(cacheKey, JSON.stringify(tmpQquest)); return tmpQquest; } else { console.log('cached'); return JSON.parse(retString); } } function Play() { //console.log(Quest); testQuests = testQuests(nums); //console.log(testQuests); if(!testQuests || typeof testQuests === 'undefined'){ alert('Data error'); return; } showQuest(num); $('.selChar').click(function() { $(this).css('visibility','hidden'); enterAns($(this).text()); }); window.addEventListener("keypress", function(e) { if(e.which == 13 || e.which==8 || e.which==46) return; charCode = String.fromCharCode(e.which); if(charCode && charCode.length>0) return enterAns(charCode); }); window.addEventListener("keydown", function(e) { // enter if(e.which == 13){ return checkAns(); } if(e.which==8 || e.which==46){ // remove last chr delLastEnterChar(); } }); // enter $('#enterChar').click(function() { checkAns(); }); // reset chr $('#Reset').click(function() { $('.selChar').css('visibility','visible'); enterChar = ''; $('#enterChar').text(enterChar); }); // del last char $('#Backspace').click(function() { delLastEnterChar(); $('.selChar').css('visibility','visible'); }); } function showQuest(num) { testQuest = testQuests[num]; if(!testQuest || typeof testQuest === 'undefined'){ alert('Data error.'); return; } let audioFile = null; engWord = testQuest[0]; chword = testQuest[1]; mp3File = testQuest[2]; enChar = testQuest[3]; Qnum = num+1; $('#chWord').text('Q'+Qnum+':'+testQuest[1]); if(mp3File){ audioFile = "/sp/audio/" + mp3File; } playCurrentAudio(audioFile); listChar(enChar); $('#questProgress').val(Qnum); enterChar = ''; $('#enterChar').text(enterChar); $('.selChar').css('visibility','visible'); } function listChar(enChar) { i=0; $(".selChar").each(function(){ $(this).text(enChar[i]); i++; }); } function enterAns(chr) { enterChar += chr; $('#enterChar').text(enterChar); } function delLastEnterChar() { enterChar = enterChar.slice(0,-1); $('#enterChar').text(enterChar); } function isWrong() { toWrong++; $('#toWrong').text(toWrong); $('#enterChar').css("background-color", "yellow"); $('#enterChar').text('✘'+enterChar); //console.log(enterChar); wrongRecord[num] = enterChar; } function isRight() { toRight++; $('#toRight').text(toRight); Score = parseInt((toRight/nums)*100).toFixed(2); $('#Score').text(Score); Integral += engWord.length; $('#Integral').text(Integral); $('#enterChar').text('✔'+enterChar); } function checkAns() { if(gameOver == 1) return; if(!enterChar || enterChar.length==0) return; if(enterChar.trim() != engWord.trim()){ isWrong(); } else { isRight(); } enterChar = ''; $.wait( function(){ $('#enterChar').css("background-color", ""); if((num+1) >= testQuests.length ){ gameOver = 1; return endTest(); } else { num++; showQuest(num); } }, 1); } let currentAudioObj = null; function stopAudio() { if (currentAudioObj) { currentAudioObj.pause(); currentAudioObj = null; } window.speechSynthesis.cancel(); } // ========================================== // 播放邏輯 (與之前相同,但移除了 onended 事件) // ========================================== async function playCurrentAudio(audioFile) { const word = engWord; const localUrl = audioFile; //console.log(`播放: ${word}`); // A. 本地檔案 if (localUrl) { playHtml5Audio(localUrl); return; } // B. API try { const apiUrl = `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`; const res = await fetch(apiUrl); if (res.ok) { const data = await res.json(); const remoteUrl = findAudioUrlInJson(data); if (remoteUrl) { playHtml5Audio(remoteUrl); return; } } } catch (e) { console.log("API Error", e); } // C. TTS playTTS(word); } function findAudioUrlInJson(data) { if (data[0] && data[0].phonetics) { for (let p of data[0].phonetics) { if (p.audio) return p.audio; } } return null; } function playHtml5Audio(url) { if (currentAudioObj) currentAudioObj.pause(); const audio = new Audio(url); currentAudioObj = audio; // 錯誤處理 audio.onerror = () => { console.log("MP3 播放失敗,轉 TTS"); playTTS(words[index].en); }; audio.play().catch(e => { console.error("瀏覽器阻擋自動播放 (使用者需先與網頁互動)", e); }); } function playTTS(word) { window.speechSynthesis.cancel(); const utter = new SpeechSynthesisUtterance(word); utter.lang = 'en-US'; utter.rate = 0.8; window.speechSynthesis.speak(utter); } const startAt = '2026-03-04 00:12:15'; const jsonFilename = '/sp/'; $(document).ready(function() { getCache(jsonFilename).then(function (data) { allQuests = data; Play(); }); var myModalEl = document.getElementById('myModal'); myModal = new bootstrap.Modal(myModalEl, {keyboard: false}); }); function endTest() { $('#exampleModalLabel').text($('#result').text()); if(reTry==0){ updateScore(); } if(wrongRecord.length<=0) return showAllPass(); reTestQuests = []; addTr = '