Spaces:
Running
Running
Upload 5 files
Browse files- algebra.html +571 -0
- gemstone.html +694 -0
- harbor.html +654 -0
- index.html +180 -19
- philosophy.html +429 -0
algebra.html
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-Hant">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>數學探險島 - 代數之丘</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
body {
|
| 13 |
+
font-family: 'Noto Sans TC', sans-serif;
|
| 14 |
+
touch-action: none; /* 防止在觸控拖曳時滾動頁面 */
|
| 15 |
+
background-image: url('https://i.meee.com.tw/AF01kln.png');
|
| 16 |
+
background-size: cover;
|
| 17 |
+
background-position: center;
|
| 18 |
+
}
|
| 19 |
+
/* 自訂積木顏色 */
|
| 20 |
+
.block-a-squared { background-color: #fca5a5; } /* red-300 */
|
| 21 |
+
.block-b-squared { background-color: #818cf8; } /* indigo-400 */
|
| 22 |
+
.block-ab { background-color: #fcd34d; } /* amber-300 */
|
| 23 |
+
.block-distractor { background-color: #9ca3af; } /* gray-400 */
|
| 24 |
+
|
| 25 |
+
/* 拼圖區格線 */
|
| 26 |
+
#grid-container {
|
| 27 |
+
display: grid;
|
| 28 |
+
background-image:
|
| 29 |
+
linear-gradient(to right, #d1d5db 1px, transparent 1px),
|
| 30 |
+
linear-gradient(to bottom, #d1d5db 1px, transparent 1px);
|
| 31 |
+
background-size: var(--unit-size) var(--unit-size);
|
| 32 |
+
border: 2px solid #6b7280;
|
| 33 |
+
}
|
| 34 |
+
/* 拖曳中的複製物件 */
|
| 35 |
+
.dragging-clone {
|
| 36 |
+
position: absolute;
|
| 37 |
+
pointer-events: none; /* 讓滑鼠事件穿透複製物件 */
|
| 38 |
+
z-index: 1000;
|
| 39 |
+
opacity: 0.8;
|
| 40 |
+
border: 2px dashed #4f46e5;
|
| 41 |
+
}
|
| 42 |
+
/* 已放置在選項區的積木 */
|
| 43 |
+
.palette-block.placed {
|
| 44 |
+
opacity: 0.3;
|
| 45 |
+
cursor: not-allowed;
|
| 46 |
+
pointer-events: none;
|
| 47 |
+
}
|
| 48 |
+
/* 已放置在拼圖區的積木 */
|
| 49 |
+
.placed-block {
|
| 50 |
+
position: absolute;
|
| 51 |
+
cursor: pointer;
|
| 52 |
+
border: 2px solid #1f2937;
|
| 53 |
+
transition: all 0.2s ease-in-out;
|
| 54 |
+
}
|
| 55 |
+
.placed-block:hover {
|
| 56 |
+
transform: scale(1.05);
|
| 57 |
+
box-shadow: 0 0 15px rgba(0,0,0,0.5);
|
| 58 |
+
}
|
| 59 |
+
/* 過關時的特別框線 */
|
| 60 |
+
.highlight-win {
|
| 61 |
+
background-color: #fef9c3; /* yellow-100 */
|
| 62 |
+
border: 2px solid #f59e0b; /* amber-500 */
|
| 63 |
+
border-radius: 8px;
|
| 64 |
+
padding: 2px 6px;
|
| 65 |
+
display: inline-block;
|
| 66 |
+
}
|
| 67 |
+
/* 隱藏滾動條,但在觸控設備上仍可滾動 */
|
| 68 |
+
#block-palette::-webkit-scrollbar {
|
| 69 |
+
display: none;
|
| 70 |
+
}
|
| 71 |
+
#block-palette {
|
| 72 |
+
-ms-overflow-style: none; /* IE and Edge */
|
| 73 |
+
scrollbar-width: none; /* Firefox */
|
| 74 |
+
}
|
| 75 |
+
</style>
|
| 76 |
+
</head>
|
| 77 |
+
<body class="w-screen h-screen overflow-hidden flex items-center justify-center relative">
|
| 78 |
+
|
| 79 |
+
<!-- 任務說明畫面 -->
|
| 80 |
+
<div id="start-screen" class="absolute inset-0 w-screen h-screen flex items-center justify-center p-4 transition-opacity duration-500 bg-gray-900/50">
|
| 81 |
+
<div class="container mx-auto p-6 md:p-8 bg-white/90 backdrop-blur-sm rounded-xl shadow-2xl max-w-2xl text-center">
|
| 82 |
+
<h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-6">任務說明:代數之丘</h1>
|
| 83 |
+
<p class="text-lg text-gray-700 mb-4">歡迎來到代數之丘!在這裡,我們不用死背公式,而是用雙手「拼」出數學!</p>
|
| 84 |
+
<p class="text-lg text-gray-700 mb-8">你的任務是拖曳下方的彩色積木,將左邊的灰色正方形完全填滿,親身體驗 <span class="font-mono font-bold text-indigo-700">(a+b)² = a² + 2ab + b²</span> 這個公式是如何誕生的!</p>
|
| 85 |
+
<button id="start-button" class="w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
|
| 86 |
+
開始挑戰
|
| 87 |
+
</button>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<!-- 遊戲主畫面 -->
|
| 92 |
+
<div id="game-container" class="flex flex-row w-full h-full max-w-screen-xl mx-auto p-4 lg:p-8 gap-8 items-start transition-opacity duration-500 opacity-0 hidden">
|
| 93 |
+
|
| 94 |
+
<!-- 左欄:包含拼圖區和過關訊息 -->
|
| 95 |
+
<div class="w-1/3 flex flex-col items-center justify-start gap-6">
|
| 96 |
+
<div id="grid-container" class="relative bg-gray-200/80 backdrop-blur-sm shadow-xl rounded-lg">
|
| 97 |
+
<!-- 拼圖區的積木會由 JS 動態加入這裡 -->
|
| 98 |
+
</div>
|
| 99 |
+
<!-- 過關訊息與按鈕 (已移至此處) -->
|
| 100 |
+
<div id="win-message-container" class="w-full text-center p-4 bg-white/90 backdrop-blur-sm rounded-xl shadow-lg hidden">
|
| 101 |
+
<p class="text-2xl font-bold text-green-600">恭喜過關!</p>
|
| 102 |
+
<p id="win-formula-explanation" class="text-lg text-gray-700 mt-2"></p>
|
| 103 |
+
<button id="next-level-button" class="mt-4 w-full bg-indigo-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-indigo-700 transition-colors shadow-md">
|
| 104 |
+
挑戰下一關
|
| 105 |
+
</button>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<!-- 右欄:控制項 -->
|
| 110 |
+
<div id="controls-container" class="w-2/3 flex-shrink-0 flex flex-col bg-white/90 backdrop-blur-sm rounded-xl shadow-lg p-6 h-full">
|
| 111 |
+
<h1 class="text-3xl font-bold text-gray-800 text-center">代數之丘</h1>
|
| 112 |
+
<hr class="my-4">
|
| 113 |
+
|
| 114 |
+
<!-- 算式區 -->
|
| 115 |
+
<div class="bg-blue-50/80 p-4 rounded-lg mb-4 space-y-2">
|
| 116 |
+
<!-- 題目行 -->
|
| 117 |
+
<div class="grid grid-cols-4 items-baseline">
|
| 118 |
+
<p class="text-lg text-gray-600 font-semibold col-span-1">題目:</p>
|
| 119 |
+
<p id="equation-title" class="col-span-3 text-2xl font-bold text-indigo-700 text-center"></p>
|
| 120 |
+
</div>
|
| 121 |
+
<!-- 面積結果行 -->
|
| 122 |
+
<div class="grid grid-cols-4 items-baseline">
|
| 123 |
+
<p class="text-lg text-gray-600 font-semibold col-span-1">面積結果:</p>
|
| 124 |
+
<div id="equation-result" class="col-span-3 text-2xl font-mono text-gray-800 text-center"></div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<!-- 說明文字 -->
|
| 129 |
+
<div id="instructions" class="text-center text-gray-500 mb-4 text-sm">
|
| 130 |
+
<p>請從下方拖曳積木,拼滿左邊的正方形。</p>
|
| 131 |
+
<p class="mt-1">💡 <span class="font-semibold">提示:</span>點擊已放置的積木可以將它移除。</p>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<!-- 積木選項區 (包含捲動按鈕) -->
|
| 135 |
+
<div class="relative mt-auto">
|
| 136 |
+
<div id="block-palette" class="flex flex-row flex-nowrap overflow-x-auto items-center gap-4 p-4 bg-gray-100/80 rounded-md">
|
| 137 |
+
<!-- 積木選項會由 JS 動態加入這裡 -->
|
| 138 |
+
</div>
|
| 139 |
+
<!-- 左捲動按鈕 -->
|
| 140 |
+
<button id="scroll-left-button" class="absolute left-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-1 shadow-md hidden transition-opacity">
|
| 141 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 142 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
| 143 |
+
</svg>
|
| 144 |
+
</button>
|
| 145 |
+
<!-- 右捲動按鈕 -->
|
| 146 |
+
<button id="scroll-right-button" class="absolute right-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-1 shadow-md hidden transition-opacity">
|
| 147 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 148 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
| 149 |
+
</svg>
|
| 150 |
+
</button>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<!-- 秘密揭曉畫面 (從 secret.html 整合進來) -->
|
| 156 |
+
<div id="secret-view" class="absolute inset-0 w-screen h-screen flex items-center justify-center p-4 opacity-0 hidden transition-opacity duration-500 bg-gray-900/50">
|
| 157 |
+
<div class="container mx-auto p-6 md:p-8 bg-white rounded-xl shadow-2xl max-w-4xl text-center">
|
| 158 |
+
<h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-8">代數之丘最大的秘密</h1>
|
| 159 |
+
|
| 160 |
+
<div class="grid md:grid-cols-3 gap-6 md:gap-8 mb-8">
|
| 161 |
+
<!-- Image 1 -->
|
| 162 |
+
<div class="flex flex-col items-center p-2 border rounded-lg">
|
| 163 |
+
<img src="https://i.meee.com.tw/Sx68qrS.png" alt="拼圖 (6+4)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/fca5a5/ffffff?text=(6%2B4)%C2%B2';">
|
| 164 |
+
<p class="text-lg md:text-xl font-mono font-bold">(6+4)² = 6² + <span class="highlight-win">2×6×4</span> + 4²</p>
|
| 165 |
+
</div>
|
| 166 |
+
<!-- Image 2 -->
|
| 167 |
+
<div class="flex flex-col items-center p-2 border rounded-lg">
|
| 168 |
+
<img src="https://i.meee.com.tw/7gGAB80.png" alt="拼圖 (4+9)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/818cf8/ffffff?text=(4%2B9)%C2%B2';">
|
| 169 |
+
<p class="text-lg md:text-xl font-mono font-bold">(4+9)² = 4² + <span class="highlight-win">2×4×9</span> + 9²</p>
|
| 170 |
+
</div>
|
| 171 |
+
<!-- Image 3 -->
|
| 172 |
+
<div class="flex flex-col items-center p-2 border rounded-lg">
|
| 173 |
+
<img src="https://i.meee.com.tw/0hdfRvP.png" alt="拼圖 (7+5)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/fcd34d/ffffff?text=(7%2B5)%C2%B2';">
|
| 174 |
+
<p class="text-lg md:text-xl font-mono font-bold">(7+5)² = 7² + <span class="highlight-win">2×7×5</span> + 5²</p>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<div class="text-left text-base md:text-lg text-gray-700 space-y-4 border-t-2 pt-8">
|
| 179 |
+
<p>選舉最大的祕密就是:票多的贏,票少的輸...<span class="italic text-gray-500">咳咳不是這個啦</span></p>
|
| 180 |
+
<p class="text-xl md:text-2xl font-bold text-red-600">代數之丘的最大秘密就是:(a+b)²展開後一定會有ab這項,而且還是<span class="underline decoration-wavy decoration-amber-500">2ab</span>,學會這個,這單元就已經學會一半了!</p>
|
| 181 |
+
<p class="font-semibold text-indigo-700">(請在學習單上做紀錄!)</p>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<div class="mt-12">
|
| 185 |
+
<a href="index.html" class="inline-block w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
|
| 186 |
+
回到探險島地圖
|
| 187 |
+
</a>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
<script>
|
| 194 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 195 |
+
// --- 常數與設定 ---
|
| 196 |
+
const UNIT_SIZE = 30; // 每個單位格的像素大小 (px)
|
| 197 |
+
const levels = [
|
| 198 |
+
{ a: 6, b: 4, distractors: [] },
|
| 199 |
+
{ a: 4, b: 9, distractors: [{w: 5, h: 8}, {w: 7, h: 6}] },
|
| 200 |
+
{ a: 7, b: 5, distractors: [{w: 8, h: 6}, {w: 6, h: 8}] }
|
| 201 |
+
];
|
| 202 |
+
let currentLevel = 0;
|
| 203 |
+
|
| 204 |
+
// --- DOM 元素 ---
|
| 205 |
+
const startScreen = document.getElementById('start-screen');
|
| 206 |
+
const startButton = document.getElementById('start-button');
|
| 207 |
+
const gameContainer = document.getElementById('game-container');
|
| 208 |
+
const secretView = document.getElementById('secret-view');
|
| 209 |
+
const gridContainer = document.getElementById('grid-container');
|
| 210 |
+
const blockPalette = document.getElementById('block-palette');
|
| 211 |
+
const equationTitle = document.getElementById('equation-title');
|
| 212 |
+
const equationResult = document.getElementById('equation-result');
|
| 213 |
+
const winMessageContainer = document.getElementById('win-message-container');
|
| 214 |
+
const winFormulaExplanation = document.getElementById('win-formula-explanation');
|
| 215 |
+
const nextLevelButton = document.getElementById('next-level-button');
|
| 216 |
+
const scrollLeftButton = document.getElementById('scroll-left-button');
|
| 217 |
+
const scrollRightButton = document.getElementById('scroll-right-button');
|
| 218 |
+
|
| 219 |
+
// --- 遊戲狀態 ---
|
| 220 |
+
let gridState = []; // 2D 陣列,記錄拼圖區每個格子的佔用情況
|
| 221 |
+
let placedBlocks = new Map(); // 記錄已放置的積木 (key: placedId, value: { el, originalId, type })
|
| 222 |
+
let draggingElement = null; // 目前正在拖曳的原始積木
|
| 223 |
+
let clone = null; // 拖曳時的複製物件
|
| 224 |
+
let offset = { x: 0, y: 0 }; // 拖曳時的滑鼠偏移量
|
| 225 |
+
|
| 226 |
+
// --- 核心函式 ---
|
| 227 |
+
|
| 228 |
+
function initLevel(levelIndex) {
|
| 229 |
+
currentLevel = levelIndex;
|
| 230 |
+
const level = levels[levelIndex];
|
| 231 |
+
const totalSize = level.a + level.b;
|
| 232 |
+
|
| 233 |
+
gridContainer.innerHTML = '';
|
| 234 |
+
blockPalette.innerHTML = '';
|
| 235 |
+
winMessageContainer.classList.add('hidden');
|
| 236 |
+
placedBlocks.clear();
|
| 237 |
+
|
| 238 |
+
gridContainer.style.width = `${totalSize * UNIT_SIZE}px`;
|
| 239 |
+
gridContainer.style.height = `${totalSize * UNIT_SIZE}px`;
|
| 240 |
+
gridContainer.style.setProperty('--unit-size', `${UNIT_SIZE}px`);
|
| 241 |
+
|
| 242 |
+
gridState = Array(totalSize).fill(null).map(() => Array(totalSize).fill(null));
|
| 243 |
+
|
| 244 |
+
createBlocks(level);
|
| 245 |
+
updateEquation();
|
| 246 |
+
|
| 247 |
+
if (currentLevel === levels.length - 1) {
|
| 248 |
+
nextLevelButton.textContent = '查看代數的秘密';
|
| 249 |
+
} else {
|
| 250 |
+
nextLevelButton.textContent = '挑戰下一關';
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
// 延遲執行以確保 DOM 尺寸已更新
|
| 254 |
+
setTimeout(updateScrollButtonsVisibility, 100);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
function createBlocks(level) {
|
| 258 |
+
const { a, b, distractors } = level;
|
| 259 |
+
const blocksData = [
|
| 260 |
+
{ id: 'a_squared', w: a, h: a, type: 'correct', class: 'block-a-squared', label: `${a}×${a}` },
|
| 261 |
+
{ id: 'b_squared', w: b, h: b, type: 'correct', class: 'block-b-squared', label: `${b}×${b}` },
|
| 262 |
+
{ id: 'ab1', w: a, h: b, type: 'correct', class: 'block-ab', label: `${a}×${b}` },
|
| 263 |
+
{ id: 'ab2', w: b, h: a, type: 'correct', class: 'block-ab', label: `${b}×${a}` },
|
| 264 |
+
...distractors.map((d, i) => ({
|
| 265 |
+
id: `distractor_${i}`, w: d.w, h: d.h, type: 'distractor', class: 'block-distractor', label: `${d.w}×${d.h}`
|
| 266 |
+
}))
|
| 267 |
+
];
|
| 268 |
+
|
| 269 |
+
blocksData.sort(() => Math.random() - 0.5);
|
| 270 |
+
|
| 271 |
+
blocksData.forEach(data => {
|
| 272 |
+
const blockEl = document.createElement('div');
|
| 273 |
+
blockEl.id = `palette-${data.id}`;
|
| 274 |
+
blockEl.className = `palette-block flex-shrink-0 ${data.class} rounded-md shadow-sm cursor-grab flex items-center justify-center text-white font-bold text-base`;
|
| 275 |
+
blockEl.style.width = `${data.w * UNIT_SIZE}px`;
|
| 276 |
+
blockEl.style.height = `${data.h * UNIT_SIZE}px`;
|
| 277 |
+
blockEl.textContent = data.label;
|
| 278 |
+
|
| 279 |
+
blockEl.dataset.id = data.id;
|
| 280 |
+
blockEl.dataset.w = data.w;
|
| 281 |
+
blockEl.dataset.h = data.h;
|
| 282 |
+
blockEl.dataset.type = data.type;
|
| 283 |
+
blockEl.dataset.class = data.class;
|
| 284 |
+
|
| 285 |
+
blockPalette.appendChild(blockEl);
|
| 286 |
+
});
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
function updateEquation() {
|
| 290 |
+
const level = levels[currentLevel];
|
| 291 |
+
equationTitle.textContent = `(${level.a} + ${level.b})²`;
|
| 292 |
+
|
| 293 |
+
const placedCorrectBlocks = Array.from(placedBlocks.values()).filter(b => b.type === 'correct');
|
| 294 |
+
|
| 295 |
+
let parts = [];
|
| 296 |
+
if (placedCorrectBlocks.some(b => b.originalId === 'a_squared')) parts.push(`${level.a}²`);
|
| 297 |
+
if (placedCorrectBlocks.some(b => b.originalId === 'b_squared')) parts.push(`${level.b}²`);
|
| 298 |
+
|
| 299 |
+
const abCount = placedCorrectBlocks.filter(b => b.originalId.startsWith('ab')).length;
|
| 300 |
+
if (abCount === 1) parts.push(`(${level.a}×${level.b})`);
|
| 301 |
+
if (abCount === 2) parts.push(`2(${level.a}×${level.b})`);
|
| 302 |
+
|
| 303 |
+
if (parts.length > 0) {
|
| 304 |
+
equationResult.innerHTML = parts.join(' + ');
|
| 305 |
+
} else {
|
| 306 |
+
equationResult.innerHTML = '...';
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
// --- 拖曳事件處理 ---
|
| 311 |
+
|
| 312 |
+
function onDragStart(e) {
|
| 313 |
+
const target = e.target.closest('.palette-block');
|
| 314 |
+
if (!target || target.classList.contains('placed')) return;
|
| 315 |
+
|
| 316 |
+
e.preventDefault();
|
| 317 |
+
draggingElement = target;
|
| 318 |
+
|
| 319 |
+
const rect = target.getBoundingClientRect();
|
| 320 |
+
const pointer = getPointer(e);
|
| 321 |
+
offset.x = pointer.x - rect.left;
|
| 322 |
+
offset.y = pointer.y - rect.top;
|
| 323 |
+
|
| 324 |
+
clone = target.cloneNode(true);
|
| 325 |
+
clone.classList.remove('palette-block');
|
| 326 |
+
clone.classList.add('dragging-clone');
|
| 327 |
+
clone.style.width = `${target.dataset.w * UNIT_SIZE}px`;
|
| 328 |
+
clone.style.height = `${target.dataset.h * UNIT_SIZE}px`;
|
| 329 |
+
document.body.appendChild(clone);
|
| 330 |
+
|
| 331 |
+
moveClone(pointer.x, pointer.y);
|
| 332 |
+
|
| 333 |
+
document.addEventListener('mousemove', onDragMove);
|
| 334 |
+
document.addEventListener('touchmove', onDragMove, { passive: false });
|
| 335 |
+
document.addEventListener('mouseup', onDragEnd);
|
| 336 |
+
document.addEventListener('touchend', onDragEnd);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
function onDragMove(e) {
|
| 340 |
+
if (!clone) return;
|
| 341 |
+
e.preventDefault();
|
| 342 |
+
const pointer = getPointer(e);
|
| 343 |
+
moveClone(pointer.x, pointer.y);
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
function onDragEnd(e) {
|
| 347 |
+
if (!draggingElement || !clone) return;
|
| 348 |
+
|
| 349 |
+
const gridRect = gridContainer.getBoundingClientRect();
|
| 350 |
+
const pointer = getPointer(e);
|
| 351 |
+
|
| 352 |
+
const blockTopLeftX = pointer.x - gridRect.left - offset.x;
|
| 353 |
+
const blockTopLeftY = pointer.y - gridRect.top - offset.y;
|
| 354 |
+
|
| 355 |
+
const gridX = Math.round(blockTopLeftX / UNIT_SIZE);
|
| 356 |
+
const gridY = Math.round(blockTopLeftY / UNIT_SIZE);
|
| 357 |
+
|
| 358 |
+
const w = parseInt(draggingElement.dataset.w);
|
| 359 |
+
const h = parseInt(draggingElement.dataset.h);
|
| 360 |
+
const type = draggingElement.dataset.type;
|
| 361 |
+
|
| 362 |
+
if (canPlace(gridX, gridY, w, h, type)) {
|
| 363 |
+
placeBlock(gridX, gridY, w, h);
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
document.body.removeChild(clone);
|
| 367 |
+
clone = null;
|
| 368 |
+
draggingElement = null;
|
| 369 |
+
document.removeEventListener('mousemove', onDragMove);
|
| 370 |
+
document.removeEventListener('touchmove', onDragMove);
|
| 371 |
+
document.removeEventListener('mouseup', onDragEnd);
|
| 372 |
+
document.removeEventListener('touchend', onDragEnd);
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
function canPlace(gridX, gridY, w, h, type) {
|
| 376 |
+
const totalSize = levels[currentLevel].a + levels[currentLevel].b;
|
| 377 |
+
|
| 378 |
+
if (currentLevel === 1 && type === 'distractor') {
|
| 379 |
+
return false;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
if (gridX < 0 || gridY < 0 || gridX + w > totalSize || gridY + h > totalSize) {
|
| 383 |
+
return false;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
for (let i = gridY; i < gridY + h; i++) {
|
| 387 |
+
for (let j = gridX; j < gridX + w; j++) {
|
| 388 |
+
if (i >= totalSize || j >= totalSize || gridState[i][j] !== null) {
|
| 389 |
+
return false;
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
return true;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
function placeBlock(gridX, gridY, w, h) {
|
| 397 |
+
const placedId = `placed-${Date.now()}`;
|
| 398 |
+
|
| 399 |
+
for (let i = gridY; i < gridY + h; i++) {
|
| 400 |
+
for (let j = gridX; j < gridX + w; j++) {
|
| 401 |
+
gridState[i][j] = placedId;
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
const placedEl = document.createElement('div');
|
| 406 |
+
placedEl.id = placedId;
|
| 407 |
+
placedEl.className = `placed-block ${draggingElement.dataset.class} flex items-center justify-center text-white font-bold`;
|
| 408 |
+
placedEl.style.left = `${gridX * UNIT_SIZE}px`;
|
| 409 |
+
placedEl.style.top = `${gridY * UNIT_SIZE}px`;
|
| 410 |
+
placedEl.style.width = `${w * UNIT_SIZE}px`;
|
| 411 |
+
placedEl.style.height = `${h * UNIT_SIZE}px`;
|
| 412 |
+
placedEl.textContent = `${w}×${h}`;
|
| 413 |
+
gridContainer.appendChild(placedEl);
|
| 414 |
+
|
| 415 |
+
placedBlocks.set(placedId, {
|
| 416 |
+
el: placedEl,
|
| 417 |
+
originalId: draggingElement.dataset.id,
|
| 418 |
+
type: draggingElement.dataset.type,
|
| 419 |
+
gridX, gridY, w, h
|
| 420 |
+
});
|
| 421 |
+
|
| 422 |
+
draggingElement.classList.add('placed');
|
| 423 |
+
placedEl.addEventListener('click', () => removeBlock(placedId));
|
| 424 |
+
|
| 425 |
+
updateEquation();
|
| 426 |
+
checkWinCondition();
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
function removeBlock(placedId) {
|
| 430 |
+
const block = placedBlocks.get(placedId);
|
| 431 |
+
if (!block) return;
|
| 432 |
+
|
| 433 |
+
gridContainer.removeChild(block.el);
|
| 434 |
+
|
| 435 |
+
for (let i = block.gridY; i < block.gridY + block.h; i++) {
|
| 436 |
+
for (let j = block.gridX; j < block.gridX + block.w; j++) {
|
| 437 |
+
if (gridState[i][j] === placedId) {
|
| 438 |
+
gridState[i][j] = null;
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
placedBlocks.delete(placedId);
|
| 444 |
+
|
| 445 |
+
const originalBlock = document.getElementById(`palette-${block.originalId}`);
|
| 446 |
+
if (originalBlock) {
|
| 447 |
+
originalBlock.classList.remove('placed');
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
updateEquation();
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
function checkWinCondition() {
|
| 454 |
+
const level = levels[currentLevel];
|
| 455 |
+
const totalSize = level.a + level.b;
|
| 456 |
+
|
| 457 |
+
const placedCorrectCount = Array.from(placedBlocks.values()).filter(b => b.type === 'correct').length;
|
| 458 |
+
|
| 459 |
+
if (placedCorrectCount !== 4) return;
|
| 460 |
+
|
| 461 |
+
let isFull = true;
|
| 462 |
+
for (let i = 0; i < totalSize; i++) {
|
| 463 |
+
for (let j = 0; j < totalSize; j++) {
|
| 464 |
+
if (gridState[i][j] === null) {
|
| 465 |
+
isFull = false;
|
| 466 |
+
break;
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
if (!isFull) break;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
if (isFull) {
|
| 473 |
+
showWinState();
|
| 474 |
+
}
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
function showWinState() {
|
| 478 |
+
const level = levels[currentLevel];
|
| 479 |
+
const finalFormula = `(${level.a}+${level.b})² = ${level.a}² + <span class="highlight-win">2×${level.a}×${level.b}</span> + ${level.b}²`;
|
| 480 |
+
winFormulaExplanation.innerHTML = finalFormula;
|
| 481 |
+
winMessageContainer.classList.remove('hidden');
|
| 482 |
+
|
| 483 |
+
blockPalette.style.pointerEvents = 'none';
|
| 484 |
+
gridContainer.style.pointerEvents = 'none';
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
// --- 捲動與輔助函式 ---
|
| 488 |
+
|
| 489 |
+
function updateScrollButtonsVisibility() {
|
| 490 |
+
const palette = blockPalette;
|
| 491 |
+
const scrollLeft = palette.scrollLeft;
|
| 492 |
+
const scrollWidth = palette.scrollWidth;
|
| 493 |
+
const clientWidth = palette.clientWidth;
|
| 494 |
+
|
| 495 |
+
if (scrollWidth <= clientWidth) {
|
| 496 |
+
scrollLeftButton.classList.add('hidden');
|
| 497 |
+
scrollRightButton.classList.add('hidden');
|
| 498 |
+
return;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
if (scrollLeft > 0) {
|
| 502 |
+
scrollLeftButton.classList.remove('hidden');
|
| 503 |
+
} else {
|
| 504 |
+
scrollLeftButton.classList.add('hidden');
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
if (scrollLeft < scrollWidth - clientWidth - 1) {
|
| 508 |
+
scrollRightButton.classList.remove('hidden');
|
| 509 |
+
} else {
|
| 510 |
+
scrollRightButton.classList.add('hidden');
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
function getPointer(e) {
|
| 515 |
+
if (e.touches && e.touches.length > 0) {
|
| 516 |
+
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
| 517 |
+
}
|
| 518 |
+
return { x: e.clientX, y: e.clientY };
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
function moveClone(x, y) {
|
| 522 |
+
if (!clone) return;
|
| 523 |
+
clone.style.left = `${x - offset.x}px`;
|
| 524 |
+
clone.style.top = `${y - offset.y}px`;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
// --- 事件監聽 ---
|
| 528 |
+
startButton.addEventListener('click', () => {
|
| 529 |
+
startScreen.style.opacity = '0';
|
| 530 |
+
gameContainer.classList.remove('hidden');
|
| 531 |
+
setTimeout(() => {
|
| 532 |
+
startScreen.classList.add('hidden');
|
| 533 |
+
gameContainer.style.opacity = '1';
|
| 534 |
+
initLevel(currentLevel);
|
| 535 |
+
}, 500);
|
| 536 |
+
});
|
| 537 |
+
|
| 538 |
+
blockPalette.addEventListener('mousedown', onDragStart);
|
| 539 |
+
blockPalette.addEventListener('touchstart', onDragStart, { passive: false });
|
| 540 |
+
blockPalette.addEventListener('scroll', updateScrollButtonsVisibility);
|
| 541 |
+
window.addEventListener('resize', updateScrollButtonsVisibility);
|
| 542 |
+
|
| 543 |
+
scrollLeftButton.addEventListener('click', () => {
|
| 544 |
+
blockPalette.scrollBy({ left: -200, behavior: 'smooth' });
|
| 545 |
+
});
|
| 546 |
+
|
| 547 |
+
scrollRightButton.addEventListener('click', () => {
|
| 548 |
+
blockPalette.scrollBy({ left: 200, behavior: 'smooth' });
|
| 549 |
+
});
|
| 550 |
+
|
| 551 |
+
nextLevelButton.addEventListener('click', () => {
|
| 552 |
+
if (currentLevel < levels.length - 1) {
|
| 553 |
+
initLevel(currentLevel + 1);
|
| 554 |
+
blockPalette.style.pointerEvents = 'auto';
|
| 555 |
+
gridContainer.style.pointerEvents = 'auto';
|
| 556 |
+
} else {
|
| 557 |
+
// 顯示秘密畫面
|
| 558 |
+
gameContainer.style.opacity = '0';
|
| 559 |
+
secretView.classList.remove('hidden');
|
| 560 |
+
setTimeout(() => {
|
| 561 |
+
gameContainer.classList.add('hidden');
|
| 562 |
+
secretView.style.opacity = '1';
|
| 563 |
+
}, 500);
|
| 564 |
+
}
|
| 565 |
+
});
|
| 566 |
+
|
| 567 |
+
});
|
| 568 |
+
</script>
|
| 569 |
+
|
| 570 |
+
</body>
|
| 571 |
+
</html>
|
gemstone.html
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-Hant">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>數學探險島 - 寶石洞窟</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
body {
|
| 13 |
+
font-family: 'Noto Sans TC', sans-serif;
|
| 14 |
+
background-image: url('https://i.meee.com.tw/pI0c5ue.png');
|
| 15 |
+
background-size: cover;
|
| 16 |
+
background-position: center;
|
| 17 |
+
}
|
| 18 |
+
canvas {
|
| 19 |
+
background-color: #2d241d;
|
| 20 |
+
display: block;
|
| 21 |
+
border-radius: 0.5rem;
|
| 22 |
+
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
|
| 23 |
+
}
|
| 24 |
+
.action-button {
|
| 25 |
+
transition: all 0.2s ease-in-out;
|
| 26 |
+
box-shadow: 0 5px #995f00;
|
| 27 |
+
}
|
| 28 |
+
.action-button:active {
|
| 29 |
+
transform: translateY(3px);
|
| 30 |
+
box-shadow: 0 2px #995f00;
|
| 31 |
+
}
|
| 32 |
+
.move-button {
|
| 33 |
+
transition: all 0.2s ease-in-out;
|
| 34 |
+
box-shadow: 0 5px #1e3a8a;
|
| 35 |
+
}
|
| 36 |
+
.move-button:active {
|
| 37 |
+
transform: translateY(3px);
|
| 38 |
+
box-shadow: 0 2px #1e3a8a;
|
| 39 |
+
}
|
| 40 |
+
.gem-button {
|
| 41 |
+
transition: transform 0.1s ease-in-out;
|
| 42 |
+
}
|
| 43 |
+
.gem-button:active {
|
| 44 |
+
transform: scale(0.9);
|
| 45 |
+
}
|
| 46 |
+
.secret-bg {
|
| 47 |
+
background-image: url('https://i.meee.com.tw/EKZpYKI.png');
|
| 48 |
+
background-size: cover;
|
| 49 |
+
background-position: center;
|
| 50 |
+
}
|
| 51 |
+
</style>
|
| 52 |
+
</head>
|
| 53 |
+
<body class="flex items-center justify-center h-screen overflow-hidden">
|
| 54 |
+
|
| 55 |
+
<div id="container" class="w-full max-w-md mx-auto text-white p-4 flex flex-col h-full">
|
| 56 |
+
|
| 57 |
+
<!-- 引導畫面 -->
|
| 58 |
+
<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">
|
| 59 |
+
<h1 class="text-4xl font-bold text-amber-300 mb-4">寶石洞窟</h1>
|
| 60 |
+
<p class="text-lg text-gray-200 mb-8">這是一個已經廢棄的寶石洞窟,接下來你必須駕駛一台礦車,成功閃避障礙物來獲得璀璨寶石!</p>
|
| 61 |
+
<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">
|
| 62 |
+
開始採礦
|
| 63 |
+
</button>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<!-- 遊戲畫面 -->
|
| 67 |
+
<div id="game-screen" class="hidden flex-col h-full">
|
| 68 |
+
<div id="canvas-wrapper" class="relative">
|
| 69 |
+
<canvas id="gameCanvas"></canvas>
|
| 70 |
+
<!-- 操作說明畫面 -->
|
| 71 |
+
<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">
|
| 72 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">操作說明</h2>
|
| 73 |
+
<div class="space-y-4 text-lg">
|
| 74 |
+
<p>點擊畫面<span class="text-blue-400 font-bold">左半邊</span>或按<span class="text-blue-400 font-bold">左方向鍵</span> ← 向左移動</p>
|
| 75 |
+
<p>點擊畫面<span class="text-blue-400 font-bold">右半邊</span>或按<span class="text-blue-400 font-bold">右方向鍵</span> → 向右移動</p>
|
| 76 |
+
<p class="mt-4">也可以使用下方的按鈕操作!</p>
|
| 77 |
+
</div>
|
| 78 |
+
<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">
|
| 79 |
+
了解!
|
| 80 |
+
</button>
|
| 81 |
+
</div>
|
| 82 |
+
<!-- 遊戲結束畫面 -->
|
| 83 |
+
<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">
|
| 84 |
+
<h2 class="text-4xl font-bold text-red-500 mb-4">挑戰失敗!</h2>
|
| 85 |
+
<p class="text-xl mb-6">礦車撞毀了!</p>
|
| 86 |
+
<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">
|
| 87 |
+
重新挑戰
|
| 88 |
+
</button>
|
| 89 |
+
</div>
|
| 90 |
+
<!-- 遊戲獲勝畫面 -->
|
| 91 |
+
<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">
|
| 92 |
+
<h2 class="text-4xl font-bold text-green-400 mb-4">成功!</h2>
|
| 93 |
+
<p class="text-xl mb-2">你成功抵達了寶石區域!</p>
|
| 94 |
+
<p id="win-perfect-dodges" class="text-2xl text-amber-300 font-bold mb-6"></p>
|
| 95 |
+
<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">
|
| 96 |
+
繼續前進
|
| 97 |
+
</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
<!-- 移動按鈕 -->
|
| 101 |
+
<div id="controls" class="flex justify-between mt-4">
|
| 102 |
+
<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">
|
| 103 |
+
<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>
|
| 104 |
+
</button>
|
| 105 |
+
<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">
|
| 106 |
+
<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>
|
| 107 |
+
</button>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- 寶石解謎畫面 -->
|
| 112 |
+
<div id="puzzle-screen" class="hidden flex-col h-full p-6 bg-black bg-opacity-60 rounded-lg">
|
| 113 |
+
<div class="flex-grow overflow-y-auto">
|
| 114 |
+
<h1 class="text-3xl font-bold text-amber-300 text-center mb-4">寶石組合挑戰</h1>
|
| 115 |
+
<p class="text-center mb-4">觀察大寶石,點擊下方的小寶石,組合出正確的配方!</p>
|
| 116 |
+
|
| 117 |
+
<div class="flex justify-center mb-4">
|
| 118 |
+
<img id="large-gem-image" src="" class="h-40 object-contain" alt="[大寶石的Image]">
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
<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>
|
| 122 |
+
|
| 123 |
+
<div id="small-gem-options" class="flex justify-center items-center gap-4 mb-6 flex-wrap"></div>
|
| 124 |
+
|
| 125 |
+
<div id="puzzle-feedback" class="text-center text-xl font-bold min-h-[32px] mb-4"></div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<div class="flex gap-4 mt-auto pt-4">
|
| 129 |
+
<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>
|
| 130 |
+
<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>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<!-- 過場畫面 -->
|
| 135 |
+
<div id="intermission-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto">
|
| 136 |
+
<h1 class="text-3xl font-bold text-amber-300 mb-4">準備挑戰下一區!</h1>
|
| 137 |
+
<p class="text-lg text-gray-200 mb-8">前方的礦道更加危險,速度會更快!</p>
|
| 138 |
+
<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">
|
| 139 |
+
繼續採礦
|
| 140 |
+
</button>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<!-- 最終通關畫面 -->
|
| 144 |
+
<div id="final-win-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto">
|
| 145 |
+
<h1 class="text-4xl font-bold text-green-400 mb-4">恭喜你完成所有寶石挑戰!</h1>
|
| 146 |
+
<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">
|
| 147 |
+
解鎖寶石洞窟的秘密
|
| 148 |
+
</button>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
<!-- 秘密揭曉畫面 (獨立於 container 之外) -->
|
| 153 |
+
<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">
|
| 154 |
+
<div class="w-full max-w-2xl bg-black bg-opacity-70 p-8 rounded-xl">
|
| 155 |
+
<div id="secret-question">
|
| 156 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">你認為這個洞窟的秘密是...</h2>
|
| 157 |
+
<div class="space-y-4 text-lg">
|
| 158 |
+
<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>
|
| 159 |
+
<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>
|
| 160 |
+
<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>
|
| 161 |
+
</div>
|
| 162 |
+
<p id="secret-feedback" class="mt-6 min-h-[28px] text-xl"></p>
|
| 163 |
+
</div>
|
| 164 |
+
<div id="secret-reveal" class="hidden text-left space-y-4">
|
| 165 |
+
<p class="text-lg">從最小的寶石到浩瀚的星辰,萬事萬物都由更基本的元素構成。你學會的『寶石分析術』,在數學的世界裡,它被稱為『因式分解』。</p>
|
| 166 |
+
<p class="text-2xl font-bold text-amber-300">這個洞窟的秘密就是:學會分析與拆解,就是看透事物本質的第一步。</p>
|
| 167 |
+
<p class="text-lg">無論是分析一顆寶石的成分、一道數學題的結構,還是未來你遇到的任何複雜問題,這種化繁為簡的智慧,才是你從這裡帶走、永不消失的真正寶藏。</p>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="mt-12 text-center">
|
| 170 |
+
<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">
|
| 171 |
+
回到探險島地圖
|
| 172 |
+
</a>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
<script>
|
| 179 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 180 |
+
// --- 畫面元素 ---
|
| 181 |
+
const container = document.getElementById('container');
|
| 182 |
+
const startScreen = document.getElementById('start-screen');
|
| 183 |
+
const gameScreen = document.getElementById('game-screen');
|
| 184 |
+
const puzzleScreen = document.getElementById('puzzle-screen');
|
| 185 |
+
const intermissionScreen = document.getElementById('intermission-screen');
|
| 186 |
+
const finalWinScreen = document.getElementById('final-win-screen');
|
| 187 |
+
const secretScreen = document.getElementById('secret-screen');
|
| 188 |
+
const startButton = document.getElementById('start-button');
|
| 189 |
+
const instructionsScreen = document.getElementById('instructions-screen');
|
| 190 |
+
const playGameButton = document.getElementById('play-game-button');
|
| 191 |
+
const gameOverScreen = document.getElementById('game-over-screen');
|
| 192 |
+
const winScreen = document.getElementById('win-screen');
|
| 193 |
+
const winPerfectDodges = document.getElementById('win-perfect-dodges');
|
| 194 |
+
const restartButton = document.getElementById('restart-button');
|
| 195 |
+
const continueButton = document.getElementById('continue-button');
|
| 196 |
+
const startNextStageButton = document.getElementById('start-next-stage-button');
|
| 197 |
+
const unlockSecretButton = document.getElementById('unlock-secret-button');
|
| 198 |
+
const canvas = document.getElementById('gameCanvas');
|
| 199 |
+
const canvasWrapper = document.getElementById('canvas-wrapper');
|
| 200 |
+
const moveLeftButton = document.getElementById('move-left-button');
|
| 201 |
+
const moveRightButton = document.getElementById('move-right-button');
|
| 202 |
+
const ctx = canvas.getContext('2d');
|
| 203 |
+
|
| 204 |
+
// --- 寶石解謎元素 ---
|
| 205 |
+
const largeGemImage = document.getElementById('large-gem-image');
|
| 206 |
+
const answerSlots = document.getElementById('answer-slots');
|
| 207 |
+
const smallGemOptions = document.getElementById('small-gem-options');
|
| 208 |
+
const puzzleFeedback = document.getElementById('puzzle-feedback');
|
| 209 |
+
const resetPuzzleButton = document.getElementById('reset-puzzle-button');
|
| 210 |
+
const checkPuzzleButton = document.getElementById('check-puzzle-button');
|
| 211 |
+
|
| 212 |
+
// --- 秘密揭曉元素 ---
|
| 213 |
+
const secretQuestion = document.getElementById('secret-question');
|
| 214 |
+
const secretReveal = document.getElementById('secret-reveal');
|
| 215 |
+
const secretFeedback = document.getElementById('secret-feedback');
|
| 216 |
+
const secretChoiceButtons = document.querySelectorAll('.secret-choice');
|
| 217 |
+
|
| 218 |
+
// --- 遊戲設定 ---
|
| 219 |
+
const LANE_COUNT = 3;
|
| 220 |
+
let laneWidth;
|
| 221 |
+
let gameInterval, timer, obstacleInterval;
|
| 222 |
+
let timeLeft;
|
| 223 |
+
let perfectDodges = 0;
|
| 224 |
+
const perfectDodgeTexts = [];
|
| 225 |
+
let keySequence = [];
|
| 226 |
+
const cheatCode = "kkkkk";
|
| 227 |
+
let currentStage = 0;
|
| 228 |
+
let currentStageSettings;
|
| 229 |
+
|
| 230 |
+
const gameStages = [
|
| 231 |
+
{ speed: 2.5, time: 20, spawnRate: 1800 },
|
| 232 |
+
{ speed: 3.5, time: 15, spawnRate: 1200 },
|
| 233 |
+
{ speed: 4.0, time: 12, spawnRate: 1000 }
|
| 234 |
+
];
|
| 235 |
+
|
| 236 |
+
// --- 寶石解謎設定 ---
|
| 237 |
+
const allSmallGems = [
|
| 238 |
+
{ id: 1, src: 'https://i.meee.com.tw/FeaxgIn.png' },
|
| 239 |
+
{ id: 2, src: 'https://i.meee.com.tw/VgnnNxo.png' },
|
| 240 |
+
{ id: 3, src: 'https://i.meee.com.tw/5vu7Qk1.png' },
|
| 241 |
+
{ id: 4, src: 'https://i.meee.com.tw/eoel84q.png' },
|
| 242 |
+
{ id: 5, src: 'https://i.meee.com.tw/b2uL22E.png' },
|
| 243 |
+
{ id: 6, src: 'https://i.meee.com.tw/HeduyDE.png' },
|
| 244 |
+
{ id: 7, src: 'https://i.meee.com.tw/2gorePd.png' },
|
| 245 |
+
{ id: 8, src: 'https://i.meee.com.tw/HNNEM09.png' },
|
| 246 |
+
{ id: 9, src: 'https://i.meee.com.tw/cpw8ccy.png' },
|
| 247 |
+
{ id: 10, src: 'https://i.meee.com.tw/Rk8hY7H.png' }
|
| 248 |
+
];
|
| 249 |
+
|
| 250 |
+
const puzzleLevels = [
|
| 251 |
+
{
|
| 252 |
+
largeGemSrc: 'https://i.meee.com.tw/PU1busV.png',
|
| 253 |
+
options: [1, 2, 3],
|
| 254 |
+
answer: { 1: 1, 2: 3, 3: 1 },
|
| 255 |
+
maxGems: 5
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
largeGemSrc: 'https://i.meee.com.tw/JCK7B2S.png',
|
| 259 |
+
options: [2, 3, 4, 5, 6, 7],
|
| 260 |
+
answer: { 3: 2, 7: 2, 2: 1, 4: 1, 5: 1, 6: 1 },
|
| 261 |
+
maxGems: 8
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
largeGemSrc: 'https://i.meee.com.tw/wFJquPy.png',
|
| 265 |
+
options: [2, 3, 4, 5, 6, 8, 9, 10],
|
| 266 |
+
answer: { 8: 3, 10: 3, 5: 6, 9: 1 },
|
| 267 |
+
maxGems: 13
|
| 268 |
+
}
|
| 269 |
+
];
|
| 270 |
+
let playerSelection = {};
|
| 271 |
+
|
| 272 |
+
// --- 玩家設定 ---
|
| 273 |
+
const player = {
|
| 274 |
+
x: 0, y: 0, width: 100, height: 100, lane: 1, image: new Image()
|
| 275 |
+
};
|
| 276 |
+
const normalCartSrc = 'https://i.meee.com.tw/w6krwUz.png';
|
| 277 |
+
const crashedCartSrc = 'https://i.meee.com.tw/4wSy5uX.png';
|
| 278 |
+
player.image.src = normalCartSrc;
|
| 279 |
+
player.image.onerror = () => { console.error("礦車圖片載入失敗!"); };
|
| 280 |
+
|
| 281 |
+
// --- 障礙物設定 ---
|
| 282 |
+
const obstacles = [];
|
| 283 |
+
const trackTies = [];
|
| 284 |
+
const obstacleImage = new Image();
|
| 285 |
+
obstacleImage.src = 'https://i.meee.com.tw/L0tV6le.png';
|
| 286 |
+
obstacleImage.onerror = () => { console.error("障礙物圖片載入失敗!"); };
|
| 287 |
+
let lastTieY = 0;
|
| 288 |
+
const TIE_SPACING = 50;
|
| 289 |
+
|
| 290 |
+
// --- 畫面管理 ---
|
| 291 |
+
function showScreen(screenId) {
|
| 292 |
+
['start-screen', 'game-screen', 'puzzle-screen', 'intermission-screen', 'final-win-screen'].forEach(id => {
|
| 293 |
+
const screen = document.getElementById(id);
|
| 294 |
+
if (id === screenId) {
|
| 295 |
+
screen.style.display = 'flex';
|
| 296 |
+
screen.classList.remove('hidden');
|
| 297 |
+
if(id === 'start-screen' || id === 'intermission-screen' || id === 'final-win-screen') {
|
| 298 |
+
screen.classList.add('my-auto');
|
| 299 |
+
} else {
|
| 300 |
+
screen.classList.remove('my-auto');
|
| 301 |
+
}
|
| 302 |
+
} else {
|
| 303 |
+
screen.style.display = 'none';
|
| 304 |
+
screen.classList.add('hidden');
|
| 305 |
+
}
|
| 306 |
+
});
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
// --- 礦車遊戲函式 ---
|
| 310 |
+
function resizeCanvas() {
|
| 311 |
+
const container = document.getElementById('container');
|
| 312 |
+
const controls = document.getElementById('controls');
|
| 313 |
+
canvas.width = container.clientWidth;
|
| 314 |
+
canvas.height = window.innerHeight * 0.9 - controls.offsetHeight - 20;
|
| 315 |
+
canvasWrapper.style.width = `${canvas.width}px`;
|
| 316 |
+
canvasWrapper.style.height = `${canvas.height}px`;
|
| 317 |
+
laneWidth = canvas.width / LANE_COUNT;
|
| 318 |
+
player.y = canvas.height - player.height - 10;
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
function getLaneCenterX(lane) {
|
| 322 |
+
return (lane * laneWidth) + (laneWidth / 2);
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
function spawnTrackTies() {
|
| 326 |
+
while (lastTieY < canvas.height + TIE_SPACING) {
|
| 327 |
+
trackTies.push({ y: lastTieY });
|
| 328 |
+
lastTieY += TIE_SPACING;
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
function drawTrack() {
|
| 333 |
+
ctx.fillStyle = '#57402c';
|
| 334 |
+
trackTies.forEach(tie => {
|
| 335 |
+
for (let i = 0; i < LANE_COUNT; i++) {
|
| 336 |
+
const laneX = getLaneCenterX(i);
|
| 337 |
+
const rail1X = laneX - laneWidth * 0.25;
|
| 338 |
+
const rail2X = laneX + laneWidth * 0.25;
|
| 339 |
+
ctx.fillRect(rail1X, tie.y, rail2X - rail1X, 10);
|
| 340 |
+
}
|
| 341 |
+
});
|
| 342 |
+
const railColor = '#858585', highlightColor = '#b0b0b0', railWidth = 8;
|
| 343 |
+
for (let i = 0; i < LANE_COUNT; i++) {
|
| 344 |
+
const laneX = getLaneCenterX(i);
|
| 345 |
+
const rail1X = laneX - laneWidth * 0.25;
|
| 346 |
+
const rail2X = laneX + laneWidth * 0.25;
|
| 347 |
+
ctx.fillStyle = railColor;
|
| 348 |
+
ctx.fillRect(rail1X - railWidth / 2, 0, railWidth, canvas.height);
|
| 349 |
+
ctx.fillRect(rail2X - railWidth / 2, 0, railWidth, canvas.height);
|
| 350 |
+
ctx.fillStyle = highlightColor;
|
| 351 |
+
ctx.fillRect(rail1X - railWidth / 2, 0, railWidth / 2, canvas.height);
|
| 352 |
+
ctx.fillRect(rail2X - railWidth / 2, 0, railWidth / 2, canvas.height);
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
function drawPlayer() {
|
| 357 |
+
player.x = getLaneCenterX(player.lane) - player.width / 2;
|
| 358 |
+
if (player.image.complete && player.image.naturalHeight !== 0) {
|
| 359 |
+
ctx.drawImage(player.image, player.x, player.y, player.width, player.height);
|
| 360 |
+
} else {
|
| 361 |
+
ctx.fillStyle = 'blue';
|
| 362 |
+
ctx.fillRect(player.x, player.y, player.width, player.height);
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
function drawObstacles() {
|
| 367 |
+
obstacles.forEach(obstacle => {
|
| 368 |
+
if (obstacleImage.complete && obstacleImage.naturalHeight !== 0) {
|
| 369 |
+
ctx.drawImage(obstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height);
|
| 370 |
+
} else {
|
| 371 |
+
ctx.fillStyle = '#a16207';
|
| 372 |
+
ctx.beginPath();
|
| 373 |
+
ctx.roundRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height, [10]);
|
| 374 |
+
ctx.fill();
|
| 375 |
+
}
|
| 376 |
+
});
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
function drawUI() {
|
| 380 |
+
ctx.fillStyle = 'white';
|
| 381 |
+
ctx.font = 'bold 20px "Noto Sans TC"';
|
| 382 |
+
ctx.textAlign = 'center';
|
| 383 |
+
ctx.fillText(`倒數計時: ${timeLeft.toFixed(1)} 秒`, canvas.width / 2, 30);
|
| 384 |
+
ctx.textAlign = 'right';
|
| 385 |
+
ctx.fillText(`完美閃避: ${perfectDodges}`, canvas.width - 20, 30);
|
| 386 |
+
perfectDodgeTexts.forEach(text => {
|
| 387 |
+
ctx.save();
|
| 388 |
+
ctx.globalAlpha = text.alpha;
|
| 389 |
+
ctx.fillStyle = '#fde047';
|
| 390 |
+
ctx.font = 'bold 24px "Noto Sans TC"';
|
| 391 |
+
ctx.textAlign = 'center';
|
| 392 |
+
ctx.fillText(text.text, text.x, text.y);
|
| 393 |
+
ctx.restore();
|
| 394 |
+
});
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
function update() {
|
| 398 |
+
for (let i = trackTies.length - 1; i >= 0; i--) {
|
| 399 |
+
trackTies[i].y += currentStageSettings.speed;
|
| 400 |
+
if (trackTies[i].y > canvas.height) {
|
| 401 |
+
trackTies.splice(i, 1);
|
| 402 |
+
trackTies.unshift({ y: (trackTies[0]?.y || 0) - TIE_SPACING });
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
for (let i = obstacles.length - 1; i >= 0; i--) {
|
| 406 |
+
obstacles[i].y += currentStageSettings.speed;
|
| 407 |
+
if (obstacles[i].y > canvas.height) obstacles.splice(i, 1);
|
| 408 |
+
}
|
| 409 |
+
for (let i = perfectDodgeTexts.length - 1; i >= 0; i--) {
|
| 410 |
+
perfectDodgeTexts[i].y -= 1;
|
| 411 |
+
perfectDodgeTexts[i].alpha -= 0.02;
|
| 412 |
+
if (perfectDodgeTexts[i].alpha <= 0) perfectDodgeTexts.splice(i, 1);
|
| 413 |
+
}
|
| 414 |
+
obstacles.forEach(obstacle => {
|
| 415 |
+
if (player.lane === obstacle.lane && player.y < obstacle.y + obstacle.height && player.y + player.height > obstacle.y) {
|
| 416 |
+
gameOver();
|
| 417 |
+
}
|
| 418 |
+
});
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
function draw() {
|
| 422 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 423 |
+
drawTrack();
|
| 424 |
+
drawObstacles();
|
| 425 |
+
drawPlayer();
|
| 426 |
+
drawUI();
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
function spawnObstacle() {
|
| 430 |
+
const lane = Math.floor(Math.random() * LANE_COUNT);
|
| 431 |
+
const size = laneWidth * 0.5;
|
| 432 |
+
const x = getLaneCenterX(lane) - size / 2;
|
| 433 |
+
obstacles.push({ x, y: -size, width: size, height: size, lane, dodged: false });
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
function gameLoop() {
|
| 437 |
+
update();
|
| 438 |
+
draw();
|
| 439 |
+
gameInterval = requestAnimationFrame(gameLoop);
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
function stopGame() {
|
| 443 |
+
if (gameInterval) cancelAnimationFrame(gameInterval);
|
| 444 |
+
if (timer) clearInterval(timer);
|
| 445 |
+
if (obstacleInterval) clearInterval(obstacleInterval);
|
| 446 |
+
gameInterval = timer = obstacleInterval = null;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
function startGame(stageIndex) {
|
| 450 |
+
currentStage = stageIndex;
|
| 451 |
+
currentStageSettings = gameStages[stageIndex];
|
| 452 |
+
player.image.src = normalCartSrc; // Reset to normal cart image
|
| 453 |
+
stopGame();
|
| 454 |
+
instructionsScreen.classList.add('hidden');
|
| 455 |
+
gameOverScreen.classList.add('hidden');
|
| 456 |
+
winScreen.classList.add('hidden');
|
| 457 |
+
resizeCanvas();
|
| 458 |
+
obstacles.length = 0;
|
| 459 |
+
trackTies.length = 0;
|
| 460 |
+
perfectDodgeTexts.length = 0;
|
| 461 |
+
perfectDodges = 0;
|
| 462 |
+
lastTieY = 0;
|
| 463 |
+
spawnTrackTies();
|
| 464 |
+
player.lane = 1;
|
| 465 |
+
timeLeft = currentStageSettings.time;
|
| 466 |
+
timer = setInterval(() => {
|
| 467 |
+
timeLeft -= 0.1;
|
| 468 |
+
if (timeLeft <= 0) {
|
| 469 |
+
timeLeft = 0;
|
| 470 |
+
winGame();
|
| 471 |
+
}
|
| 472 |
+
}, 100);
|
| 473 |
+
obstacleInterval = setInterval(spawnObstacle, currentStageSettings.spawnRate);
|
| 474 |
+
gameLoop();
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
function gameOver() {
|
| 478 |
+
stopGame();
|
| 479 |
+
player.image.src = crashedCartSrc; // Change to crashed cart image
|
| 480 |
+
draw(); // Redraw canvas one last time with the new image
|
| 481 |
+
gameOverScreen.classList.remove('hidden');
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
function winGame() {
|
| 485 |
+
stopGame();
|
| 486 |
+
winPerfectDodges.textContent = `完美閃避: ${perfectDodges} 次`;
|
| 487 |
+
winScreen.classList.remove('hidden');
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
function handleMove(direction) {
|
| 491 |
+
if (!gameInterval) return;
|
| 492 |
+
const oldLane = player.lane;
|
| 493 |
+
let newLane = player.lane;
|
| 494 |
+
if (direction === 'left') newLane = Math.max(0, player.lane - 1);
|
| 495 |
+
else if (direction === 'right') newLane = Math.min(LANE_COUNT - 1, player.lane + 1);
|
| 496 |
+
if (oldLane !== newLane) {
|
| 497 |
+
player.lane = newLane;
|
| 498 |
+
checkPerfectDodge(oldLane);
|
| 499 |
+
}
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
function checkPerfectDodge(fromLane) {
|
| 503 |
+
const perfectDodgeZoneTop = player.y - player.height / 2;
|
| 504 |
+
const perfectDodgeZoneBottom = player.y + player.height;
|
| 505 |
+
obstacles.forEach(obstacle => {
|
| 506 |
+
if (obstacle.lane === fromLane && !obstacle.dodged) {
|
| 507 |
+
if (obstacle.y + obstacle.height > perfectDodgeZoneTop && obstacle.y < perfectDodgeZoneBottom) {
|
| 508 |
+
perfectDodges++;
|
| 509 |
+
obstacle.dodged = true;
|
| 510 |
+
perfectDodgeTexts.push({ text: '完美!', x: getLaneCenterX(fromLane), y: player.y, alpha: 1 });
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
});
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
// --- 寶石解謎函式 ---
|
| 517 |
+
function showPuzzleScreen() {
|
| 518 |
+
showScreen('puzzle-screen');
|
| 519 |
+
loadPuzzleLevel(currentStage);
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
function loadPuzzleLevel(levelIndex) {
|
| 523 |
+
const level = puzzleLevels[levelIndex];
|
| 524 |
+
largeGemImage.src = level.largeGemSrc;
|
| 525 |
+
smallGemOptions.innerHTML = '';
|
| 526 |
+
level.options.forEach(gemId => {
|
| 527 |
+
const gemData = allSmallGems.find(g => g.id === gemId);
|
| 528 |
+
if (gemData) {
|
| 529 |
+
const gemBtn = document.createElement('button');
|
| 530 |
+
gemBtn.className = 'gem-button';
|
| 531 |
+
gemBtn.innerHTML = `<img src="${gemData.src}" alt="小寶石 ${gemData.id}" class="h-16 w-16 object-contain">`;
|
| 532 |
+
gemBtn.onclick = () => addGemToSelection(gemData);
|
| 533 |
+
smallGemOptions.appendChild(gemBtn);
|
| 534 |
+
}
|
| 535 |
+
});
|
| 536 |
+
resetPuzzle();
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
function addGemToSelection(gem) {
|
| 540 |
+
const currentTotal = Object.values(playerSelection).reduce((sum, count) => sum + count, 0);
|
| 541 |
+
const level = puzzleLevels[currentStage];
|
| 542 |
+
|
| 543 |
+
if (currentTotal >= level.maxGems) {
|
| 544 |
+
puzzleFeedback.textContent = '數量太多了喔!';
|
| 545 |
+
puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-yellow-400';
|
| 546 |
+
setTimeout(() => {
|
| 547 |
+
if(puzzleFeedback.textContent === '數量太多了喔!') {
|
| 548 |
+
puzzleFeedback.textContent = '';
|
| 549 |
+
}
|
| 550 |
+
}, 2000);
|
| 551 |
+
return;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
playerSelection[gem.id] = (playerSelection[gem.id] || 0) + 1;
|
| 555 |
+
renderSelection();
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
function removeGemFromSelection(gemId) {
|
| 559 |
+
if (playerSelection[gemId]) {
|
| 560 |
+
playerSelection[gemId]--;
|
| 561 |
+
if (playerSelection[gemId] === 0) {
|
| 562 |
+
delete playerSelection[gemId];
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
renderSelection();
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
function renderSelection() {
|
| 569 |
+
answerSlots.innerHTML = '';
|
| 570 |
+
puzzleLevels[currentStage].options.forEach(gemId => {
|
| 571 |
+
const gemData = allSmallGems.find(g => g.id === gemId);
|
| 572 |
+
if (playerSelection[gemId]) {
|
| 573 |
+
const count = playerSelection[gemId];
|
| 574 |
+
for (let i = 0; i < count; i++) {
|
| 575 |
+
const img = document.createElement('img');
|
| 576 |
+
img.src = gemData.src;
|
| 577 |
+
img.alt = `[已選擇的小寶石 ${gemData.id}]`;
|
| 578 |
+
img.className = 'h-12 w-12 object-contain cursor-pointer';
|
| 579 |
+
img.onclick = () => removeGemFromSelection(gemId);
|
| 580 |
+
answerSlots.appendChild(img);
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
});
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
function resetPuzzle() {
|
| 587 |
+
playerSelection = {};
|
| 588 |
+
answerSlots.innerHTML = '';
|
| 589 |
+
puzzleFeedback.textContent = '';
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
function checkPuzzleAnswer() {
|
| 593 |
+
const level = puzzleLevels[currentStage];
|
| 594 |
+
if (!level) return;
|
| 595 |
+
const answer = level.answer;
|
| 596 |
+
let correct = true;
|
| 597 |
+
|
| 598 |
+
if (Object.keys(playerSelection).length !== Object.keys(answer).length) {
|
| 599 |
+
correct = false;
|
| 600 |
+
} else {
|
| 601 |
+
for (const gemId in answer) {
|
| 602 |
+
if (playerSelection[gemId] !== answer[gemId]) {
|
| 603 |
+
correct = false;
|
| 604 |
+
break;
|
| 605 |
+
}
|
| 606 |
+
}
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
if (correct) {
|
| 610 |
+
puzzleFeedback.textContent = '組合正確!你獲得了大寶石!';
|
| 611 |
+
puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-green-400';
|
| 612 |
+
setTimeout(() => {
|
| 613 |
+
const nextStage = currentStage + 1;
|
| 614 |
+
if (nextStage < puzzleLevels.length) {
|
| 615 |
+
showScreen('intermission-screen');
|
| 616 |
+
} else {
|
| 617 |
+
showScreen('final-win-screen');
|
| 618 |
+
}
|
| 619 |
+
}, 1500);
|
| 620 |
+
} else {
|
| 621 |
+
puzzleFeedback.textContent = '組合不對喔,再試一次!';
|
| 622 |
+
puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-red-500';
|
| 623 |
+
}
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
// --- 事件監聽 ---
|
| 627 |
+
startButton.addEventListener('click', () => {
|
| 628 |
+
showScreen('game-screen');
|
| 629 |
+
resizeCanvas();
|
| 630 |
+
instructionsScreen.classList.remove('hidden');
|
| 631 |
+
});
|
| 632 |
+
|
| 633 |
+
playGameButton.addEventListener('click', () => startGame(0));
|
| 634 |
+
restartButton.addEventListener('click', () => startGame(currentStage));
|
| 635 |
+
continueButton.addEventListener('click', showPuzzleScreen);
|
| 636 |
+
startNextStageButton.addEventListener('click', () => {
|
| 637 |
+
showScreen('game-screen');
|
| 638 |
+
startGame(currentStage + 1);
|
| 639 |
+
});
|
| 640 |
+
resetPuzzleButton.addEventListener('click', resetPuzzle);
|
| 641 |
+
checkPuzzleButton.addEventListener('click', checkPuzzleAnswer);
|
| 642 |
+
unlockSecretButton.addEventListener('click', () => {
|
| 643 |
+
container.style.display = 'none';
|
| 644 |
+
secretScreen.style.display = 'flex';
|
| 645 |
+
});
|
| 646 |
+
|
| 647 |
+
secretChoiceButtons.forEach(button => {
|
| 648 |
+
button.addEventListener('click', () => {
|
| 649 |
+
const choice = button.dataset.choice;
|
| 650 |
+
secretFeedback.classList.remove('text-yellow-400', 'text-green-400');
|
| 651 |
+
if (choice === 'C') {
|
| 652 |
+
secretFeedback.textContent = '';
|
| 653 |
+
secretQuestion.style.display = 'none';
|
| 654 |
+
secretReveal.style.display = 'block';
|
| 655 |
+
} else if (choice === 'A') {
|
| 656 |
+
secretFeedback.textContent = '駕駛技術固然重要,但那只是過程喔!';
|
| 657 |
+
secretFeedback.classList.add('text-yellow-400');
|
| 658 |
+
} else {
|
| 659 |
+
secretFeedback.textContent = '閃避只是手段,不是目的呀!';
|
| 660 |
+
secretFeedback.classList.add('text-yellow-400');
|
| 661 |
+
}
|
| 662 |
+
});
|
| 663 |
+
});
|
| 664 |
+
|
| 665 |
+
window.addEventListener('resize', resizeCanvas);
|
| 666 |
+
|
| 667 |
+
window.addEventListener('keydown', (e) => {
|
| 668 |
+
if (e.key === 'ArrowLeft') handleMove('left');
|
| 669 |
+
else if (e.key === 'ArrowRight') handleMove('right');
|
| 670 |
+
|
| 671 |
+
if (gameInterval) {
|
| 672 |
+
keySequence.push(e.key);
|
| 673 |
+
keySequence = keySequence.slice(-cheatCode.length);
|
| 674 |
+
if (keySequence.join('') === cheatCode) {
|
| 675 |
+
winGame();
|
| 676 |
+
}
|
| 677 |
+
}
|
| 678 |
+
});
|
| 679 |
+
|
| 680 |
+
canvas.addEventListener('click', (e) => {
|
| 681 |
+
const rect = canvas.getBoundingClientRect();
|
| 682 |
+
const clickX = e.clientX - rect.left;
|
| 683 |
+
if (clickX < canvas.width / 2) handleMove('left');
|
| 684 |
+
else handleMove('right');
|
| 685 |
+
});
|
| 686 |
+
moveLeftButton.addEventListener('click', () => handleMove('left'));
|
| 687 |
+
moveRightButton.addEventListener('click', () => handleMove('right'));
|
| 688 |
+
|
| 689 |
+
// --- 初始啟動 ---
|
| 690 |
+
showScreen('start-screen');
|
| 691 |
+
});
|
| 692 |
+
</script>
|
| 693 |
+
</body>
|
| 694 |
+
</html>
|
harbor.html
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-Hant">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>數學探險島 - 海風港灣</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
<!-- MathJax for rendering formulas -->
|
| 12 |
+
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
| 13 |
+
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
| 14 |
+
<style>
|
| 15 |
+
body {
|
| 16 |
+
font-family: 'Noto Sans TC', sans-serif;
|
| 17 |
+
overflow: hidden;
|
| 18 |
+
background-image: url('https://i.meee.com.tw/TDC4EZE.png');
|
| 19 |
+
background-size: cover;
|
| 20 |
+
background-position: center;
|
| 21 |
+
}
|
| 22 |
+
#game-container-bg {
|
| 23 |
+
background-image: url('https://i.meee.com.tw/vT2QeZF.png');
|
| 24 |
+
background-size: cover;
|
| 25 |
+
background-position: center;
|
| 26 |
+
}
|
| 27 |
+
.seaweed {
|
| 28 |
+
position: absolute;
|
| 29 |
+
bottom: -10px;
|
| 30 |
+
pointer-events: none;
|
| 31 |
+
filter: brightness(0.9);
|
| 32 |
+
}
|
| 33 |
+
.fish {
|
| 34 |
+
position: absolute;
|
| 35 |
+
cursor: pointer;
|
| 36 |
+
transition: transform 0.2s ease, opacity 0.15s ease;
|
| 37 |
+
}
|
| 38 |
+
.fish:hover {
|
| 39 |
+
transform: scale(1.1);
|
| 40 |
+
}
|
| 41 |
+
.catch-feedback {
|
| 42 |
+
position: absolute;
|
| 43 |
+
font-size: 2rem;
|
| 44 |
+
font-weight: bold;
|
| 45 |
+
pointer-events: none;
|
| 46 |
+
animation: fadeUp 1s forwards;
|
| 47 |
+
text-shadow: 1px 1px 2px black;
|
| 48 |
+
}
|
| 49 |
+
@keyframes fadeUp {
|
| 50 |
+
from { opacity: 1; transform: translateY(0); }
|
| 51 |
+
to { opacity: 0; transform: translateY(-50px); }
|
| 52 |
+
}
|
| 53 |
+
.loader {
|
| 54 |
+
border: 8px solid #f3f3f3;
|
| 55 |
+
border-radius: 50%;
|
| 56 |
+
border-top: 8px solid #3498db;
|
| 57 |
+
width: 60px;
|
| 58 |
+
height: 60px;
|
| 59 |
+
animation: spin 1s linear infinite;
|
| 60 |
+
}
|
| 61 |
+
@keyframes spin {
|
| 62 |
+
0% { transform: rotate(0deg); }
|
| 63 |
+
100% { transform: rotate(360deg); }
|
| 64 |
+
}
|
| 65 |
+
.market-choice.selected {
|
| 66 |
+
border-color: #facc15; /* yellow-400 */
|
| 67 |
+
box-shadow: 0 0 15px #facc15;
|
| 68 |
+
}
|
| 69 |
+
.secret-bg {
|
| 70 |
+
background-image: url('https://i.meee.com.tw/EKZpYKI.png');
|
| 71 |
+
background-size: cover;
|
| 72 |
+
background-position: center;
|
| 73 |
+
}
|
| 74 |
+
</style>
|
| 75 |
+
</head>
|
| 76 |
+
<body class="flex items-center justify-center h-screen">
|
| 77 |
+
|
| 78 |
+
<div id="container" class="w-full max-w-4xl mx-auto text-white p-4 flex flex-col h-[90vh] max-h-[800px] relative">
|
| 79 |
+
|
| 80 |
+
<!-- 引導畫面 -->
|
| 81 |
+
<div id="start-screen" class="flex flex-col items-center justify-center text-center p-8 bg-black bg-opacity-70 rounded-lg my-auto">
|
| 82 |
+
<h1 class="text-5xl font-bold text-cyan-300 mb-4" style="text-shadow: 2px 2px 4px #000;">海風港灣</h1>
|
| 83 |
+
<p class="text-xl text-gray-200 mb-8 max-w-2xl">歡迎來到海風港灣!這次的任務是捕捉指定的魚種,並到市場賣出。有了捕捉的技術,真正要賺大錢還得靠數學呢!請你在捕捉完漁獲後,根據市場需求,將漁獲賣到最適合的市場!</p>
|
| 84 |
+
<button id="start-button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-4 px-8 rounded-lg text-2xl">
|
| 85 |
+
開始抓魚
|
| 86 |
+
</button>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<!-- 載入畫面 -->
|
| 90 |
+
<div id="loading-screen" class="hidden flex-col items-center justify-center text-center p-8 bg-black bg-opacity-70 rounded-lg my-auto">
|
| 91 |
+
<div class="loader mb-6"></div>
|
| 92 |
+
<p id="loading-text" class="text-2xl text-gray-200">正在準備海底世界...</p>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<!-- 遊戲畫面 -->
|
| 96 |
+
<div id="game-screen" class="hidden flex-col h-full w-full">
|
| 97 |
+
<div class="flex justify-between items-center p-2 bg-black/50 rounded-t-lg">
|
| 98 |
+
<div>
|
| 99 |
+
<p class="text-lg">目標: <span id="goal-text" class="font-bold text-yellow-400"></span></p>
|
| 100 |
+
<p class="text-lg">已捕捉: <span id="score" class="font-bold text-xl">0</span> / <span id="goal-count"></span></p>
|
| 101 |
+
</div>
|
| 102 |
+
<p class="text-sm text-cyan-200">請適度捕撈,維持海洋永續</p>
|
| 103 |
+
<div>
|
| 104 |
+
<p class="text-lg">時間</p>
|
| 105 |
+
<p id="timer" class="font-bold text-3xl">20</p>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
<div id="game-container-bg" class="relative flex-grow rounded-b-lg overflow-hidden border-4 border-black/50">
|
| 109 |
+
<div id="game-container" class="absolute inset-0">
|
| 110 |
+
<img src="https://i.meee.com.tw/XE9mkQQ.gif" class="seaweed" style="left: 0%; height: 45%; transform: scaleX(-1);" alt="[海草的Image]">
|
| 111 |
+
<img src="https://i.meee.com.tw/cTBZUgx.gif" class="seaweed" style="left: 20%; height: 35%;" alt="[海草的Image]">
|
| 112 |
+
<img src="https://i.meee.com.tw/KyN2GaF.gif" class="seaweed" style="left: 45%; height: 30%;" alt="[海草的Image]">
|
| 113 |
+
<img src="https://i.meee.com.tw/XE9mkQQ.gif" class="seaweed" style="right: 25%; height: 48%;" alt="[海草的Image]">
|
| 114 |
+
<img src="https://i.meee.com.tw/cTBZUgx.gif" class="seaweed" style="right: 10%; height: 55%; transform: scaleX(-1);" alt="[海草的Image]">
|
| 115 |
+
<img src="https://i.meee.com.tw/KyN2GaF.gif" class="seaweed" style="right: -2%; height: 38%;" alt="[海草的Image]">
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<!-- 疊加畫面 (操作說明, 成功/失敗) -->
|
| 121 |
+
<div id="overlay-container" class="hidden absolute inset-0 flex-col items-center justify-center z-10">
|
| 122 |
+
<div id="instructions-screen" class="hidden bg-black bg-opacity-80 w-full h-full flex-col items-center justify-center text-center p-4">
|
| 123 |
+
<div id="instructions-content" class="bg-slate-800 p-8 rounded-lg">
|
| 124 |
+
<!-- JS will fill this -->
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<div id="game-over-screen" class="hidden bg-black bg-opacity-70 w-full h-full flex-col items-center justify-center text-center p-4">
|
| 128 |
+
<h2 class="text-4xl font-bold text-red-500 mb-4">挑戰失敗!</h2>
|
| 129 |
+
<button id="restart-button" class="bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-3 px-6 rounded-lg text-xl">
|
| 130 |
+
重新挑戰
|
| 131 |
+
</button>
|
| 132 |
+
</div>
|
| 133 |
+
<div id="win-screen" class="hidden bg-black bg-opacity-70 w-full h-full flex-col items-center justify-center text-center p-4">
|
| 134 |
+
<h2 class="text-4xl font-bold text-green-400 mb-4">成功!</h2>
|
| 135 |
+
<p class="text-xl mb-6">你抓到足夠的漁獲了!</p>
|
| 136 |
+
<button id="continue-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg text-xl">
|
| 137 |
+
繼續前進
|
| 138 |
+
</button>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<!-- 市場選擇畫面 -->
|
| 143 |
+
<div id="market-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-70 rounded-lg">
|
| 144 |
+
<div id="market-instructions" class="text-center my-auto">
|
| 145 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">任務說明:選擇市場</h2>
|
| 146 |
+
<p class="text-lg">每個地點對<span class="market-fish-name text-yellow-300 font-bold"></span>的需求都不同,需求<span class="text-yellow-300 font-bold">比例</span>越高的市場,就能賣到越好的價格!</p>
|
| 147 |
+
<p class="text-lg mt-2">仔細觀察下方的統計圖,找出最賺錢的市場吧!</p>
|
| 148 |
+
<button id="show-market-choices-button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
|
| 149 |
+
了解,前往選擇
|
| 150 |
+
</button>
|
| 151 |
+
</div>
|
| 152 |
+
<div id="market-choices" class="hidden flex-col">
|
| 153 |
+
<h2 class="text-3xl font-bold text-amber-300 text-center mb-4">你要把<span class="market-fish-name"></span>賣到哪裡?</h2>
|
| 154 |
+
<div id="market-images-container" class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
| 155 |
+
<!-- Market images will be inserted here by JS -->
|
| 156 |
+
</div>
|
| 157 |
+
<p id="market-feedback" class="text-center text-xl font-bold min-h-[32px] mt-4"></p>
|
| 158 |
+
<button id="next-level-button" class="hidden bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg text-xl w-full max-w-xs mx-auto mt-4">
|
| 159 |
+
<!-- Button text will be set by JS -->
|
| 160 |
+
</button>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<!-- 攻略畫面 -->
|
| 165 |
+
<div id="strategy-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-80 rounded-lg text-left overflow-y-auto">
|
| 166 |
+
<h1 class="text-3xl font-bold text-amber-300 text-center mb-4">經營之神的攻略</h1>
|
| 167 |
+
<p class="text-lg mb-4">在真實世界中,不同地點的調查所收到的資料不一定會一樣多,總人數不一樣的時候,我們很難用肉眼就判斷出比例的高低,因此需要「相對次數」這樣的統計數據。</p>
|
| 168 |
+
<div class="text-center bg-gray-900 p-3 rounded-lg text-xl mb-4">
|
| 169 |
+
$$\text{相對次數} = \frac{\text{需求(人)}}{\text{總人數}} \times 100\%$$
|
| 170 |
+
</div>
|
| 171 |
+
<img src="https://i.meee.com.tw/i67LLqV.png" class="rounded-lg mx-auto my-4 w-full max-w-md" alt="[包含相對次數的統計圖表]">
|
| 172 |
+
<p class="text-lg mb-6 text-center">有了相對次數,會不會更好判斷呢?用下一關的挑戰來試試吧!</p>
|
| 173 |
+
<button id="start-final-stage-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl w-full max-w-xs mx-auto mt-4">
|
| 174 |
+
挑戰最終關卡
|
| 175 |
+
</button>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<!-- 最終秘密畫面 -->
|
| 179 |
+
<div id="final-secret-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-80 rounded-lg text-left overflow-y-auto">
|
| 180 |
+
<h1 class="text-3xl font-bold text-amber-300 text-center mb-6">海風港灣的秘密</h1>
|
| 181 |
+
|
| 182 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
| 183 |
+
<img src="https://i.meee.com.tw/rmcY2Vc.png" class="rounded-lg border-4 border-amber-400" alt="[代數之丘的統計圖表]">
|
| 184 |
+
<img src="https://i.meee.com.tw/qV8QrUI.png" class="rounded-lg" alt="[哲學之塔的統計圖表]">
|
| 185 |
+
<img src="https://i.meee.com.tw/i67LLqV.png" class="rounded-lg" alt="[寶石洞窟的統計圖表]">
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<p class="text-lg mb-4">恭喜你,完成了所有挑戰!你學會了捕魚,也學會了如何分析數據來做出最好的商業決策。</p>
|
| 189 |
+
<p class="text-2xl font-bold text-cyan-300 mb-4">這個港灣的秘密就是:<br>「數字」本身有時候會騙人,「比例」才能揭露真相。</p>
|
| 190 |
+
<p class="text-lg mb-4">單看數字,哲學之塔似乎是更好的市場。但當你把「總人數」也考慮進來,計算出「相對次數」(也就是需求比例),你才會發現代數之丘的需求比例,是超過哲學之塔的。</p>
|
| 191 |
+
<p class="text-lg">這個智慧,不只適用於賣魚。未來在你看新聞、分析報告,甚至做人生重大決定時,記得問自己:「這個數字背後的『分母』是什麼?」看透比例,你就能做出更聰明的選擇。</p>
|
| 192 |
+
<a href="index.html" id="back-to-map-button" class="inline-block text-center bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg text-xl w-full max-w-xs mx-auto mt-8">
|
| 193 |
+
回到探險島地圖
|
| 194 |
+
</a>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
<script>
|
| 200 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 201 |
+
// --- 畫面元素 ---
|
| 202 |
+
const startScreen = document.getElementById('start-screen');
|
| 203 |
+
const loadingScreen = document.getElementById('loading-screen');
|
| 204 |
+
const loadingText = document.getElementById('loading-text');
|
| 205 |
+
const gameScreen = document.getElementById('game-screen');
|
| 206 |
+
const startButton = document.getElementById('start-button');
|
| 207 |
+
const gameContainer = document.getElementById('game-container');
|
| 208 |
+
const scoreDisplay = document.getElementById('score');
|
| 209 |
+
const timerDisplay = document.getElementById('timer');
|
| 210 |
+
const instructionsScreen = document.getElementById('instructions-screen');
|
| 211 |
+
const instructionsContent = document.getElementById('instructions-content');
|
| 212 |
+
const goalText = document.getElementById('goal-text');
|
| 213 |
+
const goalCount = document.getElementById('goal-count');
|
| 214 |
+
const overlayContainer = document.getElementById('overlay-container');
|
| 215 |
+
const gameOverScreen = document.getElementById('game-over-screen');
|
| 216 |
+
const winScreen = document.getElementById('win-screen');
|
| 217 |
+
const restartButton = document.getElementById('restart-button');
|
| 218 |
+
const continueButton = document.getElementById('continue-button');
|
| 219 |
+
const marketScreen = document.getElementById('market-screen');
|
| 220 |
+
const marketInstructions = document.getElementById('market-instructions');
|
| 221 |
+
const marketChoices = document.getElementById('market-choices');
|
| 222 |
+
const showMarketChoicesButton = document.getElementById('show-market-choices-button');
|
| 223 |
+
const marketImagesContainer = document.getElementById('market-images-container');
|
| 224 |
+
const marketFeedback = document.getElementById('market-feedback');
|
| 225 |
+
const nextLevelButton = document.getElementById('next-level-button');
|
| 226 |
+
const startFinalStageButton = document.getElementById('start-final-stage-button');
|
| 227 |
+
const finalSecretScreen = document.getElementById('final-secret-screen');
|
| 228 |
+
|
| 229 |
+
// --- 遊戲設定 ---
|
| 230 |
+
const MAX_FISH_ON_SCREEN = 8;
|
| 231 |
+
let score = 0, timeLeft = 0;
|
| 232 |
+
let gameInterval, timerInterval, fishSpawnInterval;
|
| 233 |
+
let currentLevel = 0;
|
| 234 |
+
let currentLevelSettings;
|
| 235 |
+
|
| 236 |
+
const levels = [
|
| 237 |
+
{ goal: 10, time: 20, targetFish: '小丑魚', speedMultiplier: 1.0, spawnRate: 1200, fishSet: ['小丑魚', '螃蟹'] },
|
| 238 |
+
{ goal: 10, time: 20, targetFish: '水母', speedMultiplier: 1.5, spawnRate: 1000, fishSet: ['水母', '章魚'] },
|
| 239 |
+
{ goal: 10, time: 20, targetFish: '水母', speedMultiplier: 1.8, spawnRate: 800, fishSet: ['水母', '鯊魚'] }
|
| 240 |
+
];
|
| 241 |
+
|
| 242 |
+
const fishTypes = [
|
| 243 |
+
{ name: '小丑魚', src: 'https://i.meee.com.tw/7tfldTT.gif', width: 160, height: 120 },
|
| 244 |
+
{ name: '鯊魚', src: 'https://i.meee.com.tw/KCQk7C6.gif', width: 270, height: 270 },
|
| 245 |
+
{ name: '章魚', src: 'https://i.meee.com.tw/lBWJmMy.gif', width: 160, height: 130 },
|
| 246 |
+
{ name: '海馬', src: 'https://i.meee.com.tw/sMehyty.gif', width: 150, height: 150 },
|
| 247 |
+
{ name: '螃蟹', src: 'https://i.meee.com.tw/zx9Vg0B.gif', width: 150, height: 110 },
|
| 248 |
+
{ name: '水母', src: 'https://i.meee.com.tw/81gncdG.gif', width: 160, height: 160 },
|
| 249 |
+
];
|
| 250 |
+
|
| 251 |
+
const marketLevels = [
|
| 252 |
+
{
|
| 253 |
+
images: { A: 'https://i.meee.com.tw/jQ3A90u.png', B: 'https://i.meee.com.tw/HEx1GWE.png', C: 'https://i.meee.com.tw/199bHzA.png' },
|
| 254 |
+
correctAnswer: 'B',
|
| 255 |
+
feedback: { correct: "答對了!哲學之塔的需求比例最高,要賺大錢還是得靠數學,不能蠻幹阿!", wrong: "每個地點的調查人數都一樣多,需求數量越多,即表示需求比例越高!" }
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
// A: 代數之丘(左), B: 哲學之塔(中), C: 寶石洞窟(右)
|
| 259 |
+
images: { A: 'https://i.meee.com.tw/lTXgvxv.png', B: 'https://i.meee.com.tw/FKPNhKh.png', C: 'https://i.meee.com.tw/zdv5MtZ.png' },
|
| 260 |
+
correctAnswer: 'A', // 正確答案是代數之丘
|
| 261 |
+
feedback: {
|
| 262 |
+
firstClick: {
|
| 263 |
+
C: "你確定嗎?其他兩個地方的需求人數比較多喔!若是確定請再點選一次",
|
| 264 |
+
B: "你確定嗎?雖然哲學之塔的需求人數最多,但他的總人數也最多喔!你確定他是比例最高的嗎?",
|
| 265 |
+
A: "你確定嗎?哲學之塔的需求人數比較多喔!"
|
| 266 |
+
},
|
| 267 |
+
correct: "你做出了正確的決定!但到底是運氣好,還是數學好呢,如果是運氣好的話,可沒有辦法長久經營阿~",
|
| 268 |
+
wrong: "商品買賣,不是越多人買越賺錢,不是喔!因為你長期的決策錯誤,導致買賣虧損,最後造成店家倒閉..."
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
{
|
| 272 |
+
// A: 代數之丘, B: 哲學之塔, C: 寶石洞窟
|
| 273 |
+
images: { A: 'https://i.meee.com.tw/rmcY2Vc.png', B: 'https://i.meee.com.tw/qV8QrUI.png', C: 'https://i.meee.com.tw/i67LLqV.png' },
|
| 274 |
+
correctAnswer: 'A', // 正確答案是代數之丘
|
| 275 |
+
feedback: { correct: "有了相對次數這項統計數據,是不是就更容易做選擇了呢~", wrong: "再仔細看看,哪個市場的需求比例最高呢?" }
|
| 276 |
+
}
|
| 277 |
+
];
|
| 278 |
+
let firstChoice = null;
|
| 279 |
+
const fishesOnScreen = [];
|
| 280 |
+
|
| 281 |
+
// --- 畫面管理 ---
|
| 282 |
+
function showScreen(screenId) {
|
| 283 |
+
['start-screen', 'game-screen', 'market-screen', 'loading-screen', 'strategy-screen', 'final-secret-screen'].forEach(id => {
|
| 284 |
+
document.getElementById(id).style.display = (id === screenId) ? 'flex' : 'none';
|
| 285 |
+
});
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
function showOverlay(overlayId) {
|
| 289 |
+
overlayContainer.style.display = 'flex';
|
| 290 |
+
['instructions-screen', 'game-over-screen', 'win-screen'].forEach(id => {
|
| 291 |
+
document.getElementById(id).style.display = (id === overlayId) ? 'flex' : 'none';
|
| 292 |
+
});
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
function hideOverlay() {
|
| 296 |
+
overlayContainer.style.display = 'none';
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
// --- 預載入函式 ---
|
| 300 |
+
function preloadImages(urls, onProgress) {
|
| 301 |
+
return new Promise((resolve) => {
|
| 302 |
+
let loadedCount = 0;
|
| 303 |
+
const totalImages = urls.length;
|
| 304 |
+
if (totalImages === 0) resolve();
|
| 305 |
+
urls.forEach(url => {
|
| 306 |
+
const img = new Image();
|
| 307 |
+
img.src = url;
|
| 308 |
+
const onFinish = () => {
|
| 309 |
+
loadedCount++;
|
| 310 |
+
onProgress(loadedCount, totalImages);
|
| 311 |
+
if (loadedCount === totalImages) resolve();
|
| 312 |
+
};
|
| 313 |
+
img.onload = onFinish;
|
| 314 |
+
img.onerror = () => { console.error(`圖片載入失敗: ${url}`); onFinish(); };
|
| 315 |
+
});
|
| 316 |
+
});
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
function startGame(levelIndex) {
|
| 320 |
+
currentLevel = levelIndex;
|
| 321 |
+
currentLevelSettings = levels[currentLevel];
|
| 322 |
+
score = 0;
|
| 323 |
+
timeLeft = currentLevelSettings.time;
|
| 324 |
+
updateUI();
|
| 325 |
+
|
| 326 |
+
fishesOnScreen.forEach(fish => fish.element.remove());
|
| 327 |
+
fishesOnScreen.length = 0;
|
| 328 |
+
|
| 329 |
+
fishSpawnInterval = setInterval(spawnFish, currentLevelSettings.spawnRate);
|
| 330 |
+
|
| 331 |
+
timerInterval = setInterval(() => {
|
| 332 |
+
timeLeft--;
|
| 333 |
+
updateUI();
|
| 334 |
+
if (timeLeft <= 0) endGame(false);
|
| 335 |
+
}, 1000);
|
| 336 |
+
|
| 337 |
+
gameInterval = setInterval(updateGame, 1000 / 60);
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
function spawnFish() {
|
| 341 |
+
if (fishesOnScreen.length >= MAX_FISH_ON_SCREEN) return;
|
| 342 |
+
|
| 343 |
+
let fishData;
|
| 344 |
+
const fishSet = currentLevelSettings.fishSet.map(name => fishTypes.find(f => f.name === name));
|
| 345 |
+
const targetFish = fishSet.find(f => f.name === currentLevelSettings.targetFish);
|
| 346 |
+
const otherFish = fishSet.filter(f => f.name !== currentLevelSettings.targetFish);
|
| 347 |
+
|
| 348 |
+
const activeDistractor = otherFish.length > 0 ? otherFish[0] : targetFish;
|
| 349 |
+
|
| 350 |
+
if (Math.random() < 0.75) {
|
| 351 |
+
fishData = targetFish;
|
| 352 |
+
} else {
|
| 353 |
+
fishData = activeDistractor;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
const fishElement = document.createElement('img');
|
| 357 |
+
fishElement.src = fishData.src;
|
| 358 |
+
fishElement.className = 'fish';
|
| 359 |
+
|
| 360 |
+
const direction = Math.random() < 0.5 ? 'left' : 'right';
|
| 361 |
+
const speed = (Math.random() * 2 + 1.5) * currentLevelSettings.speedMultiplier;
|
| 362 |
+
const startY = Math.random() * (gameContainer.clientHeight - fishData.height);
|
| 363 |
+
|
| 364 |
+
fishElement.style.width = `${fishData.width}px`;
|
| 365 |
+
fishElement.style.height = `${fishData.height}px`;
|
| 366 |
+
fishElement.style.top = `${startY}px`;
|
| 367 |
+
|
| 368 |
+
if (direction === 'left') {
|
| 369 |
+
fishElement.style.left = `${gameContainer.clientWidth}px`;
|
| 370 |
+
fishElement.style.transform = 'scaleX(-1)';
|
| 371 |
+
} else {
|
| 372 |
+
fishElement.style.left = `-${fishData.width}px`;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
const fishObject = { element: fishElement, data: fishData, speed, direction, vy: (Math.random() - 0.5) * 2, lastVyChange: Date.now() };
|
| 376 |
+
|
| 377 |
+
if (currentLevel === 2 && fishData.name === '水母') {
|
| 378 |
+
fishObject.health = 2;
|
| 379 |
+
} else {
|
| 380 |
+
fishObject.health = 1;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
fishElement.onclick = () => catchFish(fishObject);
|
| 384 |
+
fishesOnScreen.push(fishObject);
|
| 385 |
+
gameContainer.appendChild(fishObject.element);
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
function updateGame() {
|
| 389 |
+
for (let i = fishesOnScreen.length - 1; i >= 0; i--) {
|
| 390 |
+
const fish = fishesOnScreen[i];
|
| 391 |
+
let currentX = parseFloat(fish.element.style.left);
|
| 392 |
+
let currentY = parseFloat(fish.element.style.top);
|
| 393 |
+
|
| 394 |
+
if (fish.direction === 'left') {
|
| 395 |
+
currentX -= fish.speed;
|
| 396 |
+
if (currentX < -fish.data.width) {
|
| 397 |
+
fish.element.remove();
|
| 398 |
+
fishesOnScreen.splice(i, 1);
|
| 399 |
+
continue;
|
| 400 |
+
}
|
| 401 |
+
} else {
|
| 402 |
+
currentX += fish.speed;
|
| 403 |
+
if (currentX > gameContainer.clientWidth) {
|
| 404 |
+
fish.element.remove();
|
| 405 |
+
fishesOnScreen.splice(i, 1);
|
| 406 |
+
continue;
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
fish.element.style.left = `${currentX}px`;
|
| 410 |
+
|
| 411 |
+
if (currentLevel > 0) {
|
| 412 |
+
if (Date.now() - fish.lastVyChange > 1000) {
|
| 413 |
+
fish.vy = (Math.random() - 0.5) * 6;
|
| 414 |
+
fish.lastVyChange = Date.now();
|
| 415 |
+
}
|
| 416 |
+
currentY += fish.vy;
|
| 417 |
+
if (currentY < 0 || currentY > gameContainer.clientHeight - fish.data.height) {
|
| 418 |
+
fish.vy *= -1;
|
| 419 |
+
}
|
| 420 |
+
fish.element.style.top = `${currentY}px`;
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
function catchFish(fishObject) {
|
| 426 |
+
fishObject.health--;
|
| 427 |
+
|
| 428 |
+
if (fishObject.health > 0) {
|
| 429 |
+
showCatchFeedback('!', fishObject.element, false);
|
| 430 |
+
fishObject.element.style.opacity = '0.5';
|
| 431 |
+
setTimeout(() => {
|
| 432 |
+
if (fishObject.element) {
|
| 433 |
+
fishObject.element.style.opacity = '1';
|
| 434 |
+
}
|
| 435 |
+
}, 150);
|
| 436 |
+
return;
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
if (fishObject.data.name === currentLevelSettings.targetFish) {
|
| 440 |
+
score++;
|
| 441 |
+
showCatchFeedback('+1', fishObject.element, true);
|
| 442 |
+
} else {
|
| 443 |
+
score = Math.max(0, score - 1);
|
| 444 |
+
showCatchFeedback('-1', fishObject.element, false);
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
fishObject.element.remove();
|
| 448 |
+
const index = fishesOnScreen.indexOf(fishObject);
|
| 449 |
+
if (index > -1) fishesOnScreen.splice(index, 1);
|
| 450 |
+
updateUI();
|
| 451 |
+
if (score >= currentLevelSettings.goal) endGame(true);
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
function showCatchFeedback(text, fishElement, isCorrect) {
|
| 455 |
+
const feedback = document.createElement('div');
|
| 456 |
+
feedback.className = 'catch-feedback';
|
| 457 |
+
feedback.textContent = text;
|
| 458 |
+
feedback.style.color = isCorrect ? '#4ade80' : '#f87171';
|
| 459 |
+
const rect = fishElement.getBoundingClientRect();
|
| 460 |
+
const containerRect = gameContainer.getBoundingClientRect();
|
| 461 |
+
feedback.style.left = `${rect.left - containerRect.left + rect.width / 2}px`;
|
| 462 |
+
feedback.style.top = `${rect.top - containerRect.top + rect.height / 2}px`;
|
| 463 |
+
gameContainer.appendChild(feedback);
|
| 464 |
+
setTimeout(() => feedback.remove(), 1000);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
function updateUI() {
|
| 468 |
+
scoreDisplay.textContent = score;
|
| 469 |
+
timerDisplay.textContent = timeLeft;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
function endGame(isWin) {
|
| 473 |
+
clearInterval(gameInterval);
|
| 474 |
+
clearInterval(timerInterval);
|
| 475 |
+
clearInterval(fishSpawnInterval);
|
| 476 |
+
gameInterval = timerInterval = fishSpawnInterval = null;
|
| 477 |
+
|
| 478 |
+
if (isWin) {
|
| 479 |
+
showOverlay('win-screen');
|
| 480 |
+
} else {
|
| 481 |
+
showOverlay('game-over-screen');
|
| 482 |
+
}
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
function setupMarketScreen() {
|
| 486 |
+
const marketLevel = marketLevels[currentLevel];
|
| 487 |
+
document.querySelectorAll('.market-fish-name').forEach(el => el.textContent = levels[currentLevel].targetFish);
|
| 488 |
+
marketImagesContainer.innerHTML = '';
|
| 489 |
+
// A, B, C keys correspond to left, middle, right images
|
| 490 |
+
const marketOrder = ['A', 'B', 'C'];
|
| 491 |
+
marketOrder.forEach(key => {
|
| 492 |
+
const src = marketLevel.images[key];
|
| 493 |
+
const img = document.createElement('img');
|
| 494 |
+
img.src = src;
|
| 495 |
+
img.dataset.market = key;
|
| 496 |
+
img.alt = `[${key}市場的統計圖表]`;
|
| 497 |
+
img.className = 'market-choice cursor-pointer rounded-lg border-4 border-transparent hover:border-yellow-400';
|
| 498 |
+
img.addEventListener('click', handleMarketChoice);
|
| 499 |
+
marketImagesContainer.appendChild(img);
|
| 500 |
+
});
|
| 501 |
+
|
| 502 |
+
marketFeedback.textContent = '';
|
| 503 |
+
nextLevelButton.classList.add('hidden');
|
| 504 |
+
firstChoice = null;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
function handleMarketChoice(e) {
|
| 508 |
+
const choice = e.target.dataset.market;
|
| 509 |
+
const marketLevel = marketLevels[currentLevel];
|
| 510 |
+
|
| 511 |
+
document.querySelectorAll('.market-choice').forEach(img => img.classList.remove('selected'));
|
| 512 |
+
|
| 513 |
+
if (currentLevel === 0) {
|
| 514 |
+
if (choice === marketLevel.correctAnswer) {
|
| 515 |
+
marketFeedback.textContent = marketLevel.feedback.correct;
|
| 516 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
|
| 517 |
+
nextLevelButton.textContent = "前往第二關";
|
| 518 |
+
nextLevelButton.classList.remove('hidden');
|
| 519 |
+
} else {
|
| 520 |
+
marketFeedback.textContent = marketLevel.feedback.wrong;
|
| 521 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-yellow-400';
|
| 522 |
+
}
|
| 523 |
+
} else if (currentLevel === 1) {
|
| 524 |
+
if (firstChoice === choice) {
|
| 525 |
+
if (choice === marketLevel.correctAnswer) {
|
| 526 |
+
marketFeedback.textContent = marketLevel.feedback.correct;
|
| 527 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
|
| 528 |
+
nextLevelButton.textContent = "經營之神的攻略";
|
| 529 |
+
nextLevelButton.classList.remove('hidden');
|
| 530 |
+
} else {
|
| 531 |
+
marketFeedback.textContent = marketLevel.feedback.wrong;
|
| 532 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-red-500';
|
| 533 |
+
}
|
| 534 |
+
firstChoice = null;
|
| 535 |
+
} else {
|
| 536 |
+
firstChoice = choice;
|
| 537 |
+
e.target.classList.add('selected');
|
| 538 |
+
marketFeedback.textContent = marketLevel.feedback.firstClick[choice];
|
| 539 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-cyan-300';
|
| 540 |
+
}
|
| 541 |
+
} else if (currentLevel === 2) {
|
| 542 |
+
if (choice === marketLevel.correctAnswer) {
|
| 543 |
+
marketFeedback.textContent = marketLevel.feedback.correct;
|
| 544 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
|
| 545 |
+
nextLevelButton.textContent = "查看海風港灣的秘密";
|
| 546 |
+
nextLevelButton.classList.remove('hidden');
|
| 547 |
+
} else {
|
| 548 |
+
marketFeedback.textContent = marketLevel.feedback.wrong;
|
| 549 |
+
marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-red-500';
|
| 550 |
+
}
|
| 551 |
+
}
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
// --- 事件監聽 ---
|
| 556 |
+
startButton.addEventListener('click', async () => {
|
| 557 |
+
showScreen('loading-screen');
|
| 558 |
+
const imageUrls = [
|
| 559 |
+
...fishTypes.map(fish => fish.src),
|
| 560 |
+
...Object.values(marketLevels[0].images),
|
| 561 |
+
...Object.values(marketLevels[1].images),
|
| 562 |
+
...Object.values(marketLevels[2].images),
|
| 563 |
+
'https://i.meee.com.tw/i67LLqV.png'
|
| 564 |
+
];
|
| 565 |
+
await preloadImages(imageUrls, (loaded, total) => {
|
| 566 |
+
loadingText.textContent = `正在準備海底世界... (${loaded}/${total})`;
|
| 567 |
+
});
|
| 568 |
+
|
| 569 |
+
showScreen('game-screen');
|
| 570 |
+
instructionsContent.innerHTML = `
|
| 571 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">第一關任務</h2>
|
| 572 |
+
<div class="space-y-4 text-lg">
|
| 573 |
+
<p>在 <span class="text-yellow-400 font-bold">${levels[0].time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levels[0].goal}</span> 隻${levels[0].targetFish}!</p>
|
| 574 |
+
<p>點擊<span class="text-green-400 font-bold">正確的魚</span>來加分。</p>
|
| 575 |
+
<p>注意!抓到<span class="text-red-400 font-bold">錯誤的魚</span>會扣分喔!</p>
|
| 576 |
+
</div>
|
| 577 |
+
<button id="play-game-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
|
| 578 |
+
了解!
|
| 579 |
+
</button>`;
|
| 580 |
+
document.getElementById('play-game-button').onclick = () => { hideOverlay(); startGame(0); };
|
| 581 |
+
showOverlay('instructions-screen');
|
| 582 |
+
goalText.textContent = `抓 ${levels[0].goal} 隻${levels[0].targetFish}`;
|
| 583 |
+
goalCount.textContent = levels[0].goal;
|
| 584 |
+
});
|
| 585 |
+
|
| 586 |
+
restartButton.addEventListener('click', () => {
|
| 587 |
+
hideOverlay();
|
| 588 |
+
startGame(currentLevel);
|
| 589 |
+
});
|
| 590 |
+
|
| 591 |
+
continueButton.addEventListener('click', () => {
|
| 592 |
+
hideOverlay();
|
| 593 |
+
showScreen('market-screen');
|
| 594 |
+
marketInstructions.style.display = 'block';
|
| 595 |
+
marketChoices.style.display = 'none';
|
| 596 |
+
});
|
| 597 |
+
|
| 598 |
+
showMarketChoicesButton.addEventListener('click', () => {
|
| 599 |
+
marketInstructions.style.display = 'none';
|
| 600 |
+
marketChoices.style.display = 'flex';
|
| 601 |
+
setupMarketScreen();
|
| 602 |
+
});
|
| 603 |
+
|
| 604 |
+
nextLevelButton.addEventListener('click', () => {
|
| 605 |
+
if (currentLevel === 0) {
|
| 606 |
+
const nextLevel = currentLevel + 1;
|
| 607 |
+
const levelSettings = levels[nextLevel];
|
| 608 |
+
showScreen('game-screen');
|
| 609 |
+
instructionsContent.innerHTML = `
|
| 610 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">第 ${nextLevel + 1} 關任務</h2>
|
| 611 |
+
<div class="space-y-4 text-lg">
|
| 612 |
+
<p>在 <span class="text-yellow-400 font-bold">${levelSettings.time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levelSettings.goal}</span> 隻${levelSettings.targetFish}!</p>
|
| 613 |
+
<p>海洋生物的移動方式不一樣了,小心!</p>
|
| 614 |
+
</div>
|
| 615 |
+
<button id="play-next-level-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
|
| 616 |
+
挑戰!
|
| 617 |
+
</button>`;
|
| 618 |
+
document.getElementById('play-next-level-button').onclick = () => { hideOverlay(); startGame(nextLevel); };
|
| 619 |
+
showOverlay('instructions-screen');
|
| 620 |
+
goalText.textContent = `抓 ${levelSettings.goal} 隻${levelSettings.targetFish}`;
|
| 621 |
+
goalCount.textContent = levelSettings.goal;
|
| 622 |
+
} else if (currentLevel === 1) {
|
| 623 |
+
showScreen('strategy-screen');
|
| 624 |
+
} else if (currentLevel === 2) {
|
| 625 |
+
showScreen('final-secret-screen');
|
| 626 |
+
}
|
| 627 |
+
});
|
| 628 |
+
|
| 629 |
+
startFinalStageButton.addEventListener('click', () => {
|
| 630 |
+
const nextLevel = currentLevel + 1;
|
| 631 |
+
const levelSettings = levels[nextLevel];
|
| 632 |
+
showScreen('game-screen');
|
| 633 |
+
instructionsContent.innerHTML = `
|
| 634 |
+
<h2 class="text-3xl font-bold text-amber-300 mb-6">最終挑戰!</h2>
|
| 635 |
+
<div class="space-y-4 text-lg">
|
| 636 |
+
<p>在 <span class="text-yellow-400 font-bold">${levelSettings.time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levelSettings.goal}</span> 隻${levelSettings.targetFish}!</p>
|
| 637 |
+
<p class="text-cyan-300">注意!這次的${levelSettings.targetFish}比��頑強,需要點擊兩下才能捕捉!</p>
|
| 638 |
+
</div>
|
| 639 |
+
<button id="play-final-level-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
|
| 640 |
+
挑戰!
|
| 641 |
+
</button>`;
|
| 642 |
+
document.getElementById('play-final-level-button').onclick = () => { hideOverlay(); startGame(nextLevel); };
|
| 643 |
+
showOverlay('instructions-screen');
|
| 644 |
+
goalText.textContent = `抓 ${levelSettings.goal} 隻${levelSettings.targetFish}`;
|
| 645 |
+
goalCount.textContent = levelSettings.goal;
|
| 646 |
+
});
|
| 647 |
+
|
| 648 |
+
// --- 初始啟動 ---
|
| 649 |
+
showScreen('start-screen');
|
| 650 |
+
});
|
| 651 |
+
</script>
|
| 652 |
+
</body>
|
| 653 |
+
</html>
|
| 654 |
+
|
index.html
CHANGED
|
@@ -1,19 +1,180 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-Hant">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>數學探險島</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
body {
|
| 13 |
+
font-family: 'Noto Sans TC', sans-serif;
|
| 14 |
+
background-color: #333;
|
| 15 |
+
overflow: hidden; /* 隱藏滾動條 */
|
| 16 |
+
}
|
| 17 |
+
#map-container {
|
| 18 |
+
position: absolute;
|
| 19 |
+
top: 0;
|
| 20 |
+
left: 0;
|
| 21 |
+
width: 100vw;
|
| 22 |
+
height: 100vh;
|
| 23 |
+
background-image: url('https://i.meee.com.tw/emt8qds.png');
|
| 24 |
+
background-size: cover; /* 填滿整個容器 */
|
| 25 |
+
background-repeat: no-repeat;
|
| 26 |
+
background-position: center;
|
| 27 |
+
}
|
| 28 |
+
.main-title {
|
| 29 |
+
position: relative; /* 確保標題在最上層 */
|
| 30 |
+
z-index: 20;
|
| 31 |
+
animation: fadeOutTitle 5s forwards;
|
| 32 |
+
animation-delay: 2s; /* 2秒後開始淡出 */
|
| 33 |
+
}
|
| 34 |
+
@keyframes fadeOutTitle {
|
| 35 |
+
0% { opacity: 1; }
|
| 36 |
+
100% { opacity: 0; pointer-events: none; }
|
| 37 |
+
}
|
| 38 |
+
.location-link {
|
| 39 |
+
position: absolute;
|
| 40 |
+
border-radius: 50%;
|
| 41 |
+
transform: translate(-50%, -50%);
|
| 42 |
+
display: flex;
|
| 43 |
+
align-items: center;
|
| 44 |
+
justify-content: center;
|
| 45 |
+
text-decoration: none;
|
| 46 |
+
cursor: pointer;
|
| 47 |
+
z-index: 10;
|
| 48 |
+
}
|
| 49 |
+
.location-link::before {
|
| 50 |
+
content: '';
|
| 51 |
+
position: absolute;
|
| 52 |
+
width: 80px; /* 縮小光圈的尺寸 */
|
| 53 |
+
height: 80px;
|
| 54 |
+
border-radius: 50%;
|
| 55 |
+
transition: all 0.3s ease;
|
| 56 |
+
transform: scale(0); /* 預設隱藏 */
|
| 57 |
+
opacity: 0;
|
| 58 |
+
}
|
| 59 |
+
.location-link:hover::before {
|
| 60 |
+
transform: scale(1); /* 滑鼠移入時顯示 */
|
| 61 |
+
opacity: 1;
|
| 62 |
+
background-color: rgba(255, 255, 255, 0.2);
|
| 63 |
+
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.5), 0 0 20px 7px rgba(255, 235, 59, 0.6);
|
| 64 |
+
}
|
| 65 |
+
.location-link .label {
|
| 66 |
+
opacity: 0;
|
| 67 |
+
color: white;
|
| 68 |
+
font-size: 1.5rem;
|
| 69 |
+
font-weight: bold;
|
| 70 |
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
|
| 71 |
+
transition: opacity 0.3s ease;
|
| 72 |
+
background-color: rgba(0,0,0,0.5);
|
| 73 |
+
padding: 0.5rem 1rem;
|
| 74 |
+
border-radius: 999px;
|
| 75 |
+
position: relative; /* 確保文字在光圈之上 */
|
| 76 |
+
z-index: 5;
|
| 77 |
+
}
|
| 78 |
+
.location-link:hover .label {
|
| 79 |
+
opacity: 1;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
/* 最終調整後的地點位置 */
|
| 83 |
+
#algebra-hill {
|
| 84 |
+
top: 28%;
|
| 85 |
+
left: 88%;
|
| 86 |
+
width: 25%;
|
| 87 |
+
height: 35%;
|
| 88 |
+
}
|
| 89 |
+
#philosophy-tower {
|
| 90 |
+
top: 35%;
|
| 91 |
+
left: 37%; /* 再次偏左 */
|
| 92 |
+
width: 22%;
|
| 93 |
+
height: 40%;
|
| 94 |
+
}
|
| 95 |
+
#gemstone-cave {
|
| 96 |
+
top: 58%;
|
| 97 |
+
left: 88%;
|
| 98 |
+
width: 20%;
|
| 99 |
+
height: 25%;
|
| 100 |
+
}
|
| 101 |
+
#harbor-bay {
|
| 102 |
+
top: 75%;
|
| 103 |
+
left: 25%;
|
| 104 |
+
width: 35%;
|
| 105 |
+
height: 35%;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/* 風的痕跡 SVG 樣式 */
|
| 109 |
+
#footprints-svg {
|
| 110 |
+
position: absolute;
|
| 111 |
+
top: 0;
|
| 112 |
+
left: 0;
|
| 113 |
+
width: 100%;
|
| 114 |
+
height: 100%;
|
| 115 |
+
pointer-events: none; /* 讓 SVG 不會擋到滑鼠點擊 */
|
| 116 |
+
z-index: 5;
|
| 117 |
+
}
|
| 118 |
+
.footprint-path {
|
| 119 |
+
stroke: rgba(255, 255, 255, 0.4);
|
| 120 |
+
stroke-width: 2.5px;
|
| 121 |
+
fill: none;
|
| 122 |
+
stroke-linecap: round;
|
| 123 |
+
stroke-dasharray: 150 400; /* 150px長的痕跡, 400px的間隔 */
|
| 124 |
+
stroke-dashoffset: 550;
|
| 125 |
+
animation: flow 15s linear infinite;
|
| 126 |
+
}
|
| 127 |
+
/* 讓每個路徑的動畫錯開,看起來更自然 */
|
| 128 |
+
.footprint-path:nth-child(2) { animation-delay: -3s; }
|
| 129 |
+
.footprint-path:nth-child(3) { animation-delay: -7s; }
|
| 130 |
+
.footprint-path:nth-child(4) { animation-delay: -10s; }
|
| 131 |
+
|
| 132 |
+
@keyframes flow {
|
| 133 |
+
from {
|
| 134 |
+
stroke-dashoffset: 550; /* 從路徑外開始 */
|
| 135 |
+
}
|
| 136 |
+
to {
|
| 137 |
+
stroke-dashoffset: -550; /* 移動到路徑的另一端外 */
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
</style>
|
| 142 |
+
</head>
|
| 143 |
+
<body class="bg-gray-900 flex items-center justify-center min-h-screen p-4">
|
| 144 |
+
|
| 145 |
+
<div id="map-container">
|
| 146 |
+
<!-- 動態痕跡 SVG -->
|
| 147 |
+
<svg id="footprints-svg" viewBox="0 0 1200 900" preserveAspectRatio="xMidYMid meet">
|
| 148 |
+
<!-- 重新設計的、更不規則的曲線路徑 -->
|
| 149 |
+
<path class="footprint-path" d="M600 900 C 500 880, 350 800, 300 675" />
|
| 150 |
+
<path class="footprint-path" d="M600 900 C 600 700, 380 550, 444 315" />
|
| 151 |
+
<path class="footprint-path" d="M600 900 C 750 850, 950 700, 1056 522" />
|
| 152 |
+
<path class="footprint-path" d="M600 900 C 700 750, 980 500, 1056 252" />
|
| 153 |
+
</svg>
|
| 154 |
+
|
| 155 |
+
<!-- 地點連結 -->
|
| 156 |
+
<a id="algebra-hill" href="algebra.html" class="location-link">
|
| 157 |
+
<span class="label">代數之丘</span>
|
| 158 |
+
</a>
|
| 159 |
+
<a id="philosophy-tower" href="philosophy.html" class="location-link">
|
| 160 |
+
<span class="label">哲學之塔</span>
|
| 161 |
+
</a>
|
| 162 |
+
<a id="gemstone-cave" href="gemstone.html" class="location-link">
|
| 163 |
+
<span class="label">寶石洞窟</span>
|
| 164 |
+
</a>
|
| 165 |
+
<a id="harbor-bay" href="harbor.html" class="location-link">
|
| 166 |
+
<span class="label">海風港灣</span>
|
| 167 |
+
</a>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<h1 class="main-title text-4xl md:text-5xl font-bold text-white text-center" style="text-shadow: 3px 3px 5px rgba(0,0,0,0.5);">歡迎來到數學探險島</h1>
|
| 171 |
+
|
| 172 |
+
<!-- 設計者資訊 -->
|
| 173 |
+
<div class="absolute bottom-4 right-4 text-right text-white text-sm opacity-80 z-10" style="text-shadow: 1px 1px 3px rgba(0,0,0,0.7);">
|
| 174 |
+
<p>遊戲設計者:新竹縣精華國中藍星宇</p>
|
| 175 |
+
<p>FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" class="underline hover:text-yellow-300 transition-colors">萬物皆數</a></p>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
</body>
|
| 179 |
+
</html>
|
| 180 |
+
|
philosophy.html
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-Hant">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>數學探險島 - 哲學之塔</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
body {
|
| 13 |
+
font-family: 'Noto Sans TC', sans-serif;
|
| 14 |
+
background-image: url('https://i.meee.com.tw/wCWCOGx.png');
|
| 15 |
+
background-size: cover;
|
| 16 |
+
background-position: center;
|
| 17 |
+
background-attachment: fixed;
|
| 18 |
+
}
|
| 19 |
+
.tower-bg {
|
| 20 |
+
background-image: url('https://www.transparenttextures.com/patterns/stone-wall.png');
|
| 21 |
+
background-color: #e2e8f0;
|
| 22 |
+
}
|
| 23 |
+
/* 自訂拉桿樣式 */
|
| 24 |
+
input[type=range] {
|
| 25 |
+
-webkit-appearance: none;
|
| 26 |
+
width: 100%;
|
| 27 |
+
background: transparent;
|
| 28 |
+
}
|
| 29 |
+
input[type=range]:focus {
|
| 30 |
+
outline: none;
|
| 31 |
+
}
|
| 32 |
+
input[type=range]::-webkit-slider-runnable-track {
|
| 33 |
+
width: 100%;
|
| 34 |
+
height: 12px;
|
| 35 |
+
cursor: pointer;
|
| 36 |
+
background: #93c5fd;
|
| 37 |
+
border-radius: 5px;
|
| 38 |
+
border: 1px solid #60a5fa;
|
| 39 |
+
}
|
| 40 |
+
input[type=range]::-webkit-slider-thumb {
|
| 41 |
+
border: 2px solid #3b82f6;
|
| 42 |
+
height: 30px;
|
| 43 |
+
width: 30px;
|
| 44 |
+
border-radius: 50%;
|
| 45 |
+
background: #eff6ff;
|
| 46 |
+
cursor: pointer;
|
| 47 |
+
-webkit-appearance: none;
|
| 48 |
+
margin-top: -10px;
|
| 49 |
+
}
|
| 50 |
+
/* 成功時的閃爍動畫 */
|
| 51 |
+
@keyframes flash {
|
| 52 |
+
0%, 100% { box-shadow: 0 0 20px 5px rgba(34, 197, 94, 0.7); }
|
| 53 |
+
50% { box-shadow: 0 0 5px 0px rgba(34, 197, 94, 0.2); }
|
| 54 |
+
}
|
| 55 |
+
.success-flash {
|
| 56 |
+
animation: flash 1.5s ease-in-out;
|
| 57 |
+
}
|
| 58 |
+
/* 測驗選項樣式 */
|
| 59 |
+
.quiz-option {
|
| 60 |
+
display: block;
|
| 61 |
+
padding: 0.75rem 1rem;
|
| 62 |
+
border: 2px solid #e5e7eb;
|
| 63 |
+
border-radius: 0.5rem;
|
| 64 |
+
margin-bottom: 0.5rem;
|
| 65 |
+
cursor: pointer;
|
| 66 |
+
transition: all 0.2s;
|
| 67 |
+
}
|
| 68 |
+
.quiz-option:hover {
|
| 69 |
+
background-color: #f3f4f6;
|
| 70 |
+
}
|
| 71 |
+
input[type="radio"]:checked + .quiz-option {
|
| 72 |
+
background-color: #dbeafe;
|
| 73 |
+
border-color: #60a5fa;
|
| 74 |
+
}
|
| 75 |
+
.question-container.incorrect {
|
| 76 |
+
border: 2px solid #ef4444;
|
| 77 |
+
border-radius: 0.75rem;
|
| 78 |
+
padding: 1rem;
|
| 79 |
+
background-color: #fee2e2;
|
| 80 |
+
}
|
| 81 |
+
</style>
|
| 82 |
+
</head>
|
| 83 |
+
<body class="flex items-center justify-center min-h-screen p-4">
|
| 84 |
+
|
| 85 |
+
<div id="main-container" class="container mx-auto max-w-5xl">
|
| 86 |
+
|
| 87 |
+
<!-- 故事引導畫面 -->
|
| 88 |
+
<div id="story-view" class="bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8 text-center">
|
| 89 |
+
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 text-center mb-6">前情提要:西帕索斯的悲劇</h1>
|
| 90 |
+
<div class="border rounded-lg shadow-inner bg-gray-100 p-12 flex flex-col items-center justify-center" style="height: 50vh;">
|
| 91 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-indigo-300 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 92 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v11.494m-9-5.747h18" />
|
| 93 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v11.494m-9-5.747h18" />
|
| 94 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 18h16" />
|
| 95 |
+
</svg>
|
| 96 |
+
<p class="text-xl text-gray-600">故事即將展開...</p>
|
| 97 |
+
<p class="mt-4 text-lg text-indigo-600 font-semibold">點擊下方按鈕,在新分頁閱讀故事前情提要!</p>
|
| 98 |
+
</div>
|
| 99 |
+
<a href="https://g.co/gemini/share/d181055c5aa2" target="_blank" id="story-link-button" class="mt-8 inline-block w-full md:w-auto bg-blue-500 text-white font-bold py-3 px-8 rounded-lg hover:bg-blue-600 transition-colors shadow-lg text-lg">
|
| 100 |
+
閱讀故事
|
| 101 |
+
</a>
|
| 102 |
+
<button id="start-quiz-button" class="mt-4 w-full md:w-auto bg-indigo-600 text-white font-bold py-3 px-8 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg">
|
| 103 |
+
開始挑戰!
|
| 104 |
+
</button>
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<!-- 閱讀測驗畫面 (新增) -->
|
| 108 |
+
<div id="quiz-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8">
|
| 109 |
+
<h1 class="text-3xl font-bold text-gray-800 text-center mb-6">閱讀測��:西帕索斯的考驗</h1>
|
| 110 |
+
<div class="space-y-6 text-left">
|
| 111 |
+
<!-- 問題 1 -->
|
| 112 |
+
<div id="question-container-0" class="question-container">
|
| 113 |
+
<p class="font-semibold mb-2">1. 根據故事內容,西帕索斯為什麼被畢達哥拉斯囚禁在哲學之塔?</p>
|
| 114 |
+
<div class="space-y-2">
|
| 115 |
+
<label><input type="radio" name="q0" value="A" class="hidden"> <span class="quiz-option">(A) 因為他試圖偷取畢達哥拉斯的數學理論。</span></label>
|
| 116 |
+
<label><input type="radio" name="q0" value="B" class="hidden"> <span class="quiz-option">(B) 因為他在公開場合侮辱了畢達哥拉斯本人。</span></label>
|
| 117 |
+
<label><input type="radio" name="q0" value="C" class="hidden"> <span class="quiz-option">(C) 因為他發現了一個無法用分數或小數表示的數字,這與畢達哥拉斯學派的信念相衝突。</span></label>
|
| 118 |
+
<label><input type="radio" name="q0" value="D" class="hidden"> <span class="quiz-option">(D) 因為他沒能成功計算出一個正方形的面積。</span></label>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
<!-- 問題 2 -->
|
| 122 |
+
<div id="question-container-1" class="question-container">
|
| 123 |
+
<p class="font-semibold mb-2">2. 故事中,那個動搖了畢達哥拉斯學派信念的「神秘數字」,最初是源自於什麼?</p>
|
| 124 |
+
<div class="space-y-2">
|
| 125 |
+
<label><input type="radio" name="q1" value="A" class="hidden"> <span class="quiz-option">(A) 一個面積為3的圓形半徑。</span></label>
|
| 126 |
+
<label><input type="radio" name="q1" value="B" class="hidden"> <span class="quiz-option">(B) 一個面積為2的正方形邊長。</span></label>
|
| 127 |
+
<label><input type="radio" name="q1" value="C" class="hidden"> <span class="quiz-option">(C) 一個從未有人見過的全新立體圖形。</span></label>
|
| 128 |
+
<label><input type="radio" name="q1" value="D" class="hidden"> <span class="quiz-option">(D) 一個畢達哥拉斯自己提出的數學謎題。</span></label>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
<!-- 問題 3 -->
|
| 132 |
+
<div id="question-container-2" class="question-container">
|
| 133 |
+
<p class="font-semibold mb-2">3. 在故事的結尾,你(讀者)被賦予的主要任務是什麼?</p>
|
| 134 |
+
<div class="space-y-2">
|
| 135 |
+
<label><input type="radio" name="q2" value="A" class="hidden"> <span class="quiz-option">(A) 找到一把萬能鑰匙,趁半夜把西帕索斯從監獄裡救出來。</span></label>
|
| 136 |
+
<label><input type="radio" name="q2" value="B" class="hidden"> <span class="quiz-option">(B) 回到未來,尋找歷史文獻來證明西帕索斯是對的。</span></label>
|
| 137 |
+
<label><input type="radio" name="q2" value="C" class="hidden"> <span class="quiz-option">(C) 成為畢達哥拉斯的學徒,從內部瓦解他的學派。</span></label>
|
| 138 |
+
<label><input type="radio" name="q2" value="D" class="hidden"> <span class="quiz-option">(D) 找出幾個「神秘數字」的近似值,用具體的證據去說服畢達哥拉斯。</span></label>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
<div id="quiz-feedback" class="text-center font-semibold mt-6 min-h-[24px]"></div>
|
| 143 |
+
<div class="flex flex-col md:flex-row gap-4 mt-6">
|
| 144 |
+
<button id="reread-story-button" class="w-full md:w-1/2 bg-gray-500 text-white font-bold py-3 px-6 rounded-lg hover:bg-gray-600 transition-colors">再看一次故事</button>
|
| 145 |
+
<button id="submit-quiz-button" class="w-full md:w-1/2 bg-green-500 text-white font-bold py-3 px-6 rounded-lg hover:bg-green-600 transition-colors">提交答案</button>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<!-- 遊戲畫面 (預設隱藏) -->
|
| 150 |
+
<div id="game-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8 tower-bg">
|
| 151 |
+
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 text-center mb-2">哲學之塔</h1>
|
| 152 |
+
<p class="text-center text-gray-600 mb-6">幫助被囚禁的西帕索斯,找出正方形的神秘邊長!</p>
|
| 153 |
+
|
| 154 |
+
<div class="grid md:grid-cols-2 gap-8 items-center">
|
| 155 |
+
<!-- 左側:正方形展示區 -->
|
| 156 |
+
<div class="flex flex-col items-center justify-center bg-white/70 p-6 rounded-lg shadow-inner">
|
| 157 |
+
<div id="square-display" class="w-48 h-48 bg-blue-300 border-4 border-blue-500 flex items-center justify-center relative transition-transform duration-500">
|
| 158 |
+
<p class="text-2xl font-bold text-white">面積 = <span id="target-area-text">2</span></p>
|
| 159 |
+
</div>
|
| 160 |
+
<p class="mt-4 text-2xl font-mono text-gray-700">邊長 = <span id="target-sqrt-text">√2</span></p>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<!-- 右側:互動操作區 -->
|
| 164 |
+
<div class="flex flex-col space-y-6">
|
| 165 |
+
<div class="bg-white/80 p-4 rounded-lg text-center">
|
| 166 |
+
<p class="text-lg">你的猜測邊長 <span id="guess-sqrt-text" class="font-mono">√2</span> ≒ <span id="current-value" class="font-bold text-2xl text-indigo-600">1.50</span></p>
|
| 167 |
+
<p class="text-lg">邊長的平方(正方形面積):<span id="current-squared-value" class="font-bold text-2xl text-red-500">2.25</span></p>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<input type="range" id="value-slider" min="1" max="2" value="1.5" step="0.01" class="w-full">
|
| 171 |
+
|
| 172 |
+
<button id="check-button" class="w-full bg-green-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-green-600 transition-colors shadow-md text-xl">
|
| 173 |
+
確定答案
|
| 174 |
+
</button>
|
| 175 |
+
|
| 176 |
+
<div id="feedback-box" class="text-center text-xl font-semibold min-h-[32px]"></div>
|
| 177 |
+
<button id="next-level-button" class="w-full bg-indigo-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-indigo-600 transition-colors shadow-md text-xl hidden">
|
| 178 |
+
挑戰下一關
|
| 179 |
+
</button>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<!-- 生活應用畫面 (預設隱藏) -->
|
| 185 |
+
<div id="application-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8">
|
| 186 |
+
<h1 class="text-3xl md:text-4xl font-bold text-green-600 mb-4 text-center">恭喜你,成功解救了西帕索斯!</h1>
|
| 187 |
+
<p class="text-lg text-gray-700 mb-8 text-center">你們玩遊戲的時候,有沒有想過,為什麼角色撞到怪物就會扣血?為什麼子彈會正好打到你?這不是靠神奇的第六感,是靠數學──而且關鍵數學招式就是平方根!</p>
|
| 188 |
+
|
| 189 |
+
<div class="bg-blue-50 p-6 rounded-lg text-left">
|
| 190 |
+
<h2 class="text-2xl font-bold text-indigo-600 mb-4 text-center">🎮 遊戲物理引擎裡的平方根魔法</h2>
|
| 191 |
+
<div class="space-y-6">
|
| 192 |
+
<div>
|
| 193 |
+
<h3 class="font-semibold text-xl mb-2">判斷「有沒有撞到」</h3>
|
| 194 |
+
<p>遊戲世界是由座標組成的(就像地圖上的 X、Y 點)。當角色和怪物在不同位置,遊戲必須算兩者的距離,來判斷是不是近到可以碰撞。</p>
|
| 195 |
+
<p class="mt-2 p-3 bg-gray-200 rounded-md text-center font-mono text-lg">距離 = √((x₁ - x₂)² + (y₁ - y₂)²)</p>
|
| 196 |
+
<p class="mt-2">最後那個開根號,就是讓距離變回「真實長度」。沒有平方根,遊戲根本不知道你到底是不是碰到牆、怪物、寶箱。</p>
|
| 197 |
+
</div>
|
| 198 |
+
<div>
|
| 199 |
+
<h3 class="font-semibold text-xl mb-2">算出移動的真速度</h3>
|
| 200 |
+
<p>在 3D 遊戲中,速度不只有一個方向。例如你同時向前(X 軸)+ 向右(Y 軸)移動,總速度不是單純相加,而是:</p>
|
| 201 |
+
<p class="mt-2 p-3 bg-gray-200 rounded-md text-center font-mono text-lg">總速度 = √(水平速度² + 垂直速度²)</p>
|
| 202 |
+
<p class="mt-2">這樣遊戲才能正確決定動畫快慢、物理反應強度。</p>
|
| 203 |
+
</div>
|
| 204 |
+
<div>
|
| 205 |
+
<h3 class="font-semibold text-xl mb-2">模擬「真實的碰撞反應」</h3>
|
| 206 |
+
<p>角色被怪物推一下,會往斜方向飛。遊戲會算推力向量的大小(也就是「合力長度」)來決定你飛多遠,這裡也要平方根。</p>
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
<div class="mt-6 pt-6 border-t">
|
| 210 |
+
<p class="text-xl text-center"><span class="font-bold">💡 簡單比喻:</span>「平方根在遊戲引擎裡,就像一把『距離測量尺』,沒有它,遊戲世界就不知道東西有多近、有多快,甚至不會知道你到底撞到沒。」</p>
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
|
| 214 |
+
<div class="mt-12 text-center">
|
| 215 |
+
<a href="index.html" class="inline-block w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
|
| 216 |
+
回到探險島地圖
|
| 217 |
+
</a>
|
| 218 |
+
</div>
|
| 219 |
+
</div>
|
| 220 |
+
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
<script>
|
| 224 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 225 |
+
// --- 關卡設定 ---
|
| 226 |
+
const levels = [
|
| 227 |
+
{ target: 2, min: 1, max: 2, tolerance: 0.05, type: 'manual' },
|
| 228 |
+
{ target: 3, min: 1, max: 2, tolerance: 0.05, type: 'auto', speed: 0.007 },
|
| 229 |
+
{ target: 5, min: 2, max: 3, tolerance: 0.05, type: 'auto', speed: 0.011 }
|
| 230 |
+
];
|
| 231 |
+
let currentLevel = 0;
|
| 232 |
+
|
| 233 |
+
const quizQuestions = [
|
| 234 |
+
{ answer: 'C' },
|
| 235 |
+
{ answer: 'B' },
|
| 236 |
+
{ answer: 'D' }
|
| 237 |
+
];
|
| 238 |
+
|
| 239 |
+
// --- DOM 元素 ---
|
| 240 |
+
const storyView = document.getElementById('story-view');
|
| 241 |
+
const startQuizButton = document.getElementById('start-quiz-button');
|
| 242 |
+
const quizView = document.getElementById('quiz-view');
|
| 243 |
+
const submitQuizButton = document.getElementById('submit-quiz-button');
|
| 244 |
+
const rereadStoryButton = document.getElementById('reread-story-button');
|
| 245 |
+
const quizFeedback = document.getElementById('quiz-feedback');
|
| 246 |
+
const gameView = document.getElementById('game-view');
|
| 247 |
+
const applicationView = document.getElementById('application-view');
|
| 248 |
+
const squareDisplay = document.getElementById('square-display');
|
| 249 |
+
const targetAreaText = document.getElementById('target-area-text');
|
| 250 |
+
const targetSqrtText = document.getElementById('target-sqrt-text');
|
| 251 |
+
const guessSqrtText = document.getElementById('guess-sqrt-text');
|
| 252 |
+
const currentValue = document.getElementById('current-value');
|
| 253 |
+
const currentSquaredValue = document.getElementById('current-squared-value');
|
| 254 |
+
const valueSlider = document.getElementById('value-slider');
|
| 255 |
+
const checkButton = document.getElementById('check-button');
|
| 256 |
+
const feedbackBox = document.getElementById('feedback-box');
|
| 257 |
+
const nextLevelButton = document.getElementById('next-level-button');
|
| 258 |
+
|
| 259 |
+
// --- 遊戲狀態 ---
|
| 260 |
+
let sliderAnimationId = null;
|
| 261 |
+
let sliderDirection = 1;
|
| 262 |
+
|
| 263 |
+
// --- 核心函式 ---
|
| 264 |
+
function initLevel(levelIndex) {
|
| 265 |
+
const level = levels[levelIndex];
|
| 266 |
+
|
| 267 |
+
if (sliderAnimationId) {
|
| 268 |
+
cancelAnimationFrame(sliderAnimationId);
|
| 269 |
+
sliderAnimationId = null;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
targetAreaText.textContent = level.target;
|
| 273 |
+
targetSqrtText.innerHTML = `√${level.target}`;
|
| 274 |
+
guessSqrtText.innerHTML = `√${level.target}`;
|
| 275 |
+
|
| 276 |
+
valueSlider.min = level.min;
|
| 277 |
+
valueSlider.max = level.max;
|
| 278 |
+
valueSlider.value = (level.min + level.max) / 2;
|
| 279 |
+
valueSlider.step = 0.01;
|
| 280 |
+
|
| 281 |
+
updateSliderDisplay();
|
| 282 |
+
feedbackBox.textContent = '';
|
| 283 |
+
feedbackBox.className = 'text-center text-xl font-semibold min-h-[32px]';
|
| 284 |
+
checkButton.disabled = false;
|
| 285 |
+
nextLevelButton.classList.add('hidden');
|
| 286 |
+
squareDisplay.classList.remove('success-flash');
|
| 287 |
+
|
| 288 |
+
if (level.type === 'manual') {
|
| 289 |
+
valueSlider.style.pointerEvents = 'auto';
|
| 290 |
+
checkButton.textContent = '確定答案';
|
| 291 |
+
} else {
|
| 292 |
+
valueSlider.style.pointerEvents = 'none';
|
| 293 |
+
checkButton.textContent = '按下鎖定!';
|
| 294 |
+
sliderDirection = 1;
|
| 295 |
+
startSliderAnimation();
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
if (levelIndex === levels.length - 1) {
|
| 299 |
+
nextLevelButton.textContent = '查看生活應用';
|
| 300 |
+
} else {
|
| 301 |
+
nextLevelButton.textContent = '挑戰下一關';
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
function startSliderAnimation() {
|
| 306 |
+
const level = levels[currentLevel];
|
| 307 |
+
let val = parseFloat(valueSlider.value);
|
| 308 |
+
val += sliderDirection * level.speed;
|
| 309 |
+
if (val >= level.max || val <= level.min) {
|
| 310 |
+
sliderDirection *= -1;
|
| 311 |
+
val = Math.max(level.min, Math.min(level.max, val));
|
| 312 |
+
}
|
| 313 |
+
valueSlider.value = val;
|
| 314 |
+
updateSliderDisplay();
|
| 315 |
+
sliderAnimationId = requestAnimationFrame(startSliderAnimation);
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
function updateSliderDisplay() {
|
| 319 |
+
const val = parseFloat(valueSlider.value);
|
| 320 |
+
const squaredVal = val * val;
|
| 321 |
+
currentValue.textContent = val.toFixed(2);
|
| 322 |
+
currentSquaredValue.textContent = squaredVal.toFixed(2);
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
function checkAnswer() {
|
| 326 |
+
const level = levels[currentLevel];
|
| 327 |
+
|
| 328 |
+
if (level.type === 'auto' && sliderAnimationId) {
|
| 329 |
+
cancelAnimationFrame(sliderAnimationId);
|
| 330 |
+
sliderAnimationId = null;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
checkButton.disabled = true;
|
| 334 |
+
|
| 335 |
+
const val = parseFloat(valueSlider.value);
|
| 336 |
+
const squaredVal = val * val;
|
| 337 |
+
const difference = Math.abs(squaredVal - level.target);
|
| 338 |
+
feedbackBox.textContent = '';
|
| 339 |
+
|
| 340 |
+
if (difference <= level.tolerance) {
|
| 341 |
+
feedbackBox.textContent = '太棒了!你找到了!';
|
| 342 |
+
feedbackBox.className = 'text-center text-xl font-semibold text-green-600';
|
| 343 |
+
nextLevelButton.classList.remove('hidden');
|
| 344 |
+
squareDisplay.classList.add('success-flash');
|
| 345 |
+
} else {
|
| 346 |
+
const hint = squaredVal < level.target ? '太小了' : '太大了';
|
| 347 |
+
feedbackBox.textContent = `喔喔!${hint},再試一次!`;
|
| 348 |
+
feedbackBox.className = 'text-center text-xl font-semibold text-red-600';
|
| 349 |
+
|
| 350 |
+
if (level.type === 'auto') {
|
| 351 |
+
setTimeout(() => {
|
| 352 |
+
initLevel(currentLevel);
|
| 353 |
+
}, 2000);
|
| 354 |
+
} else {
|
| 355 |
+
checkButton.disabled = false;
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
function loadNext() {
|
| 361 |
+
currentLevel++;
|
| 362 |
+
if (currentLevel < levels.length) {
|
| 363 |
+
initLevel(currentLevel);
|
| 364 |
+
} else {
|
| 365 |
+
gameView.classList.add('hidden');
|
| 366 |
+
applicationView.classList.remove('hidden');
|
| 367 |
+
}
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
function checkQuiz() {
|
| 371 |
+
let allCorrect = true;
|
| 372 |
+
quizFeedback.textContent = '';
|
| 373 |
+
quizFeedback.classList.remove('text-red-500', 'text-green-600');
|
| 374 |
+
|
| 375 |
+
quizQuestions.forEach((question, index) => {
|
| 376 |
+
const container = document.getElementById(`question-container-${index}`);
|
| 377 |
+
const selected = document.querySelector(`input[name="q${index}"]:checked`);
|
| 378 |
+
|
| 379 |
+
container.classList.remove('incorrect');
|
| 380 |
+
|
| 381 |
+
if (!selected || selected.value !== question.answer) {
|
| 382 |
+
allCorrect = false;
|
| 383 |
+
container.classList.add('incorrect');
|
| 384 |
+
}
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
if (allCorrect) {
|
| 388 |
+
quizFeedback.textContent = '太棒了!你很用心在看故事喔!';
|
| 389 |
+
quizFeedback.classList.add('text-green-600');
|
| 390 |
+
submitQuizButton.disabled = true;
|
| 391 |
+
rereadStoryButton.disabled = true;
|
| 392 |
+
setTimeout(() => {
|
| 393 |
+
quizView.classList.add('hidden');
|
| 394 |
+
gameView.classList.remove('hidden');
|
| 395 |
+
}, 2000);
|
| 396 |
+
} else {
|
| 397 |
+
quizFeedback.textContent = '有題目答錯了,再檢查看看或重讀一次故事吧!';
|
| 398 |
+
quizFeedback.classList.add('text-red-500');
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
// --- 事件監聽 ---
|
| 403 |
+
startQuizButton.addEventListener('click', () => {
|
| 404 |
+
storyView.classList.add('hidden');
|
| 405 |
+
quizView.classList.remove('hidden');
|
| 406 |
+
submitQuizButton.disabled = false;
|
| 407 |
+
rereadStoryButton.disabled = false;
|
| 408 |
+
quizFeedback.textContent = '';
|
| 409 |
+
quizQuestions.forEach((_, index) => {
|
| 410 |
+
document.getElementById(`question-container-${index}`).classList.remove('incorrect');
|
| 411 |
+
});
|
| 412 |
+
});
|
| 413 |
+
|
| 414 |
+
rereadStoryButton.addEventListener('click', () => {
|
| 415 |
+
quizView.classList.add('hidden');
|
| 416 |
+
storyView.classList.remove('hidden');
|
| 417 |
+
});
|
| 418 |
+
|
| 419 |
+
submitQuizButton.addEventListener('click', checkQuiz);
|
| 420 |
+
valueSlider.addEventListener('input', updateSliderDisplay);
|
| 421 |
+
checkButton.addEventListener('click', checkAnswer);
|
| 422 |
+
nextLevelButton.addEventListener('click', loadNext);
|
| 423 |
+
|
| 424 |
+
// --- 初始啟動 ---
|
| 425 |
+
initLevel(currentLevel);
|
| 426 |
+
});
|
| 427 |
+
</script>
|
| 428 |
+
</body>
|
| 429 |
+
</html>
|