Spaces:
Running
Running
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>數學探險島 - 寶石洞窟</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Noto Sans TC', sans-serif; | |
| background-image: url('https://i.meee.com.tw/pI0c5ue.png'); | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| canvas { | |
| background-color: #2d241d; | |
| display: block; | |
| border-radius: 0.5rem; | |
| box-shadow: inset 0 0 20px rgba(0,0,0,0.5); | |
| } | |
| .action-button { | |
| transition: all 0.2s ease-in-out; | |
| box-shadow: 0 5px #995f00; | |
| } | |
| .action-button:active { | |
| transform: translateY(3px); | |
| box-shadow: 0 2px #995f00; | |
| } | |
| .move-button { | |
| transition: all 0.2s ease-in-out; | |
| box-shadow: 0 5px #1e3a8a; | |
| } | |
| .move-button:active { | |
| transform: translateY(3px); | |
| box-shadow: 0 2px #1e3a8a; | |
| } | |
| .gem-button { | |
| transition: transform 0.1s ease-in-out; | |
| } | |
| .gem-button:active { | |
| transform: scale(0.9); | |
| } | |
| .secret-bg { | |
| background-image: url('https://i.meee.com.tw/EKZpYKI.png'); | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| </style> | |
| </head> | |
| <body class="flex items-center justify-center h-screen overflow-hidden"> | |
| <div id="container" class="w-full max-w-md mx-auto text-white p-4 flex flex-col h-full"> | |
| <!-- 引導畫面 --> | |
| <div id="start-screen" class="flex flex-col items-center justify-center text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto"> | |
| <h1 class="text-4xl font-bold text-amber-300 mb-4">寶石洞窟</h1> | |
| <p class="text-lg text-gray-200 mb-8">這是一個已經廢棄的寶石洞窟,接下來你必須駕駛一台礦車,成功閃避障礙物來獲得璀璨寶石!</p> | |
| <button id="start-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-4 px-8 rounded-lg text-2xl"> | |
| 開始採礦 | |
| </button> | |
| </div> | |
| <!-- 遊戲畫面 --> | |
| <div id="game-screen" class="hidden flex-col h-full"> | |
| <div id="canvas-wrapper" class="relative"> | |
| <canvas id="gameCanvas"></canvas> | |
| <!-- 操作說明畫面 --> | |
| <div id="instructions-screen" class="hidden absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center text-center p-4 rounded-lg"> | |
| <h2 class="text-3xl font-bold text-amber-300 mb-6">操作說明</h2> | |
| <div class="space-y-4 text-lg"> | |
| <p>點擊畫面<span class="text-blue-400 font-bold">左半邊</span>或按<span class="text-blue-400 font-bold">左方向鍵</span> ← 向左移動</p> | |
| <p>點擊畫面<span class="text-blue-400 font-bold">右半邊</span>或按<span class="text-blue-400 font-bold">右方向鍵</span> → 向右移動</p> | |
| <p class="mt-4">也可以使用下方的按鈕操作!</p> | |
| </div> | |
| <button id="play-game-button" class="action-button bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8"> | |
| 了解! | |
| </button> | |
| </div> | |
| <!-- 遊戲結束畫面 --> | |
| <div id="game-over-screen" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center text-center p-4 rounded-lg"> | |
| <h2 class="text-4xl font-bold text-red-500 mb-4">挑戰失敗!</h2> | |
| <p class="text-xl mb-6">礦車撞毀了!</p> | |
| <button id="restart-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-3 px-6 rounded-lg text-xl"> | |
| 重新挑戰 | |
| </button> | |
| </div> | |
| <!-- 遊戲獲勝畫面 --> | |
| <div id="win-screen" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center text-center p-4 rounded-lg"> | |
| <h2 class="text-4xl font-bold text-green-400 mb-4">成功!</h2> | |
| <p class="text-xl mb-2">你成功抵達了寶石區域!</p> | |
| <p id="win-perfect-dodges" class="text-2xl text-amber-300 font-bold mb-6"></p> | |
| <button id="continue-button" class="action-button bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg text-xl"> | |
| 繼續前進 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 移動按鈕 --> | |
| <div id="controls" class="flex justify-between mt-4"> | |
| <button id="move-left-button" class="move-button bg-blue-600 hover:bg-blue-700 text-white font-bold p-4 rounded-lg w-24 h-16"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg> | |
| </button> | |
| <button id="move-right-button" class="move-button bg-blue-600 hover:bg-blue-700 text-white font-bold p-4 rounded-lg w-24 h-16"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 寶石解謎畫面 --> | |
| <div id="puzzle-screen" class="hidden flex-col h-full p-6 bg-black bg-opacity-60 rounded-lg"> | |
| <div class="flex-grow overflow-y-auto"> | |
| <h1 class="text-3xl font-bold text-amber-300 text-center mb-4">寶石組合挑戰</h1> | |
| <p class="text-center mb-4">觀察大寶石,點擊下方的小寶石,組合出正確的配方!</p> | |
| <div class="flex justify-center mb-4"> | |
| <img id="large-gem-image" src="" class="h-40 object-contain" alt="[大寶石的Image]"> | |
| </div> | |
| <div id="answer-slots" class="bg-gray-900/50 min-h-[80px] rounded-lg p-2 flex flex-wrap items-center justify-center gap-2 mb-4 border-2 border-amber-400"></div> | |
| <div id="small-gem-options" class="flex justify-center items-center gap-4 mb-6 flex-wrap"></div> | |
| <div id="puzzle-feedback" class="text-center text-xl font-bold min-h-[32px] mb-4"></div> | |
| </div> | |
| <div class="flex gap-4 mt-auto pt-4"> | |
| <button id="reset-puzzle-button" class="w-1/2 bg-gray-500 hover:bg-gray-600 text-white font-bold py-3 rounded-lg">重置</button> | |
| <button id="check-puzzle-button" class="w-1/2 bg-green-500 hover:bg-green-600 text-white font-bold py-3 rounded-lg">鑑定寶石</button> | |
| </div> | |
| </div> | |
| <!-- 過場畫面 --> | |
| <div id="intermission-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto"> | |
| <h1 class="text-3xl font-bold text-amber-300 mb-4">準備挑戰下一區!</h1> | |
| <p class="text-lg text-gray-200 mb-8">前方的礦道更加危險,速度會更快!</p> | |
| <button id="start-next-stage-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-4 px-8 rounded-lg text-2xl"> | |
| 繼續採礦 | |
| </button> | |
| </div> | |
| <!-- 最終通關畫面 --> | |
| <div id="final-win-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto"> | |
| <h1 class="text-4xl font-bold text-green-400 mb-4">恭喜你完成所有寶石挑戰!</h1> | |
| <button id="unlock-secret-button" class="action-button bg-purple-600 hover:bg-purple-700 text-white font-bold py-4 px-8 rounded-lg text-2xl"> | |
| 解鎖寶石洞窟的秘密 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 秘密揭曉畫面 (獨立於 container 之外) --> | |
| <div id="secret-screen" class="hidden absolute inset-0 w-screen h-screen secret-bg p-8 text-white flex-col items-center justify-center"> | |
| <div class="w-full max-w-2xl bg-black bg-opacity-70 p-8 rounded-xl"> | |
| <div id="secret-question"> | |
| <h2 class="text-3xl font-bold text-amber-300 mb-6">你認為這個洞窟的秘密是...</h2> | |
| <div class="space-y-4 text-lg"> | |
| <button data-choice="A" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">A. 如何完美駕駛礦車,一次通關</button> | |
| <button data-choice="B" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">B. 如何增加完美閃避的次數</button> | |
| <button data-choice="C" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">C. 寶石分析術</button> | |
| </div> | |
| <p id="secret-feedback" class="mt-6 min-h-[28px] text-xl"></p> | |
| </div> | |
| <div id="secret-reveal" class="hidden text-left space-y-4"> | |
| <p class="text-lg">從最小的寶石到浩瀚的星辰,萬事萬物都由更基本的元素構成。你學會的『寶石分析術』,在數學的世界裡,它被稱為『因式分解』。</p> | |
| <p class="text-2xl font-bold text-amber-300">這個洞窟的秘密就是:學會分析與拆解,就是看透事物本質的第一步。</p> | |
| <p class="text-lg">無論是分析一顆寶石的成分、一道數學題的結構,還是未來你遇到的任何複雜問題,這種化繁為簡的智慧,才是你從這裡帶走、永不消失的真正寶藏。</p> | |
| </div> | |
| <div class="mt-12 text-center"> | |
| <a href="index.html" id="back-to-map-button" class="action-button inline-block bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg text-xl"> | |
| 回到探險島地圖 | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // --- 畫面元素 --- | |
| const container = document.getElementById('container'); | |
| const startScreen = document.getElementById('start-screen'); | |
| const gameScreen = document.getElementById('game-screen'); | |
| const puzzleScreen = document.getElementById('puzzle-screen'); | |
| const intermissionScreen = document.getElementById('intermission-screen'); | |
| const finalWinScreen = document.getElementById('final-win-screen'); | |
| const secretScreen = document.getElementById('secret-screen'); | |
| const startButton = document.getElementById('start-button'); | |
| const instructionsScreen = document.getElementById('instructions-screen'); | |
| const playGameButton = document.getElementById('play-game-button'); | |
| const gameOverScreen = document.getElementById('game-over-screen'); | |
| const winScreen = document.getElementById('win-screen'); | |
| const winPerfectDodges = document.getElementById('win-perfect-dodges'); | |
| const restartButton = document.getElementById('restart-button'); | |
| const continueButton = document.getElementById('continue-button'); | |
| const startNextStageButton = document.getElementById('start-next-stage-button'); | |
| const unlockSecretButton = document.getElementById('unlock-secret-button'); | |
| const canvas = document.getElementById('gameCanvas'); | |
| const canvasWrapper = document.getElementById('canvas-wrapper'); | |
| const moveLeftButton = document.getElementById('move-left-button'); | |
| const moveRightButton = document.getElementById('move-right-button'); | |
| const ctx = canvas.getContext('2d'); | |
| // --- 寶石解謎元素 --- | |
| const largeGemImage = document.getElementById('large-gem-image'); | |
| const answerSlots = document.getElementById('answer-slots'); | |
| const smallGemOptions = document.getElementById('small-gem-options'); | |
| const puzzleFeedback = document.getElementById('puzzle-feedback'); | |
| const resetPuzzleButton = document.getElementById('reset-puzzle-button'); | |
| const checkPuzzleButton = document.getElementById('check-puzzle-button'); | |
| // --- 秘密揭曉元素 --- | |
| const secretQuestion = document.getElementById('secret-question'); | |
| const secretReveal = document.getElementById('secret-reveal'); | |
| const secretFeedback = document.getElementById('secret-feedback'); | |
| const secretChoiceButtons = document.querySelectorAll('.secret-choice'); | |
| // --- 遊戲設定 --- | |
| const LANE_COUNT = 3; | |
| let laneWidth; | |
| let gameInterval, timer, obstacleInterval; | |
| let timeLeft; | |
| let perfectDodges = 0; | |
| const perfectDodgeTexts = []; | |
| let keySequence = []; | |
| const cheatCode = "kkkkk"; | |
| let currentStage = 0; | |
| let currentStageSettings; | |
| const gameStages = [ | |
| { speed: 2.5, time: 20, spawnRate: 1800 }, | |
| { speed: 3.5, time: 15, spawnRate: 1200 }, | |
| { speed: 4.0, time: 12, spawnRate: 1000 } | |
| ]; | |
| // --- 寶石解謎設定 --- | |
| const allSmallGems = [ | |
| { id: 1, src: 'https://i.meee.com.tw/FeaxgIn.png' }, | |
| { id: 2, src: 'https://i.meee.com.tw/VgnnNxo.png' }, | |
| { id: 3, src: 'https://i.meee.com.tw/5vu7Qk1.png' }, | |
| { id: 4, src: 'https://i.meee.com.tw/eoel84q.png' }, | |
| { id: 5, src: 'https://i.meee.com.tw/b2uL22E.png' }, | |
| { id: 6, src: 'https://i.meee.com.tw/HeduyDE.png' }, | |
| { id: 7, src: 'https://i.meee.com.tw/2gorePd.png' }, | |
| { id: 8, src: 'https://i.meee.com.tw/HNNEM09.png' }, | |
| { id: 9, src: 'https://i.meee.com.tw/cpw8ccy.png' }, | |
| { id: 10, src: 'https://i.meee.com.tw/Rk8hY7H.png' } | |
| ]; | |
| const puzzleLevels = [ | |
| { | |
| largeGemSrc: 'https://i.meee.com.tw/PU1busV.png', | |
| options: [1, 2, 3], | |
| answer: { 1: 1, 2: 3, 3: 1 }, | |
| maxGems: 5 | |
| }, | |
| { | |
| largeGemSrc: 'https://i.meee.com.tw/JCK7B2S.png', | |
| options: [2, 3, 4, 5, 6, 7], | |
| answer: { 3: 2, 7: 2, 2: 1, 4: 1, 5: 1, 6: 1 }, | |
| maxGems: 8 | |
| }, | |
| { | |
| largeGemSrc: 'https://i.meee.com.tw/wFJquPy.png', | |
| options: [2, 3, 4, 5, 6, 8, 9, 10], | |
| answer: { 8: 3, 10: 3, 5: 6, 9: 1 }, | |
| maxGems: 13 | |
| } | |
| ]; | |
| let playerSelection = {}; | |
| // --- 玩家設定 --- | |
| const player = { | |
| x: 0, y: 0, width: 100, height: 100, lane: 1, image: new Image() | |
| }; | |
| const normalCartSrc = 'https://i.meee.com.tw/w6krwUz.png'; | |
| const crashedCartSrc = 'https://i.meee.com.tw/4wSy5uX.png'; | |
| player.image.src = normalCartSrc; | |
| player.image.onerror = () => { console.error("礦車圖片載入失敗!"); }; | |
| // --- 障礙物設定 --- | |
| const obstacles = []; | |
| const trackTies = []; | |
| const obstacleImage = new Image(); | |
| obstacleImage.src = 'https://i.meee.com.tw/L0tV6le.png'; | |
| obstacleImage.onerror = () => { console.error("障礙物圖片載入失敗!"); }; | |
| let lastTieY = 0; | |
| const TIE_SPACING = 50; | |
| // --- 畫面管理 --- | |
| function showScreen(screenId) { | |
| ['start-screen', 'game-screen', 'puzzle-screen', 'intermission-screen', 'final-win-screen'].forEach(id => { | |
| const screen = document.getElementById(id); | |
| if (id === screenId) { | |
| screen.style.display = 'flex'; | |
| screen.classList.remove('hidden'); | |
| if(id === 'start-screen' || id === 'intermission-screen' || id === 'final-win-screen') { | |
| screen.classList.add('my-auto'); | |
| } else { | |
| screen.classList.remove('my-auto'); | |
| } | |
| } else { | |
| screen.style.display = 'none'; | |
| screen.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| // --- 礦車遊戲函式 --- | |
| function resizeCanvas() { | |
| const container = document.getElementById('container'); | |
| const controls = document.getElementById('controls'); | |
| canvas.width = container.clientWidth; | |
| canvas.height = window.innerHeight * 0.9 - controls.offsetHeight - 20; | |
| canvasWrapper.style.width = `${canvas.width}px`; | |
| canvasWrapper.style.height = `${canvas.height}px`; | |
| laneWidth = canvas.width / LANE_COUNT; | |
| player.y = canvas.height - player.height - 10; | |
| } | |
| function getLaneCenterX(lane) { | |
| return (lane * laneWidth) + (laneWidth / 2); | |
| } | |
| function spawnTrackTies() { | |
| while (lastTieY < canvas.height + TIE_SPACING) { | |
| trackTies.push({ y: lastTieY }); | |
| lastTieY += TIE_SPACING; | |
| } | |
| } | |
| function drawTrack() { | |
| ctx.fillStyle = '#57402c'; | |
| trackTies.forEach(tie => { | |
| for (let i = 0; i < LANE_COUNT; i++) { | |
| const laneX = getLaneCenterX(i); | |
| const rail1X = laneX - laneWidth * 0.25; | |
| const rail2X = laneX + laneWidth * 0.25; | |
| ctx.fillRect(rail1X, tie.y, rail2X - rail1X, 10); | |
| } | |
| }); | |
| const railColor = '#858585', highlightColor = '#b0b0b0', railWidth = 8; | |
| for (let i = 0; i < LANE_COUNT; i++) { | |
| const laneX = getLaneCenterX(i); | |
| const rail1X = laneX - laneWidth * 0.25; | |
| const rail2X = laneX + laneWidth * 0.25; | |
| ctx.fillStyle = railColor; | |
| ctx.fillRect(rail1X - railWidth / 2, 0, railWidth, canvas.height); | |
| ctx.fillRect(rail2X - railWidth / 2, 0, railWidth, canvas.height); | |
| ctx.fillStyle = highlightColor; | |
| ctx.fillRect(rail1X - railWidth / 2, 0, railWidth / 2, canvas.height); | |
| ctx.fillRect(rail2X - railWidth / 2, 0, railWidth / 2, canvas.height); | |
| } | |
| } | |
| function drawPlayer() { | |
| player.x = getLaneCenterX(player.lane) - player.width / 2; | |
| if (player.image.complete && player.image.naturalHeight !== 0) { | |
| ctx.drawImage(player.image, player.x, player.y, player.width, player.height); | |
| } else { | |
| ctx.fillStyle = 'blue'; | |
| ctx.fillRect(player.x, player.y, player.width, player.height); | |
| } | |
| } | |
| function drawObstacles() { | |
| obstacles.forEach(obstacle => { | |
| if (obstacleImage.complete && obstacleImage.naturalHeight !== 0) { | |
| ctx.drawImage(obstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height); | |
| } else { | |
| ctx.fillStyle = '#a16207'; | |
| ctx.beginPath(); | |
| ctx.roundRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height, [10]); | |
| ctx.fill(); | |
| } | |
| }); | |
| } | |
| function drawUI() { | |
| ctx.fillStyle = 'white'; | |
| ctx.font = 'bold 20px "Noto Sans TC"'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(`倒數計時: ${timeLeft.toFixed(1)} 秒`, canvas.width / 2, 30); | |
| ctx.textAlign = 'right'; | |
| ctx.fillText(`完美閃避: ${perfectDodges}`, canvas.width - 20, 30); | |
| perfectDodgeTexts.forEach(text => { | |
| ctx.save(); | |
| ctx.globalAlpha = text.alpha; | |
| ctx.fillStyle = '#fde047'; | |
| ctx.font = 'bold 24px "Noto Sans TC"'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(text.text, text.x, text.y); | |
| ctx.restore(); | |
| }); | |
| } | |
| function update() { | |
| for (let i = trackTies.length - 1; i >= 0; i--) { | |
| trackTies[i].y += currentStageSettings.speed; | |
| if (trackTies[i].y > canvas.height) { | |
| trackTies.splice(i, 1); | |
| trackTies.unshift({ y: (trackTies[0]?.y || 0) - TIE_SPACING }); | |
| } | |
| } | |
| for (let i = obstacles.length - 1; i >= 0; i--) { | |
| obstacles[i].y += currentStageSettings.speed; | |
| if (obstacles[i].y > canvas.height) obstacles.splice(i, 1); | |
| } | |
| for (let i = perfectDodgeTexts.length - 1; i >= 0; i--) { | |
| perfectDodgeTexts[i].y -= 1; | |
| perfectDodgeTexts[i].alpha -= 0.02; | |
| if (perfectDodgeTexts[i].alpha <= 0) perfectDodgeTexts.splice(i, 1); | |
| } | |
| obstacles.forEach(obstacle => { | |
| if (player.lane === obstacle.lane && player.y < obstacle.y + obstacle.height && player.y + player.height > obstacle.y) { | |
| gameOver(); | |
| } | |
| }); | |
| } | |
| function draw() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| drawTrack(); | |
| drawObstacles(); | |
| drawPlayer(); | |
| drawUI(); | |
| } | |
| function spawnObstacle() { | |
| const lane = Math.floor(Math.random() * LANE_COUNT); | |
| const size = laneWidth * 0.5; | |
| const x = getLaneCenterX(lane) - size / 2; | |
| obstacles.push({ x, y: -size, width: size, height: size, lane, dodged: false }); | |
| } | |
| function gameLoop() { | |
| update(); | |
| draw(); | |
| gameInterval = requestAnimationFrame(gameLoop); | |
| } | |
| function stopGame() { | |
| if (gameInterval) cancelAnimationFrame(gameInterval); | |
| if (timer) clearInterval(timer); | |
| if (obstacleInterval) clearInterval(obstacleInterval); | |
| gameInterval = timer = obstacleInterval = null; | |
| } | |
| function startGame(stageIndex) { | |
| currentStage = stageIndex; | |
| currentStageSettings = gameStages[stageIndex]; | |
| player.image.src = normalCartSrc; // Reset to normal cart image | |
| stopGame(); | |
| instructionsScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| winScreen.classList.add('hidden'); | |
| resizeCanvas(); | |
| obstacles.length = 0; | |
| trackTies.length = 0; | |
| perfectDodgeTexts.length = 0; | |
| perfectDodges = 0; | |
| lastTieY = 0; | |
| spawnTrackTies(); | |
| player.lane = 1; | |
| timeLeft = currentStageSettings.time; | |
| timer = setInterval(() => { | |
| timeLeft -= 0.1; | |
| if (timeLeft <= 0) { | |
| timeLeft = 0; | |
| winGame(); | |
| } | |
| }, 100); | |
| obstacleInterval = setInterval(spawnObstacle, currentStageSettings.spawnRate); | |
| gameLoop(); | |
| } | |
| function gameOver() { | |
| stopGame(); | |
| player.image.src = crashedCartSrc; // Change to crashed cart image | |
| draw(); // Redraw canvas one last time with the new image | |
| gameOverScreen.classList.remove('hidden'); | |
| } | |
| function winGame() { | |
| stopGame(); | |
| winPerfectDodges.textContent = `完美閃避: ${perfectDodges} 次`; | |
| winScreen.classList.remove('hidden'); | |
| } | |
| function handleMove(direction) { | |
| if (!gameInterval) return; | |
| const oldLane = player.lane; | |
| let newLane = player.lane; | |
| if (direction === 'left') newLane = Math.max(0, player.lane - 1); | |
| else if (direction === 'right') newLane = Math.min(LANE_COUNT - 1, player.lane + 1); | |
| if (oldLane !== newLane) { | |
| player.lane = newLane; | |
| checkPerfectDodge(oldLane); | |
| } | |
| } | |
| function checkPerfectDodge(fromLane) { | |
| const perfectDodgeZoneTop = player.y - player.height / 2; | |
| const perfectDodgeZoneBottom = player.y + player.height; | |
| obstacles.forEach(obstacle => { | |
| if (obstacle.lane === fromLane && !obstacle.dodged) { | |
| if (obstacle.y + obstacle.height > perfectDodgeZoneTop && obstacle.y < perfectDodgeZoneBottom) { | |
| perfectDodges++; | |
| obstacle.dodged = true; | |
| perfectDodgeTexts.push({ text: '完美!', x: getLaneCenterX(fromLane), y: player.y, alpha: 1 }); | |
| } | |
| } | |
| }); | |
| } | |
| // --- 寶石解謎函式 --- | |
| function showPuzzleScreen() { | |
| showScreen('puzzle-screen'); | |
| loadPuzzleLevel(currentStage); | |
| } | |
| function loadPuzzleLevel(levelIndex) { | |
| const level = puzzleLevels[levelIndex]; | |
| largeGemImage.src = level.largeGemSrc; | |
| smallGemOptions.innerHTML = ''; | |
| level.options.forEach(gemId => { | |
| const gemData = allSmallGems.find(g => g.id === gemId); | |
| if (gemData) { | |
| const gemBtn = document.createElement('button'); | |
| gemBtn.className = 'gem-button'; | |
| gemBtn.innerHTML = `<img src="${gemData.src}" alt="小寶石 ${gemData.id}" class="h-16 w-16 object-contain">`; | |
| gemBtn.onclick = () => addGemToSelection(gemData); | |
| smallGemOptions.appendChild(gemBtn); | |
| } | |
| }); | |
| resetPuzzle(); | |
| } | |
| function addGemToSelection(gem) { | |
| const currentTotal = Object.values(playerSelection).reduce((sum, count) => sum + count, 0); | |
| const level = puzzleLevels[currentStage]; | |
| if (currentTotal >= level.maxGems) { | |
| puzzleFeedback.textContent = '數量太多了喔!'; | |
| puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-yellow-400'; | |
| setTimeout(() => { | |
| if(puzzleFeedback.textContent === '數量太多了喔!') { | |
| puzzleFeedback.textContent = ''; | |
| } | |
| }, 2000); | |
| return; | |
| } | |
| playerSelection[gem.id] = (playerSelection[gem.id] || 0) + 1; | |
| renderSelection(); | |
| } | |
| function removeGemFromSelection(gemId) { | |
| if (playerSelection[gemId]) { | |
| playerSelection[gemId]--; | |
| if (playerSelection[gemId] === 0) { | |
| delete playerSelection[gemId]; | |
| } | |
| } | |
| renderSelection(); | |
| } | |
| function renderSelection() { | |
| answerSlots.innerHTML = ''; | |
| puzzleLevels[currentStage].options.forEach(gemId => { | |
| const gemData = allSmallGems.find(g => g.id === gemId); | |
| if (playerSelection[gemId]) { | |
| const count = playerSelection[gemId]; | |
| for (let i = 0; i < count; i++) { | |
| const img = document.createElement('img'); | |
| img.src = gemData.src; | |
| img.alt = `[已選擇的小寶石 ${gemData.id}]`; | |
| img.className = 'h-12 w-12 object-contain cursor-pointer'; | |
| img.onclick = () => removeGemFromSelection(gemId); | |
| answerSlots.appendChild(img); | |
| } | |
| } | |
| }); | |
| } | |
| function resetPuzzle() { | |
| playerSelection = {}; | |
| answerSlots.innerHTML = ''; | |
| puzzleFeedback.textContent = ''; | |
| } | |
| function checkPuzzleAnswer() { | |
| const level = puzzleLevels[currentStage]; | |
| if (!level) return; | |
| const answer = level.answer; | |
| let correct = true; | |
| if (Object.keys(playerSelection).length !== Object.keys(answer).length) { | |
| correct = false; | |
| } else { | |
| for (const gemId in answer) { | |
| if (playerSelection[gemId] !== answer[gemId]) { | |
| correct = false; | |
| break; | |
| } | |
| } | |
| } | |
| if (correct) { | |
| puzzleFeedback.textContent = '組合正確!你獲得了大寶石!'; | |
| puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-green-400'; | |
| setTimeout(() => { | |
| const nextStage = currentStage + 1; | |
| if (nextStage < puzzleLevels.length) { | |
| showScreen('intermission-screen'); | |
| } else { | |
| showScreen('final-win-screen'); | |
| } | |
| }, 1500); | |
| } else { | |
| puzzleFeedback.textContent = '組合不對喔,再試一次!'; | |
| puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-red-500'; | |
| } | |
| } | |
| // --- 事件監聽 --- | |
| startButton.addEventListener('click', () => { | |
| showScreen('game-screen'); | |
| resizeCanvas(); | |
| instructionsScreen.classList.remove('hidden'); | |
| }); | |
| playGameButton.addEventListener('click', () => startGame(0)); | |
| restartButton.addEventListener('click', () => startGame(currentStage)); | |
| continueButton.addEventListener('click', showPuzzleScreen); | |
| startNextStageButton.addEventListener('click', () => { | |
| showScreen('game-screen'); | |
| startGame(currentStage + 1); | |
| }); | |
| resetPuzzleButton.addEventListener('click', resetPuzzle); | |
| checkPuzzleButton.addEventListener('click', checkPuzzleAnswer); | |
| unlockSecretButton.addEventListener('click', () => { | |
| container.style.display = 'none'; | |
| secretScreen.style.display = 'flex'; | |
| }); | |
| secretChoiceButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const choice = button.dataset.choice; | |
| secretFeedback.classList.remove('text-yellow-400', 'text-green-400'); | |
| if (choice === 'C') { | |
| secretFeedback.textContent = ''; | |
| secretQuestion.style.display = 'none'; | |
| secretReveal.style.display = 'block'; | |
| } else if (choice === 'A') { | |
| secretFeedback.textContent = '駕駛技術固然重要,但那只是過程喔!'; | |
| secretFeedback.classList.add('text-yellow-400'); | |
| } else { | |
| secretFeedback.textContent = '閃避只是手段,不是目的呀!'; | |
| secretFeedback.classList.add('text-yellow-400'); | |
| } | |
| }); | |
| }); | |
| window.addEventListener('resize', resizeCanvas); | |
| window.addEventListener('keydown', (e) => { | |
| if (e.key === 'ArrowLeft') handleMove('left'); | |
| else if (e.key === 'ArrowRight') handleMove('right'); | |
| if (gameInterval) { | |
| keySequence.push(e.key); | |
| keySequence = keySequence.slice(-cheatCode.length); | |
| if (keySequence.join('') === cheatCode) { | |
| winGame(); | |
| } | |
| } | |
| }); | |
| canvas.addEventListener('click', (e) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| const clickX = e.clientX - rect.left; | |
| if (clickX < canvas.width / 2) handleMove('left'); | |
| else handleMove('right'); | |
| }); | |
| moveLeftButton.addEventListener('click', () => handleMove('left')); | |
| moveRightButton.addEventListener('click', () => handleMove('right')); | |
| // --- 初始啟動 --- | |
| showScreen('start-screen'); | |
| }); | |
| </script> | |
| </body> | |
| </html> |