Spaces:
Running
Running
Upload 3 files
Browse files- README.md +132 -140
- function.html +482 -198
- sequence.html +5 -0
README.md
CHANGED
|
@@ -1,140 +1,132 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
##
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
#
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
``
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
###
|
| 35 |
-
|
| 36 |
-
* **
|
| 37 |
-
* **
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
* **
|
| 44 |
-
* **
|
| 45 |
-
* **
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
* **
|
| 51 |
-
* **
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
* **Phase
|
| 59 |
-
* **Phase
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
* **
|
| 63 |
-
* **
|
| 64 |
-
* **
|
| 65 |
-
* **Phase
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
* **Phase
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
* **
|
| 77 |
-
* **
|
| 78 |
-
|
| 79 |
-
* **
|
| 80 |
-
* **
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
*
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
- [
|
| 92 |
-
- [
|
| 93 |
-
- [
|
| 94 |
-
|
| 95 |
-
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
*
|
| 111 |
-
* **
|
| 112 |
-
* **
|
| 113 |
-
|
| 114 |
-
* **
|
| 115 |
-
* **
|
| 116 |
-
* **
|
| 117 |
-
* **
|
| 118 |
-
*
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
* **
|
| 123 |
-
* **
|
| 124 |
-
* **
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
---
|
| 135 |
-
---
|
| 136 |
-
## 7. 專案製作群 (Credits)
|
| 137 |
-
* **遊戲設計**: 新竹縣精華國中 藍星宇老師
|
| 138 |
-
* **社群**: [萬物皆數](https://www.facebook.com/groups/1554372228718393)
|
| 139 |
-
|
| 140 |
-
*Created by Antigravity for 11402 Semester Project*
|
|
|
|
| 1 |
+
# Math City: Cyber Chronicles (數學特區:未來都市)
|
| 2 |
+
## ⚠️ AI 行為準則 (System Instructions)
|
| 3 |
+
1. **語言要求:** 所有的對話、思考過程、Implementation Plan (實作計畫) 以及程式碼註解,請**嚴格使用「繁體中文 (Traditional Chinese)」,唯獨專有名詞可以用英文呈現**。
|
| 4 |
+
2. **例外:** 只有程式碼本身的變數名稱、函數名稱���留英文。
|
| 5 |
+
## 1. 專案願景 (Project Vision)
|
| 6 |
+
本專案為八年級下學期數學課程的遊戲化教學平台。透過「未來都市」的沉浸式包裝,讓學生以「特務/維護者」的身分,在解決城市危機的過程中學習數學概念。
|
| 7 |
+
|
| 8 |
+
**核心體驗**:
|
| 9 |
+
* **平台**:iPad 橫向全螢幕體驗 (Mobile Web App)。
|
| 10 |
+
* **風格**:Cyberpunk / Neon Noir (賽博龐克/霓虹暗黑)。
|
| 11 |
+
* **技術**:HTML5 Canvas + Tailwind CSS + Vanilla JS (無須建置工具)。
|
| 12 |
+
|
| 13 |
+
## 2. 檔案結構 (File Structure)
|
| 14 |
+
```
|
| 15 |
+
/
|
| 16 |
+
├── index.html # [入口] 城市全息地圖 (Math City Map)
|
| 17 |
+
├── sequence.html # [數列] 數列峽谷 (The Glitch Canyon)
|
| 18 |
+
├── function.html # [函數] 能源核心 (The Energy Core)
|
| 19 |
+
├── congruence.html # [全等] 全等重案組 (Congruence District) - {待開發}
|
| 20 |
+
├── parallel.html # [平行] 平行建構區 (Parallel Skyline) - {待開發}
|
| 21 |
+
└── assets/ # (建議) 存放圖片與音效資源
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## 3. 視覺設計系統 (Design System)
|
| 25 |
+
|
| 26 |
+
### 色彩計畫 (Neon Palette)
|
| 27 |
+
這座城市由四個區域組成,每個區域有其代表的主色調:
|
| 28 |
+
* **🟦 能源核心 (Cyan)**: `#06b6d4` (Cyan-500) - 代表科技、冷靜、函數邏輯。
|
| 29 |
+
* **🟨 數列峽谷 (Amber)**: `#f59e0b` (Amber-500) - 代表古老遺跡、警示、規律。
|
| 30 |
+
* **🟪 全等重案組 (Magenta)**: `#d946ef` (Fuchsia-500) - 代表神秘、霓虹街頭、偵探氛圍。
|
| 31 |
+
* **🟩 平行建構區 (Green)**: `#22c55e` (Green-500) - 代表建設、雷射光束、結構。
|
| 32 |
+
* **🌑 背景基調**: `#050510` (Very Dark Blue) - 極致黑,襯托霓虹光。
|
| 33 |
+
|
| 34 |
+
### UI 規範
|
| 35 |
+
* **字體**: 'Orbitron' (數字/英文標題), 'Noto Sans TC' (中文內文)。
|
| 36 |
+
* **介面**: 玻璃擬態 (Glassmorphism),半透明深色背景 + 亮色邊框。
|
| 37 |
+
* **互動**: 針對觸控優化的大按鈕,避免依賴 Hover 效果。
|
| 38 |
+
|
| 39 |
+
## 4. 遊戲模組詳解 (Game Modules)
|
| 40 |
+
|
| 41 |
+
### A. 數列峽谷 (The Glitch Canyon) - `sequence.html` [已完成/需微調]
|
| 42 |
+
* **數學單元**:等差數列與級數。
|
| 43 |
+
* **劇情**:城市邊緣的古老程式碼層出現 Glitch,階梯消失。
|
| 44 |
+
* **玩法**:動作跳躍遊戲。
|
| 45 |
+
* **Phase 1 (觀念檢測)**:進入關卡前,先通過數列填空測驗,確認對首項與公差的理解。
|
| 46 |
+
* **Phase 2 (數列跑酷)**:動作遊戲階段。
|
| 47 |
+
* **視覺風格**:全區統一碎石 (Gravel) 紋理,數位荒野主題。
|
| 48 |
+
* **規則**:僅有數列的「前兩項」會有琥珀色外框提示,後續特定平台外觀一致,玩家需自行計算並跳上正確數字平台。
|
| 49 |
+
* **裝飾**:仙人掌與骨骸等裝飾物僅生成於無數字的安全平台上,不干擾作答。
|
| 50 |
+
* **Phase 3 (召喚儀式)**:終點的互動教學。利用「倒轉複製」概念,將階梯補成長方形,引導學生推導出等差級數求和公式 $S_n = \frac{(a_1+a_n) \times n}{2}$。
|
| 51 |
+
* **Phase 4 (核心回顧)**:(Gold Standard) 遊戲結束後顯示重點摘要,包含「等差數列定義」、「公差公式」、「級數和公式」,對應學習單內容。
|
| 52 |
+
|
| 53 |
+
### B. 能源核心 (The Energy Core) - `function.html` [已完成]
|
| 54 |
+
* **數學單元**:線型函數 ($y = ax + b$)。
|
| 55 |
+
* **劇情**:中央發電廠核心不穩,需要輸入正確的晶石量以維持能量平衡。
|
| 56 |
+
* **玩法**:數據分析與邏輯推導。
|
| 57 |
+
* **Phase 1 (蒐集數據)**:輸入 $x$ (晶石),觀察 $y$ (電力),利用描點法找出函數關係。
|
| 58 |
+
* **Phase 2 (規律分析)**:觀察圖表上的點,辨識出「直線」規律,並利用規律預測未來。
|
| 59 |
+
* **Phase 3 (過載危機)**:系統給出目標電力 $y=203$,玩家需根據 $y=2x+3$ 反求投入量 $x$。
|
| 60 |
+
* **Phase 4 (進階推導)**:(Advanced Deduction) 核心重置,公式改變 ($y=ax+b$)。
|
| 61 |
+
* **Step 1 (找截距 b)**:投入 $x=0$,觀察 $y$ 值,解出 $b$。
|
| 62 |
+
* **Step 2 (找斜率 a)**:投入 $x=1$,觀察變化量,解出 $a$。
|
| 63 |
+
* **Step 3 (驗算)**:利用新公式 $y=3x+5$ 驗算 $x=10$ 的結果,並用圖表上的點 ($10,35$) 進行再確認。
|
| 64 |
+
* **Phase 5 (核心解鎖)**:輸入正確的 $a$ 與 $b$ 值,完成任務。
|
| 65 |
+
* **Phase 6 (脈衝同步)**:節奏遊戲,在音樂節拍下進行點擊,穩定核心能量,強化回饋感。
|
| 66 |
+
|
| 67 |
+
### C. 全等重案組 (Congruence District) - `congruence.html` [規劃中]
|
| 68 |
+
* **數學單元**:全等三角形判別 (SAS, ASA, SSS, RHS)。
|
| 69 |
+
* **劇情**:追捕偽裝大師「三角魔人」。
|
| 70 |
+
* **玩法**:偵探解謎。
|
| 71 |
+
* **Phase 1 (蒐證)**:在案發現場使用放大鏡蒐集「邊長」與「角度」線索。
|
| 72 |
+
* **Phase 2 (指認)**:在警局指認牆上,利用蒐集的條件篩選嫌疑犯。
|
| 73 |
+
* **Phase 3 (審判/回顧)**:整理案情,總結全等性質(SAS, ASA...),並解釋為何 SSA (搖擺分身) 無法全等。
|
| 74 |
+
|
| 75 |
+
### D. 平行建構區 (Parallel Skyline) - `parallel.html` [規劃中]
|
| 76 |
+
* **數學單元**:平行線截角性質、特殊四邊形。
|
| 77 |
+
* **劇情**:建設通往雲端的摩天大樓。
|
| 78 |
+
* **玩法**:建築工程。
|
| 79 |
+
* **平行光束**:調整雷射發射器角度,利用「內錯角相等」或「同側內角互補」原理接通橋樑。
|
| 80 |
+
* **四邊形物流**:輸送帶上出現各種四邊形建材,需根據對角線性質 (是否等長/垂直/平分) 快速分類。
|
| 81 |
+
* **Phase 3 (完工驗收)**:展示建築藍圖,總結平行線截角性質與特殊四邊形的判別家族樹。
|
| 82 |
+
|
| 83 |
+
## 5. 開發路徑 (Roadmap)
|
| 84 |
+
- [x] **Phase 1: 基礎建設**
|
| 85 |
+
- [x] 建立 `index.html` 城市地圖。
|
| 86 |
+
- [x] 生成 iPad 專用背景圖。
|
| 87 |
+
- [x] 統一導航系統 (Back to Map 按鈕)。
|
| 88 |
+
- [x] **Phase 2: 現有程式優化**
|
| 89 |
+
- [x] `function.html`: 音量核心遊戲完整化、Phase 4 進階邏輯完備、節奏遊戲計分平衡。
|
| 90 |
+
- [x] `sequence.html`: 統一視覺風格,調整難度曲線。
|
| 91 |
+
- [ ] **Phase 3: 新關卡開發**
|
| 92 |
+
- [ ] 開發 `congruence.html` (全等重案組) 原型。
|
| 93 |
+
- [ ] 開發 `parallel.html` (平行建構區) 原型。
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## 6. 修正經驗與開發筆記 (Dev Log)
|
| 98 |
+
此區塊記錄開發過程中的重要迭代與修正經驗,供日後參考。
|
| 99 |
+
|
| 100 |
+
### [Sequence Canyon] 視覺與機制修正 (Visual & Mechanics Polish)
|
| 101 |
+
* **視覺一致性的陷阱**: 修正了平台顏色提示過於明顯的問題,統一為碎石紋理,強制學生進行計算而非辨色。
|
| 102 |
+
* **提示機制**: 僅由數列前兩項提供視覺提示,後續完全依賴計算。
|
| 103 |
+
* **裝飾物配置**: 將裝飾物移至非遊戲路徑的安全平台,避免干擾判斷。
|
| 104 |
+
|
| 105 |
+
### [Function Core] 進階教學設計 (Advanced Pedagogy Design)
|
| 106 |
+
* **鷹架理論 (Scaffolding) 的應用**:
|
| 107 |
+
* **變數分離**: 在推導 $y=ax+b$ 時,不要同時讓學生解兩個未知數。我們設計為:
|
| 108 |
+
1. 先令 $x=0$,讓 $a$ 項消失,專注解 $b$ (截距)。
|
| 109 |
+
2. 知道 $b$ 後,再令 $x=1$,專注解 $a$ (斜率)。
|
| 110 |
+
* 這樣能讓學生理解每個係數的幾何意義,而非單純背誦解聯立方程式。
|
| 111 |
+
* **視覺與代數的連結 (Visual-Algebra Connection)**:
|
| 112 |
+
* **圖表驗證**: 在 Phase 4 Step 3,驗算出 $y=35$ 後,不僅僅是數字正確,程式還引導視線看向圖表上的 $(10, 35)$ 點。這強化了「代數解」與「幾何圖形上的點」是同一回事的概念。
|
| 113 |
+
* **正向回饋系統 (Positive Feedback Loop)**:
|
| 114 |
+
* **避免挫折感**: 將傳統的 `alert()` 錯誤彈窗,改為「介面上的脈衝紅框」與「引導式錯誤訊息」。
|
| 115 |
+
* **具體化錯誤**: 錯誤訊息不是只說「錯」,而是說「如果 x=2, y=11, 因 11 = 2a+5, 所以 a=?」。這將**錯誤轉化為另一個數學題目**,而非單純的懲罰。
|
| 116 |
+
* **節奏遊戲獎勵**: 在完成艱澀的運算後,Phase 6 的節奏遊戲提供純粹的感官刺激與高分回饋 (5000分),平衡大腦疲勞。
|
| 117 |
+
* **情境化總結 (Contextual Summary)**:
|
| 118 |
+
* 任務完成頁面不僅列出分數,更點出核心數學素養:「預測」與「建模」。讓學生知道他們剛剛做的計算,本質上就是科學家預測未來的過程。
|
| 119 |
+
|
| 120 |
+
### [Function Core] 介面優化與除錯經驗
|
| 121 |
+
* **按鈕鎖定邏輯**: 驗證成功後鎖定按鈕 (`disabled`) 非常重要,可防止重複觸發事件或重複計分。需確保 CSS Selector 選中正確的按鈕。
|
| 122 |
+
* **樣式與動畫**:
|
| 123 |
+
* **跳動 vs 呼吸**: `animate-bounce` 用於錯誤訊息有時過於滑稽且難以閱讀,改為 `animate-pulse` (呼吸燈/閃耀) 配合紅色邊框,既有警示效果又保持質感。
|
| 124 |
+
* **Canvas 文字清晰度**: 在 High-DPI 螢幕 (Retina) 上,Canvas 必須正確縮放 (Scale by DevicePixelRatio),否則文字會模糊。
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## 7. 專案製作群 (Credits)
|
| 129 |
+
* **遊戲設計**: 新竹縣精華國中 藍星宇老師
|
| 130 |
+
* **社群**: [萬物皆數](https://www.facebook.com/groups/1554372228718393)
|
| 131 |
+
|
| 132 |
+
*Created by Antigravity for 11402 Semester Project*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function.html
CHANGED
|
@@ -30,6 +30,8 @@
|
|
| 30 |
background-image: url('Assets/index/functionbg.png');
|
| 31 |
background-size: cover;
|
| 32 |
background-position: center;
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
/* Dark overlay for the whole page */
|
|
@@ -54,51 +56,13 @@
|
|
| 54 |
z-index: 0;
|
| 55 |
}
|
| 56 |
|
| 57 |
-
/*
|
| 58 |
-
#dialogue-
|
| 59 |
-
position: fixed;
|
| 60 |
-
bottom: 2rem;
|
| 61 |
-
left: 2rem;
|
| 62 |
-
width: 450px;
|
| 63 |
-
max-width: 90%;
|
| 64 |
-
z-index: 40;
|
| 65 |
-
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
@media (max-width: 768px) {
|
| 69 |
-
#dialogue-box {
|
| 70 |
-
left: 50%;
|
| 71 |
-
transform: translateX(-50%);
|
| 72 |
-
bottom: 1rem;
|
| 73 |
-
width: 95%;
|
| 74 |
-
}
|
| 75 |
-
}
|
| 76 |
|
| 77 |
#dialogue-box.translate-y-full {
|
| 78 |
transform: translateY(150%);
|
| 79 |
}
|
| 80 |
|
| 81 |
-
/* Banner */
|
| 82 |
-
#formula-banner {
|
| 83 |
-
position: fixed;
|
| 84 |
-
top: 2rem;
|
| 85 |
-
right: 2rem;
|
| 86 |
-
width: 380px;
|
| 87 |
-
z-index: 35;
|
| 88 |
-
transition: opacity 0.5s;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
@media (max-width: 768px) {
|
| 92 |
-
#formula-banner {
|
| 93 |
-
top: 0.5rem;
|
| 94 |
-
right: 0.5rem;
|
| 95 |
-
width: 200px;
|
| 96 |
-
/* Smaller on mobile */
|
| 97 |
-
transform: scale(0.8);
|
| 98 |
-
transform-origin: top right;
|
| 99 |
-
}
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
/* Tech UI Styling */
|
| 103 |
.glass-panel {
|
| 104 |
background: rgba(15, 23, 42, 0.95);
|
|
@@ -149,32 +113,38 @@
|
|
| 149 |
<canvas id="gameCanvas"></canvas>
|
| 150 |
|
| 151 |
<!-- Main UI Container -->
|
| 152 |
-
<div id="ui-container"
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
<span
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
</div>
|
| 168 |
</div>
|
| 169 |
-
</div>
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
<
|
| 176 |
</div>
|
| 177 |
-
|
| 178 |
</div>
|
| 179 |
|
| 180 |
<!-- Start Overlay -->
|
|
@@ -186,11 +156,11 @@
|
|
| 186 |
能源核心
|
| 187 |
</h1>
|
| 188 |
<h2
|
| 189 |
-
class="text-
|
| 190 |
Energy Core
|
| 191 |
</h2>
|
| 192 |
<button onclick="startGame()"
|
| 193 |
-
class="group relative px-10 py-4 bg-cyan-600/80 hover:bg-cyan-500 text-white font-bold rounded-xl transition-all shadow-[0_0_20px_rgba(6,182,212,0.4)] text-
|
| 194 |
<span class="relative z-10">進入核心</span>
|
| 195 |
<div
|
| 196 |
class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:animate-shimmer">
|
|
@@ -219,6 +189,7 @@
|
|
| 219 |
STORY_BRIEF: 1,
|
| 220 |
PHASE1_INTRO: 2,
|
| 221 |
PHASE2_Q: 3, // General Question State
|
|
|
|
| 222 |
PHASE2_PATTERN: 4,
|
| 223 |
PHASE2_PREDICTION_INTRO: 4.5,
|
| 224 |
PHASE3_CRISIS: 5,
|
|
@@ -263,6 +234,14 @@
|
|
| 263 |
let qStep = 0;
|
| 264 |
let currentState = STATE.INIT;
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
// --- Audio ---
|
| 267 |
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
| 268 |
function playSound(type) {
|
|
@@ -431,6 +410,96 @@
|
|
| 431 |
ctx.scale(dpr, dpr);
|
| 432 |
}
|
| 433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
// --- State Management ---
|
| 435 |
function enterPhase(phase) {
|
| 436 |
currentState = phase;
|
|
@@ -439,21 +508,54 @@
|
|
| 439 |
|
| 440 |
function updateUI(errorMsg = null) {
|
| 441 |
const box = document.getElementById('dialogue-box');
|
| 442 |
-
|
| 443 |
const formulaBanner = document.getElementById('formula-banner');
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
let html = '';
|
| 446 |
|
| 447 |
switch (currentState) {
|
| 448 |
case STATE.STORY_BRIEF:
|
| 449 |
formulaBanner.classList.add('hidden');
|
|
|
|
| 450 |
html = `
|
| 451 |
-
<h2 class="text-
|
| 452 |
-
<p class="text-slate-200 text-
|
| 453 |
能源核心是整個城市的電力來源,但現在供電系統出了一些問題,需要你來協助修復。<br><br>
|
| 454 |
但在正式維修前,我們需要先檢測你的運算能力。
|
| 455 |
</p>
|
| 456 |
-
<button onclick="enterPhase(STATE.PHASE1_INTRO)" class="w-full py-
|
| 457 |
開始檢測能力
|
| 458 |
</button>
|
| 459 |
`;
|
|
@@ -462,16 +564,16 @@
|
|
| 462 |
case STATE.PHASE1_INTRO:
|
| 463 |
formulaBanner.classList.remove('hidden');
|
| 464 |
html = `
|
| 465 |
-
<h2 class="text-
|
| 466 |
-
<div class="text-center bg-slate-800/80 p-6 rounded-xl border border-cyan-500/30 mb-
|
| 467 |
-
<div class="font-tech text-
|
| 468 |
<span class="text-cyan-400">y</span> = 2<span class="text-amber-400">x</span> + 3
|
| 469 |
</div>
|
| 470 |
-
<p class="text-slate-300 text-
|
| 471 |
-
每投入 <span class="text-amber-400 font-bold text-
|
| 472 |
</p>
|
| 473 |
</div>
|
| 474 |
-
<button onclick="startQPhase()" class="w-full py-
|
| 475 |
我準備好了 (NEXT)
|
| 476 |
</button>
|
| 477 |
`;
|
|
@@ -481,30 +583,110 @@
|
|
| 481 |
formulaBanner.classList.remove('hidden');
|
| 482 |
const xVal = checkPoints[qStep];
|
| 483 |
html = `
|
| 484 |
-
<h3 class="text-
|
| 485 |
-
<p class="text-slate-300 mb-6 text-lg">
|
| 486 |
-
若投入 <span class="text-amber-400 font-bold text-2xl drop-shadow">${xVal}</span> 顆晶石 <span class="text-slate-400">(<span class="text-amber-400 font-bold">x=${xVal}</span>)</span>...
|
| 487 |
-
</p>
|
| 488 |
|
| 489 |
-
<div class="
|
| 490 |
-
<
|
| 491 |
-
<div class="
|
| 492 |
-
<
|
| 493 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
</div>
|
| 495 |
</div>
|
| 496 |
|
| 497 |
-
${errorMsg ? `<div class="text-red-400 text-center font-bold mt-2 animate-
|
|
|
|
|
|
|
| 498 |
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
`;
|
| 503 |
break;
|
| 504 |
|
| 505 |
case STATE.PHASE2_PATTERN:
|
|
|
|
| 506 |
html = `
|
| 507 |
-
<h3 class="text-
|
| 508 |
<p class="text-slate-300 mb-6">觀察圖表上的這些點,它們呈現什麼樣的排列規律?</p>
|
| 509 |
<div class="grid grid-cols-3 gap-4">
|
| 510 |
<button onclick="checkPattern('A')" class="p-4 bg-slate-800 hover:bg-slate-700 rounded-xl border border-slate-600 font-bold text-slate-300 transition-all hover:border-cyan-400 hover:text-white">
|
|
@@ -521,12 +703,13 @@
|
|
| 521 |
break;
|
| 522 |
|
| 523 |
case STATE.PHASE2_PREDICTION_INTRO:
|
|
|
|
| 524 |
html = `
|
| 525 |
<h3 class="text-xl font-bold text-green-400 mb-4">分析完成</h3>
|
| 526 |
<div class="bg-slate-800/80 p-6 rounded-xl border border-green-500/30 mb-6">
|
| 527 |
<p class="text-slate-200 text-lg leading-relaxed font-bold">
|
| 528 |
沒錯,所有點都連成一條直線...<br>
|
| 529 |
-
<span class="text-cyan-400 block mt-2 text-
|
| 530 |
</p>
|
| 531 |
</div>
|
| 532 |
<button onclick="enterPhase(STATE.PHASE3_CRISIS)" class="w-full py-4 bg-red-600 hover:bg-red-500 text-white rounded-xl font-bold text-xl shadow-lg animate-pulse">
|
|
@@ -536,22 +719,23 @@
|
|
| 536 |
break;
|
| 537 |
|
| 538 |
case STATE.PHASE3_CRISIS:
|
|
|
|
| 539 |
html = `
|
| 540 |
<div class="border-l-4 border-red-500 pl-6">
|
| 541 |
-
<h3 class="text-
|
| 542 |
-
<p class="text-slate-200 mb-6 text-
|
| 543 |
-
因為工業區需求激增,系統現在需要供給 <span class="text-cyan-400 font-bold text-
|
| 544 |
<br>根據 y=2x+3,請問要投入多少晶石 <span class="text-amber-400 font-bold">(x)</span> 才足夠?
|
| 545 |
</p>
|
| 546 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl flex-wrap">
|
| 547 |
-
<span class="font-tech text-amber-400 text-
|
| 548 |
-
<input id="ans-input" type="number" class="tech-input flex-1 min-w-[100px] p-2 rounded text-
|
| 549 |
<button onclick="checkFinalAnswer()" class="px-6 py-3 bg-red-600 hover:bg-red-500 text-white rounded font-bold shadow-[0_0_15px_rgba(239,68,68,0.5)] whitespace-nowrap flex-shrink-0">
|
| 550 |
投入晶石
|
| 551 |
</button>
|
| 552 |
</div>
|
| 553 |
|
| 554 |
-
${errorMsg ? `<div class="text-amber-300 text-center font-bold mt-2 text-xl animate-
|
| 555 |
|
| 556 |
</div>
|
| 557 |
`;
|
|
@@ -560,18 +744,18 @@
|
|
| 560 |
case STATE.SUCCESS:
|
| 561 |
html = `
|
| 562 |
<div class="text-center">
|
| 563 |
-
<h2 class="text-
|
| 564 |
-
<p class="text-slate-300 mb-6 text-
|
| 565 |
|
| 566 |
<!-- Teaching Content -->
|
| 567 |
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500/30 mb-6 text-left">
|
| 568 |
-
<h3 class="text-
|
| 569 |
-
<p class="text-slate-200 text-
|
| 570 |
-
剛剛我們使用的「能量轉換法則」,在數學上就叫做<span class="text-amber-400 font-bold text-
|
| 571 |
</p>
|
| 572 |
-
<p class="text-slate-300 text-
|
| 573 |
函數就像一座<span class="text-cyan-400 font-bold">工廠</span>:<br>
|
| 574 |
-
它會把原料 <span class="font-bold text-amber-400">x (晶石)</span>,藉由固定的規則,轉換成產品 <span class="font-bold text-cyan-400">y (電力)</span>。
|
| 575 |
</p>
|
| 576 |
</div>
|
| 577 |
|
|
@@ -594,13 +778,13 @@
|
|
| 594 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 595 |
html = `
|
| 596 |
<div class="border-l-4 border-amber-500 pl-6">
|
| 597 |
-
<h3 class="text-
|
| 598 |
-
<p class="text-slate-200 mb-6 text-
|
| 599 |
檢測到晶石純度下降,能量轉換公式已改變!<br>
|
| 600 |
系統無法自動運作,你必須重新推導公式。
|
| 601 |
</p>
|
| 602 |
<div class="bg-slate-800/50 p-4 rounded-xl mb-4 border border-slate-600">
|
| 603 |
-
<span class="font-tech text-
|
| 604 |
y = <span class="text-amber-400 animate-pulse">?</span>x + <span class="text-cyan-400 animate-pulse">?</span>
|
| 605 |
</span>
|
| 606 |
</div>
|
|
@@ -614,34 +798,39 @@
|
|
| 614 |
case STATE.PHASE4_TEST_B:
|
| 615 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 616 |
html = `
|
| 617 |
-
<h3 class="text-
|
| 618 |
<p class="text-slate-300 mb-4">
|
| 619 |
你需要測試不同的晶石投入量,來推導出新的公式。
|
| 620 |
-
<br><span class="text-
|
| 621 |
</p>
|
| 622 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl justify-center flex-wrap">
|
| 623 |
-
<span class="font-tech text-amber-400 text-
|
| 624 |
-
<input id="test-input" type="number" class="tech-input w-24 p-2 rounded text-
|
| 625 |
<button onclick="runPhase4Test()" class="px-6 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded font-bold shadow-lg whitespace-nowrap">
|
| 626 |
啟動運作
|
| 627 |
</button>
|
| 628 |
-
</div>
|
| 629 |
-
|
|
|
|
|
|
|
| 630 |
break;
|
| 631 |
|
| 632 |
case STATE.PHASE4_FIND_B:
|
|
|
|
| 633 |
html = `
|
| 634 |
-
<h3 class="text-
|
| 635 |
-
<p class="text-slate-300 mb-4">
|
| 636 |
當投入 <span class="text-amber-400 font-bold">0</span> 顆時,
|
| 637 |
電力顯示為 <span class="text-cyan-400 font-bold">5</span>。
|
|
|
|
|
|
|
| 638 |
</p>
|
| 639 |
-
<div class="flex items-center justify-center gap-2 text-
|
| 640 |
<span>b = </span>
|
| 641 |
<input id="ans-input" type="number" class="w-24 bg-slate-700 text-cyan-400 text-center rounded border border-slate-500 focus:border-cyan-400 outline-none p-1" placeholder="?" autofocus>
|
| 642 |
</div>
|
| 643 |
|
| 644 |
-
${errorMsg ? `<div class="text-red-400 text-center font-bold mb-4 animate-
|
| 645 |
|
| 646 |
<button onclick="checkPhase4B()" class="w-full py-3 bg-cyan-600 hover:bg-cyan-500 text-white rounded-xl font-bold">
|
| 647 |
確認校準 Check
|
|
@@ -650,42 +839,43 @@
|
|
| 650 |
break;
|
| 651 |
|
| 652 |
case STATE.PHASE4_TEST_A:
|
|
|
|
| 653 |
html = `
|
| 654 |
-
<h3 class="text-
|
| 655 |
<p class="text-slate-300 mb-4">
|
| 656 |
現在已知 b=5。接下來需要找出變化率 a。
|
| 657 |
-
<br><span class="text-
|
| 658 |
</p>
|
| 659 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl justify-center flex-wrap">
|
| 660 |
-
<span class="font-tech text-amber-400 text-
|
| 661 |
-
<input id="test-input-a" type="number" class="tech-input w-24 p-2 rounded text-
|
| 662 |
<button onclick="runPhase4TestA()" class="px-6 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded font-bold shadow-lg whitespace-nowrap">
|
| 663 |
啟動運作
|
| 664 |
</button>
|
| 665 |
</div>
|
|
|
|
| 666 |
`;
|
| 667 |
break;
|
| 668 |
|
| 669 |
case STATE.PHASE4_FIND_A:
|
| 670 |
formulaBanner.classList.remove('hidden');
|
| 671 |
html = `
|
| 672 |
-
<h3 class="text-
|
| 673 |
<p class="text-slate-300 mb-4">
|
| 674 |
-
測試投入 <span class="text-amber-400 font-bold">
|
| 675 |
-
電力變為 <span class="text-cyan-400 font-bold">
|
| 676 |
</p>
|
| 677 |
<div class="bg-slate-800/50 p-4 rounded-xl mb-4 text-center border border-slate-700">
|
| 678 |
-
<
|
| 679 |
-
<span class="text-2xl font-tech block mt-2 text-white"><span class="text-cyan-400">8</span> = a <span class="text-amber-400">× 1</span> + 5</span>
|
| 680 |
</div>
|
| 681 |
<p class="text-slate-300 mb-4 text-center">請問 <span class="text-amber-400 font-bold">a</span> 是多少?</p>
|
| 682 |
-
<div class="flex flex-wrap gap-2 mb-2 items-center justify-center text-
|
| 683 |
<span>a =</span>
|
| 684 |
-
<input id="ans-input" type="number" class="tech-input w-24 p-2 rounded text-
|
| 685 |
<button onclick="checkPhase4A()" class="w-full md:w-auto px-8 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded-xl font-bold shadow-lg transition-transform hover:scale-105 whitespace-nowrap text-lg font-sans">確認</button>
|
| 686 |
</div>
|
| 687 |
|
| 688 |
-
${errorMsg ? `<div class="text-red-400 text-center font-bold mb-2 animate-
|
| 689 |
`;
|
| 690 |
break;
|
| 691 |
|
|
@@ -700,18 +890,18 @@
|
|
| 700 |
case STATE.PHASE4_VERIFY:
|
| 701 |
formulaBanner.classList.remove('hidden');
|
| 702 |
html = `
|
| 703 |
-
<h3 class="text-
|
| 704 |
<p class="text-slate-300 mb-4">
|
| 705 |
假設公式為 <span class="font-bold text-white">y = 3x + 5</span>。
|
| 706 |
<br>若投入 <span class="text-amber-400 font-bold">10</span> 顆晶石,電力應為多少?
|
| 707 |
</p>
|
| 708 |
-
<div class="flex flex-wrap gap-2 mt-6 items-center justify-center text-
|
| 709 |
<span>y =</span>
|
| 710 |
-
<input id="ans-input" type="number" class="tech-input w-24 p-2 rounded text-
|
| 711 |
<button onclick="checkPhase4Verify()" class="w-full md:w-auto px-8 py-2 bg-green-600 hover:bg-green-500 text-white rounded-xl font-bold shadow-lg transition-transform hover:scale-105 whitespace-nowrap text-lg font-sans">驗證</button>
|
| 712 |
</div>
|
| 713 |
|
| 714 |
-
${errorMsg ? `<div class="text-red-400 text-center font-bold mt-4 animate-
|
| 715 |
`;
|
| 716 |
break;
|
| 717 |
|
|
@@ -719,8 +909,8 @@
|
|
| 719 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 720 |
html = `
|
| 721 |
<div class="text-center w-full">
|
| 722 |
-
<h3 class="text-
|
| 723 |
-
<div class="flex items-center justify-center gap-2 text-
|
| 724 |
<span class="text-cyan-400">y</span>
|
| 725 |
<span class="text-slate-500">=</span>
|
| 726 |
<input id="final-a" type="number" class="w-20 bg-slate-800 text-amber-400 text-center rounded border border-slate-600 focus:border-amber-400 outline-none p-2" placeholder="?">
|
|
@@ -731,6 +921,7 @@
|
|
| 731 |
<button onclick="unlockCore()" class="w-full py-4 bg-gradient-to-r from-amber-600 to-red-600 text-white rounded-xl font-bold text-xl shadow-lg hover:shadow-amber-500/50 transition-all border border-amber-500/30">
|
| 732 |
解鎖大門 (UNLOCK)
|
| 733 |
</button>
|
|
|
|
| 734 |
</div>
|
| 735 |
`;
|
| 736 |
break;
|
|
@@ -738,14 +929,14 @@
|
|
| 738 |
case STATE.PHASE6_INTRO:
|
| 739 |
formulaBanner.classList.add('hidden');
|
| 740 |
html = `
|
| 741 |
-
<h2 class="text-
|
| 742 |
-
<p class="text-slate-200 text-
|
| 743 |
核心已解鎖,但能量極不穩定!<br>
|
| 744 |
我們需要透過<span class="text-amber-400 font-bold">手動脈衝</span>來穩定核心頻率。
|
| 745 |
</p>
|
| 746 |
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500/30 mb-6 text-center">
|
| 747 |
<p class="text-slate-300 font-bold mb-2">操作說明</p>
|
| 748 |
-
<p class="text-
|
| 749 |
當光圈縮小至<span class="text-cyan-400">中心圓環</span>時,<br>
|
| 750 |
點擊滑鼠、螢幕或按下空白鍵。
|
| 751 |
</p>
|
|
@@ -764,66 +955,67 @@
|
|
| 764 |
break;
|
| 765 |
|
| 766 |
case STATE.PHASE7_SUMMARY:
|
| 767 |
-
|
| 768 |
-
//
|
| 769 |
-
box.className = "
|
| 770 |
|
| 771 |
const currentScore = rhythmState.score;
|
| 772 |
const highScore = localStorage.getItem('math_city_score_function') || 0;
|
| 773 |
|
| 774 |
html = `
|
| 775 |
<div class="text-center w-full h-full flex flex-col items-center">
|
| 776 |
-
<h2 class="text-
|
| 777 |
任務完成 SYSTEM RESTORED
|
| 778 |
</h2>
|
| 779 |
-
<div class="text-amber-400 font-tech text-
|
| 780 |
|
| 781 |
<!-- Score Board -->
|
| 782 |
-
<div class="flex gap-
|
| 783 |
<div class="text-center">
|
| 784 |
-
<div class="text-
|
| 785 |
<div class="text-4xl font-black text-cyan-400 font-tech shimmer">${currentScore}</div>
|
| 786 |
</div>
|
| 787 |
-
<div class="w-px bg-slate-
|
| 788 |
<div class="text-center">
|
| 789 |
-
<div class="text-
|
| 790 |
<div class="text-4xl font-black text-amber-400 font-tech">${highScore}</div>
|
| 791 |
</div>
|
| 792 |
</div>
|
| 793 |
|
| 794 |
-
|
|
|
|
| 795 |
<!-- Insight 1 -->
|
| 796 |
-
<div class="bg-slate-800/50 p-
|
| 797 |
-
<h3 class="flex items-center gap-
|
| 798 |
-
<span class="text-
|
| 799 |
</h3>
|
| 800 |
-
<p class="text-slate-300 leading-relaxed mb-
|
| 801 |
-
「當我們知道規則 <span class="bg-slate-900 px-
|
| 802 |
</p>
|
| 803 |
-
<div class="text-
|
| 804 |
回顧:就像你在階段三,算出需要投入多少晶石才能救急。
|
| 805 |
</div>
|
| 806 |
</div>
|
| 807 |
|
| 808 |
<!-- Insight 2 -->
|
| 809 |
-
<div class="bg-slate-800/50 p-
|
| 810 |
-
<h3 class="flex items-center gap-
|
| 811 |
-
<span class="text-
|
| 812 |
</h3>
|
| 813 |
-
<p class="text-slate-300 leading-relaxed mb-
|
| 814 |
「當規則改變或未知時,我們也能透過觀察數據的變化,反推回公式本身。」
|
| 815 |
</p>
|
| 816 |
-
<div class="text-
|
| 817 |
回顧:就像你在階段四,透過測試 x=0 和 x=1,重新找回消失的 a 與 b。
|
| 818 |
</div>
|
| 819 |
</div>
|
| 820 |
</div>
|
| 821 |
|
| 822 |
-
<p class="text-slate-400 italic mb-
|
| 823 |
「這就是科學家與工程師的超能力:觀察數據 <span class="text-white">→</span> 找出規則 <span class="text-white">→</span> 預測未來。」
|
| 824 |
</p>
|
| 825 |
|
| 826 |
-
<a href="index.html" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-
|
| 827 |
回到 Math City
|
| 828 |
</a>
|
| 829 |
</div>
|
|
@@ -832,7 +1024,7 @@
|
|
| 832 |
|
| 833 |
case STATE.PHASE4_VERIFY_TEST:
|
| 834 |
html = `
|
| 835 |
-
<h3 class="text-
|
| 836 |
<span class="mr-2">⚡</span> 最終驗證 FINAL VERIFICATION
|
| 837 |
</h3>
|
| 838 |
<p class="text-slate-300 leading-relaxed mb-6">
|
|
@@ -841,9 +1033,9 @@
|
|
| 841 |
</p>
|
| 842 |
|
| 843 |
<div class="flex items-center gap-4 mb-2">
|
| 844 |
-
<span class="text-
|
| 845 |
<div class="relative flex-1">
|
| 846 |
-
<input type="number" id="test-input-verify" class="tech-input w-full p-4 rounded-xl text-
|
| 847 |
<div class="absolute right-4 top-1/2 -translate-y-1/2 text-slate-500 text-sm">晶石</div>
|
| 848 |
</div>
|
| 849 |
</div>
|
|
@@ -884,16 +1076,9 @@
|
|
| 884 |
|
| 885 |
if (val === correctY) {
|
| 886 |
playSound('success');
|
| 887 |
-
addPoint(targetX, correctY);
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
if (qStep < checkPoints.length) {
|
| 891 |
-
// Next question
|
| 892 |
-
updateUI();
|
| 893 |
-
} else {
|
| 894 |
-
// All questions done
|
| 895 |
-
setTimeout(() => enterPhase(STATE.PHASE2_PATTERN), 500);
|
| 896 |
-
}
|
| 897 |
} else {
|
| 898 |
playSound('alarm');
|
| 899 |
input.classList.add('animate-shake');
|
|
@@ -903,6 +1088,38 @@
|
|
| 903 |
}
|
| 904 |
}
|
| 905 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
function checkFinalAnswer() {
|
| 907 |
const input = document.getElementById('ans-input');
|
| 908 |
const val = parseInt(input.value);
|
|
@@ -952,18 +1169,26 @@
|
|
| 952 |
|
| 953 |
function runPhase4Test() {
|
| 954 |
const input = document.getElementById('test-input');
|
| 955 |
-
const x =
|
| 956 |
|
| 957 |
if (isNaN(x)) return;
|
| 958 |
|
| 959 |
-
const y = 3 * x + 5;
|
| 960 |
-
addPoint(x, y);
|
| 961 |
-
playSound('tick');
|
| 962 |
-
|
| 963 |
if (x === 0) {
|
|
|
|
|
|
|
|
|
|
| 964 |
setTimeout(() => {
|
| 965 |
enterPhase(STATE.PHASE4_FIND_B);
|
| 966 |
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
}
|
| 968 |
}
|
| 969 |
|
|
@@ -985,21 +1210,32 @@
|
|
| 985 |
}
|
| 986 |
}
|
| 987 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 988 |
function runPhase4TestA() {
|
| 989 |
const input = document.getElementById('test-input-a');
|
| 990 |
const x = parseInt(input.value);
|
| 991 |
|
| 992 |
-
if (isNaN(x))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
|
| 994 |
const y = 3 * x + 5;
|
| 995 |
addPoint(x, y);
|
| 996 |
playSound('tick');
|
| 997 |
|
| 998 |
-
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
| 1002 |
-
|
|
|
|
|
|
|
| 1003 |
}
|
| 1004 |
|
| 1005 |
function checkPhase4A() {
|
|
@@ -1013,7 +1249,7 @@
|
|
| 1013 |
enterPhase(STATE.PHASE4_VERIFY);
|
| 1014 |
} else {
|
| 1015 |
playSound('alarm');
|
| 1016 |
-
updateUI(
|
| 1017 |
}
|
| 1018 |
}
|
| 1019 |
|
|
@@ -1023,10 +1259,28 @@
|
|
| 1023 |
playSound('success');
|
| 1024 |
// Auto verification visual
|
| 1025 |
addPoint(10, 35);
|
| 1026 |
-
updateUI("預測正確!投入 10 顆晶石,電力確實為 35!");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1027 |
|
| 1028 |
-
// Delay for user to see the result before unlocking
|
| 1029 |
-
|
|
|
|
| 1030 |
} else {
|
| 1031 |
playSound('alarm');
|
| 1032 |
// Use on-screen hint instead of alert
|
|
@@ -1044,9 +1298,17 @@
|
|
| 1044 |
enterPhase(STATE.PHASE6_INTRO);
|
| 1045 |
} else {
|
| 1046 |
playSound('alarm');
|
| 1047 |
-
document.
|
| 1048 |
-
|
| 1049 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
}
|
| 1051 |
}
|
| 1052 |
|
|
@@ -1225,6 +1487,9 @@
|
|
| 1225 |
function handleMiss() {
|
| 1226 |
rhythmState.energy = Math.max(0, rhythmState.energy - 2);
|
| 1227 |
rhythmState.combo = 0;
|
|
|
|
|
|
|
|
|
|
| 1228 |
spawnFloatingText("MISS", '#ef4444');
|
| 1229 |
playSound('alarm');
|
| 1230 |
}
|
|
@@ -1409,12 +1674,12 @@
|
|
| 1409 |
// Desktop: Graph takes up right 55% of screen. Mobile: Centered.
|
| 1410 |
const isDesktop = width > 768;
|
| 1411 |
|
| 1412 |
-
const gw = isDesktop ? width * 0.
|
| 1413 |
const gh = isDesktop ? height * 0.6 : Math.min(400, height * 0.5);
|
| 1414 |
|
| 1415 |
-
// On Desktop, position graph on the right side (Start at
|
| 1416 |
// On Mobile, center it
|
| 1417 |
-
const gx = isDesktop ? (width * 0.
|
| 1418 |
const gy = isDesktop ? height * 0.2 : height * 0.1;
|
| 1419 |
|
| 1420 |
// BG - Darker background for graph area to make it pop against the global image
|
|
@@ -1440,7 +1705,7 @@
|
|
| 1440 |
|
| 1441 |
// Grid
|
| 1442 |
ctx.textAlign = 'center';
|
| 1443 |
-
ctx.font = 'bold
|
| 1444 |
|
| 1445 |
const jumpX = graphScale.xMax > 20 ? 10 : 1;
|
| 1446 |
for (let i = 0; i <= graphScale.xMax; i += jumpX) {
|
|
@@ -1484,6 +1749,20 @@
|
|
| 1484 |
ctx.stroke();
|
| 1485 |
}
|
| 1486 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1487 |
collectedPoints.forEach(p => {
|
| 1488 |
const px = ox + p.x * unitX;
|
| 1489 |
const py = oy - p.y * unitY;
|
|
@@ -1495,8 +1774,8 @@
|
|
| 1495 |
ctx.stroke();
|
| 1496 |
|
| 1497 |
// Color coded coordinates: (x, y)
|
| 1498 |
-
ctx.font = 'bold
|
| 1499 |
-
const textY = py -
|
| 1500 |
|
| 1501 |
// Measure text to center it
|
| 1502 |
const strX = `${p.x}`;
|
|
@@ -1516,6 +1795,11 @@
|
|
| 1516 |
ctx.fillStyle = '#22d3ee'; ctx.fillText(strY, cursorX + wY / 2, textY); cursorX += wY; // Cyan Y
|
| 1517 |
ctx.fillStyle = '#cbd5e1'; ctx.fillText(")", cursorX + w2 / 2, textY);
|
| 1518 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1519 |
}
|
| 1520 |
|
| 1521 |
function updateParticles() {
|
|
@@ -1559,8 +1843,8 @@
|
|
| 1559 |
drawRhythmBackground();
|
| 1560 |
drawRhythmGame();
|
| 1561 |
drawCore();
|
| 1562 |
-
} else if (currentState =
|
| 1563 |
-
// Summary Mode
|
| 1564 |
drawRhythmBackground();
|
| 1565 |
drawCore();
|
| 1566 |
} else {
|
|
|
|
| 30 |
background-image: url('Assets/index/functionbg.png');
|
| 31 |
background-size: cover;
|
| 32 |
background-position: center;
|
| 33 |
+
background-repeat: no-repeat;
|
| 34 |
+
background-attachment: fixed;
|
| 35 |
}
|
| 36 |
|
| 37 |
/* Dark overlay for the whole page */
|
|
|
|
| 56 |
z-index: 0;
|
| 57 |
}
|
| 58 |
|
| 59 |
+
/* Removed legacy .dialogue-default and #formula-banner positioning */
|
| 60 |
+
/* Layout is now handled by #dialogue-container utility classes in HTML */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
#dialogue-box.translate-y-full {
|
| 63 |
transform: translateY(150%);
|
| 64 |
}
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
/* Tech UI Styling */
|
| 67 |
.glass-panel {
|
| 68 |
background: rgba(15, 23, 42, 0.95);
|
|
|
|
| 113 |
<canvas id="gameCanvas"></canvas>
|
| 114 |
|
| 115 |
<!-- Main UI Container -->
|
| 116 |
+
<div id="ui-container" class="fixed inset-0 z-10 pointer-events-none">
|
| 117 |
+
<!--
|
| 118 |
+
Dialogue Container:
|
| 119 |
+
- Anchored Vertically Centered Left (Desktop/Tablet)
|
| 120 |
+
- Flex Column to stack Banner on top of Box
|
| 121 |
+
-->
|
| 122 |
+
<div id="dialogue-container"
|
| 123 |
+
class="fixed top-1/2 left-4 right-4 md:left-12 md:right-auto md:w-auto -translate-y-1/2 flex flex-col gap-4 items-center md:items-start pointer-events-none z-40">
|
| 124 |
+
|
| 125 |
+
<!-- Formula Banner -->
|
| 126 |
+
<div id="formula-banner" class="hidden pointer-events-auto w-full transition-opacity duration-500">
|
| 127 |
+
<div
|
| 128 |
+
class="glass-panel px-6 py-4 md:px-8 md:py-5 rounded-3xl border-2 border-cyan-500/30 flex items-center justify-center gap-6 shadow-[0_0_20px_rgba(34,211,238,0.2)]">
|
| 129 |
+
<span
|
| 130 |
+
class="text-base md:text-xl text-slate-400 font-bold tracking-widest whitespace-nowrap">核心運作法則</span>
|
| 131 |
+
<div class="flex items-center gap-1 md:gap-2 text-3xl md:text-4xl font-bold font-tech">
|
| 132 |
+
<span class="text-cyan-400">y</span>
|
| 133 |
+
<span class="text-slate-500">=</span>
|
| 134 |
+
<span class="text-slate-200">2</span>
|
| 135 |
+
<span class="text-amber-400">x</span>
|
| 136 |
+
<span class="text-slate-500">+</span>
|
| 137 |
+
<span class="text-slate-200">3</span>
|
| 138 |
+
</div>
|
| 139 |
</div>
|
| 140 |
</div>
|
|
|
|
| 141 |
|
| 142 |
+
<!-- Interactive Dialogue Box -->
|
| 143 |
+
<div id="dialogue-box"
|
| 144 |
+
class="pointer-events-auto w-full glass-panel rounded-3xl p-6 md:p-8 transform transition-all duration-500 translate-y-20 opacity-0 min-h-[200px] flex flex-col justify-center shadow-[0_0_50px_rgba(0,0,0,0.5)] bg-slate-900/90 frame-border">
|
| 145 |
+
<!-- Dynamic Content Injected Here -->
|
| 146 |
+
</div>
|
| 147 |
</div>
|
|
|
|
| 148 |
</div>
|
| 149 |
|
| 150 |
<!-- Start Overlay -->
|
|
|
|
| 156 |
能源核心
|
| 157 |
</h1>
|
| 158 |
<h2
|
| 159 |
+
class="text-3xl md:text-4xl font-bold font-tech text-cyan-400 mb-8 tracking-[0.5em] uppercase opacity-80">
|
| 160 |
Energy Core
|
| 161 |
</h2>
|
| 162 |
<button onclick="startGame()"
|
| 163 |
+
class="group relative px-10 py-4 bg-cyan-600/80 hover:bg-cyan-500 text-white font-bold rounded-xl transition-all shadow-[0_0_20px_rgba(6,182,212,0.4)] text-3xl tracking-widest border border-cyan-400/50 backdrop-blur-sm overflow-hidden">
|
| 164 |
<span class="relative z-10">進入核心</span>
|
| 165 |
<div
|
| 166 |
class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:animate-shimmer">
|
|
|
|
| 189 |
STORY_BRIEF: 1,
|
| 190 |
PHASE1_INTRO: 2,
|
| 191 |
PHASE2_Q: 3, // General Question State
|
| 192 |
+
PHASE2_COORDINATE: 3.5, // New Step: Coordinate Confirmation
|
| 193 |
PHASE2_PATTERN: 4,
|
| 194 |
PHASE2_PREDICTION_INTRO: 4.5,
|
| 195 |
PHASE3_CRISIS: 5,
|
|
|
|
| 234 |
let qStep = 0;
|
| 235 |
let currentState = STATE.INIT;
|
| 236 |
|
| 237 |
+
// Guide Arrow State
|
| 238 |
+
let guideArrowState = {
|
| 239 |
+
active: false,
|
| 240 |
+
startTime: 0,
|
| 241 |
+
targetX: 0,
|
| 242 |
+
targetY: 0
|
| 243 |
+
};
|
| 244 |
+
|
| 245 |
// --- Audio ---
|
| 246 |
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
| 247 |
function playSound(type) {
|
|
|
|
| 410 |
ctx.scale(dpr, dpr);
|
| 411 |
}
|
| 412 |
|
| 413 |
+
function drawGuideArrows() {
|
| 414 |
+
const now = Date.now();
|
| 415 |
+
const elapsed = (now - guideArrowState.startTime) / 1000;
|
| 416 |
+
const duration = 2.0; // Show for 2 seconds
|
| 417 |
+
|
| 418 |
+
if (elapsed > duration) {
|
| 419 |
+
guideArrowState.active = false;
|
| 420 |
+
return;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
const tx = guideArrowState.targetX;
|
| 424 |
+
const ty = guideArrowState.targetY;
|
| 425 |
+
|
| 426 |
+
const isDesktop = width > 768;
|
| 427 |
+
|
| 428 |
+
// Convert graph coords to canvas coords
|
| 429 |
+
// Logic sync with drawGraph
|
| 430 |
+
const padding = 60;
|
| 431 |
+
const gw = isDesktop ? width * 0.53 : Math.min(600, width - 40);
|
| 432 |
+
const gh = isDesktop ? height * 0.6 : Math.min(400, height * 0.5);
|
| 433 |
+
|
| 434 |
+
// On Desktop, position graph on the right side (Start at 44% width)
|
| 435 |
+
// On Mobile, center it
|
| 436 |
+
const gx = isDesktop ? (width * 0.44) : (width - gw) / 2;
|
| 437 |
+
const gy = isDesktop ? height * 0.2 : height * 0.1;
|
| 438 |
+
|
| 439 |
+
const ox = gx + padding;
|
| 440 |
+
const oy = gy + gh - padding;
|
| 441 |
+
const drawW = gw - padding * 2;
|
| 442 |
+
const drawH = gh - padding * 2;
|
| 443 |
+
|
| 444 |
+
const unitX = drawW / graphScale.xMax;
|
| 445 |
+
const unitY = drawH / graphScale.yMax;
|
| 446 |
+
|
| 447 |
+
const canvasX = ox + tx * unitX;
|
| 448 |
+
const canvasY = oy - ty * unitY;
|
| 449 |
+
|
| 450 |
+
const progress = Math.min(elapsed * 2, 1); // Animate in quickly
|
| 451 |
+
|
| 452 |
+
ctx.save();
|
| 453 |
+
ctx.lineWidth = 4;
|
| 454 |
+
ctx.lineCap = 'round';
|
| 455 |
+
|
| 456 |
+
// Draw X-axis Arrow (Amber, Up)
|
| 457 |
+
ctx.strokeStyle = '#F59E0B'; // Amber-500
|
| 458 |
+
ctx.fillStyle = '#F59E0B';
|
| 459 |
+
ctx.beginPath();
|
| 460 |
+
ctx.moveTo(canvasX, oy); // Start from X-axis
|
| 461 |
+
ctx.lineTo(canvasX, oy - (oy - canvasY) * progress);
|
| 462 |
+
ctx.stroke();
|
| 463 |
+
|
| 464 |
+
// Draw Arrowhead if fully extended
|
| 465 |
+
if (progress > 0.8) {
|
| 466 |
+
ctx.beginPath();
|
| 467 |
+
ctx.moveTo(canvasX, canvasY + 10);
|
| 468 |
+
ctx.lineTo(canvasX - 5, canvasY + 20);
|
| 469 |
+
ctx.lineTo(canvasX + 5, canvasY + 20);
|
| 470 |
+
ctx.fill();
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
// Draw Y-axis Arrow (Cyan, Right)
|
| 474 |
+
ctx.strokeStyle = '#22D3EE'; // Cyan-400
|
| 475 |
+
ctx.fillStyle = '#22D3EE';
|
| 476 |
+
ctx.beginPath();
|
| 477 |
+
ctx.moveTo(ox, canvasY); // Start from Y-axis
|
| 478 |
+
ctx.lineTo(ox + (canvasX - ox) * progress, canvasY);
|
| 479 |
+
ctx.stroke();
|
| 480 |
+
|
| 481 |
+
// Draw Arrowhead
|
| 482 |
+
if (progress > 0.8) {
|
| 483 |
+
ctx.beginPath();
|
| 484 |
+
ctx.moveTo(canvasX - 10, canvasY);
|
| 485 |
+
ctx.lineTo(canvasX - 20, canvasY - 5);
|
| 486 |
+
ctx.lineTo(canvasX - 20, canvasY + 5);
|
| 487 |
+
ctx.fill();
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
// Highlight Point
|
| 491 |
+
if (progress > 0.9) {
|
| 492 |
+
ctx.beginPath();
|
| 493 |
+
ctx.arc(canvasX, canvasY, 8, 0, Math.PI * 2);
|
| 494 |
+
ctx.fillStyle = '#FFFFFF';
|
| 495 |
+
ctx.fill();
|
| 496 |
+
ctx.strokeStyle = '#F59E0B';
|
| 497 |
+
ctx.stroke();
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
ctx.restore();
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
// --- State Management ---
|
| 504 |
function enterPhase(phase) {
|
| 505 |
currentState = phase;
|
|
|
|
| 508 |
|
| 509 |
function updateUI(errorMsg = null) {
|
| 510 |
const box = document.getElementById('dialogue-box');
|
| 511 |
+
const container = document.getElementById('dialogue-container');
|
| 512 |
const formulaBanner = document.getElementById('formula-banner');
|
| 513 |
|
| 514 |
+
// Default Container State:
|
| 515 |
+
// Desktop: Fixed Left Side (Start 2%, Width 46%). Vertically Centered.
|
| 516 |
+
// Reduced gap: Left ends at 40%, Right starts at 48%.
|
| 517 |
+
container.className = "fixed z-40 left-4 right-4 top-1/2 -translate-y-1/2 md:translate-y-0 md:top-0 md:bottom-0 md:left-[5%] md:right-auto md:w-[35%] flex flex-col gap-6 justify-center items-center pointer-events-none";
|
| 518 |
+
|
| 519 |
+
// Default Box State
|
| 520 |
+
box.className = "pointer-events-auto w-full glass-panel rounded-3xl p-6 md:p-8 transform transition-all duration-500 min-h-[200px] flex flex-col justify-center shadow-[0_0_50px_rgba(0,0,0,0.5)] bg-slate-900/90 frame-border";
|
| 521 |
+
|
| 522 |
+
// Specific overrides
|
| 523 |
+
if (currentState === STATE.PHASE7_SUMMARY) {
|
| 524 |
+
// Summary: Center Screen
|
| 525 |
+
container.className = "fixed inset-0 flex items-center justify-center pointer-events-auto z-50";
|
| 526 |
+
box.className = "w-[95%] md:w-[800px] glass-panel rounded-3xl p-8 transition-all duration-500 min-h-[400px] flex flex-col justify-start shadow-[0_0_80px_rgba(0,0,0,0.8)] bg-slate-900/95 frame-border relative";
|
| 527 |
+
} else if (currentState === STATE.PHASE6_GAME) {
|
| 528 |
+
box.classList.add('hidden');
|
| 529 |
+
container.classList.add('hidden');
|
| 530 |
+
} else if (currentState === STATE.STORY_BRIEF) {
|
| 531 |
+
// Story Brief: Remove fixed width, use full container width
|
| 532 |
+
// box.classList.add('md:w-[600px]'); // REMOVED
|
| 533 |
+
box.classList.remove('hidden');
|
| 534 |
+
container.classList.remove('hidden');
|
| 535 |
+
} else {
|
| 536 |
+
// Normal Phase: Allow full width (controlled by container)
|
| 537 |
+
// box.classList.add('md:w-[480px]'); // REMOVED
|
| 538 |
+
formulaBanner.className = "hidden pointer-events-auto w-full transition-opacity duration-500";
|
| 539 |
+
|
| 540 |
+
box.classList.remove('hidden');
|
| 541 |
+
container.classList.remove('hidden');
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
box.classList.remove('translate-y-full', 'opacity-0');
|
| 545 |
+
|
| 546 |
let html = '';
|
| 547 |
|
| 548 |
switch (currentState) {
|
| 549 |
case STATE.STORY_BRIEF:
|
| 550 |
formulaBanner.classList.add('hidden');
|
| 551 |
+
|
| 552 |
html = `
|
| 553 |
+
<h2 class="text-4xl font-bold text-amber-400 mb-6">MATH CITY 電力危機</h2>
|
| 554 |
+
<p class="text-slate-200 text-3xl leading-relaxed mb-8">
|
| 555 |
能源核心是整個城市的電力來源,但現在供電系統出了一些問題,需要你來協助修復。<br><br>
|
| 556 |
但在正式維修前,我們需要先檢測你的運算能力。
|
| 557 |
</p>
|
| 558 |
+
<button onclick="enterPhase(STATE.PHASE1_INTRO)" class="w-full py-5 bg-cyan-600 hover:bg-cyan-500 text-white rounded-xl font-bold text-3xl shadow-lg">
|
| 559 |
開始檢測能力
|
| 560 |
</button>
|
| 561 |
`;
|
|
|
|
| 564 |
case STATE.PHASE1_INTRO:
|
| 565 |
formulaBanner.classList.remove('hidden');
|
| 566 |
html = `
|
| 567 |
+
<h2 class="text-3xl font-bold text-cyan-400 mb-4">核心運作法則確認</h2>
|
| 568 |
+
<div class="text-center bg-slate-800/80 p-6 rounded-xl border border-cyan-500/30 mb-8">
|
| 569 |
+
<div class="font-tech text-6xl mb-6">
|
| 570 |
<span class="text-cyan-400">y</span> = 2<span class="text-amber-400">x</span> + 3
|
| 571 |
</div>
|
| 572 |
+
<p class="text-slate-300 text-4xl leading-relaxed font-bold">
|
| 573 |
+
每投入 <span class="text-amber-400 font-bold text-7xl mx-2 border-b-2 border-amber-400/50">x</span> 顆晶石,<br>就會產生 <span class="text-cyan-400 font-bold text-7xl mx-2 border-b-2 border-cyan-400/50">y</span> 點電力。
|
| 574 |
</p>
|
| 575 |
</div>
|
| 576 |
+
<button onclick="startQPhase()" class="w-full py-5 bg-slate-700 hover:bg-slate-600 text-white rounded-xl font-bold text-xl border border-slate-500">
|
| 577 |
我準備好了 (NEXT)
|
| 578 |
</button>
|
| 579 |
`;
|
|
|
|
| 583 |
formulaBanner.classList.remove('hidden');
|
| 584 |
const xVal = checkPoints[qStep];
|
| 585 |
html = `
|
| 586 |
+
<h3 class="text-3xl font-bold text-white mb-2 text-center">能力檢測 (${qStep + 1}/${checkPoints.length})</h3>
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
+
<div class="grid grid-cols-2 gap-6 mb-2 mt-4">
|
| 589 |
+
<!-- Left: Question -->
|
| 590 |
+
<div class="flex flex-col gap-4">
|
| 591 |
+
<div class="text-cyan-400 font-bold text-xl border-b border-cyan-500/30 pb-2">1. 算出電力 (y)</div>
|
| 592 |
+
<p class="text-slate-300 text-lg">
|
| 593 |
+
若投入 <span class="text-amber-400 font-bold text-2xl">${xVal}</span> <span class="text-slate-400 text-base">(<span class="text-amber-400">x=${xVal}</span>)</span> 顆晶石...
|
| 594 |
+
</p>
|
| 595 |
+
<div class="flex items-center justify-center gap-2 flex-grow">
|
| 596 |
+
<span class="font-tech text-3xl text-cyan-400">y = </span>
|
| 597 |
+
<div class="relative w-32">
|
| 598 |
+
<input id="ans-input" type="number" class="tech-input w-full p-2 text-3xl rounded-lg" placeholder="?" autofocus>
|
| 599 |
+
<div class="absolute inset-0 border-2 border-slate-500/50 rounded-lg pointer-events-none"></div>
|
| 600 |
+
</div>
|
| 601 |
+
</div>
|
| 602 |
+
<button onclick="checkQAnswer()" class="w-full h-16 bg-cyan-600 hover:bg-cyan-500 text-white rounded-xl font-bold text-xl shadow-lg flex items-center justify-center">
|
| 603 |
+
確認電力
|
| 604 |
+
</button>
|
| 605 |
+
</div>
|
| 606 |
+
|
| 607 |
+
<!-- Right: Placeholder (Visible but Inactive) -->
|
| 608 |
+
<div class="flex flex-col gap-4 opacity-50 grayscale">
|
| 609 |
+
<div class="text-slate-400 font-bold text-xl border-b border-slate-500/30 pb-2">2. 坐標描點</div>
|
| 610 |
+
<div class="bg-slate-800/30 p-4 rounded-xl border border-slate-700/30 flex flex-col items-center justify-center flex-grow">
|
| 611 |
+
<div class="text-sm text-slate-500 mb-1">對應座標</div>
|
| 612 |
+
<div class="font-tech text-3xl font-bold text-slate-500">
|
| 613 |
+
(<span class="text-amber-400">${xVal}</span>, <span class="text-cyan-400">y</span>)
|
| 614 |
+
</div>
|
| 615 |
+
</div>
|
| 616 |
+
<button class="w-full h-16 bg-slate-700 text-slate-500 rounded-xl font-bold text-xl border border-slate-600 cursor-not-allowed flex items-center justify-center">
|
| 617 |
+
描繪點 (PLOT)
|
| 618 |
+
</button>
|
| 619 |
</div>
|
| 620 |
</div>
|
| 621 |
|
| 622 |
+
${errorMsg ? `<div class="text-red-400 text-center font-bold mt-2 border-2 border-red-400 border-dashed animate-pulse bg-slate-900/50 p-2 rounded">${errorMsg}</div>` : ''}
|
| 623 |
+
`;
|
| 624 |
+
break;
|
| 625 |
|
| 626 |
+
case STATE.PHASE2_COORDINATE:
|
| 627 |
+
formulaBanner.classList.remove('hidden');
|
| 628 |
+
const xValC = checkPoints[qStep];
|
| 629 |
+
const yValC = 2 * xValC + 3;
|
| 630 |
+
html = `
|
| 631 |
+
<h3 class="text-3xl font-bold text-white mb-2 text-center">能力檢測 (${qStep + 1}/${checkPoints.length})</h3>
|
| 632 |
+
|
| 633 |
+
<div class="grid grid-cols-2 gap-6 mb-2 mt-4">
|
| 634 |
+
<!-- Left: Math Result (Confirmed) -->
|
| 635 |
+
<div class="flex flex-col gap-4 opacity-60 grayscale-[0.5] pointer-events-none">
|
| 636 |
+
<div class="text-cyan-400 font-bold text-xl border-b border-cyan-500/30 pb-2">1. 算出電力 (y)</div>
|
| 637 |
+
<p class="text-slate-300 text-lg">
|
| 638 |
+
若投入 <span class="text-amber-400 font-bold text-2xl">${xValC}</span> <span class="text-slate-400 text-base">(<span class="text-amber-400">x=${xValC}</span>)</span> 顆晶石...
|
| 639 |
+
</p>
|
| 640 |
+
<div class="flex items-center justify-center gap-2 flex-grow">
|
| 641 |
+
<span class="font-tech text-3xl text-cyan-400">y = </span>
|
| 642 |
+
<div class="relative w-32">
|
| 643 |
+
<input type="text" class="tech-input w-full p-2 text-3xl rounded-lg bg-slate-800 text-cyan-400 border-cyan-500" value="${yValC}" disabled>
|
| 644 |
+
</div>
|
| 645 |
+
</div>
|
| 646 |
+
<button class="w-full h-16 bg-slate-700 text-slate-400 rounded-xl font-bold text-xl border border-slate-600 flex items-center justify-center">
|
| 647 |
+
已確認
|
| 648 |
+
</button>
|
| 649 |
+
</div>
|
| 650 |
+
|
| 651 |
+
<!-- Right: Coordinate (Two States) -->
|
| 652 |
+
<div class="flex flex-col gap-4">
|
| 653 |
+
<div class="text-amber-400 font-bold text-xl border-b border-amber-500/30 pb-2">2. 坐標描點</div>
|
| 654 |
+
|
| 655 |
+
<!-- Step 1: Pre-substitution -->
|
| 656 |
+
<div id="coord-step-1" class="flex flex-col gap-4 h-full">
|
| 657 |
+
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500/50 flex flex-col items-center justify-center flex-grow">
|
| 658 |
+
<div class="text-sm text-cyan-300 font-bold mb-2 tracking-widest">對應座標</div>
|
| 659 |
+
<div class="font-tech text-4xl font-bold z-10 mb-2">
|
| 660 |
+
(<span class="text-amber-400">${xValC}</span>, <span class="text-cyan-400">y</span>)
|
| 661 |
+
</div>
|
| 662 |
+
</div>
|
| 663 |
+
<button onclick="substituteY()" class="w-full h-16 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl font-bold text-xl shadow-lg transition-colors flex items-center justify-center">
|
| 664 |
+
將 y 代入
|
| 665 |
+
</button>
|
| 666 |
+
</div>
|
| 667 |
+
|
| 668 |
+
<!-- Step 2: Post-substitution (Hidden initially) -->
|
| 669 |
+
<div id="coord-step-2" class="hidden flex flex-col gap-4 h-full">
|
| 670 |
+
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500 shadow-[0_0_20px_rgba(34,211,238,0.2)] flex flex-col items-center justify-center relative overflow-hidden transform transition-all flex-grow animate-pulse-once">
|
| 671 |
+
<div class="text-sm text-cyan-300 font-bold mb-2 tracking-widest">對應座標</div>
|
| 672 |
+
<div class="font-tech text-4xl font-bold z-10 mb-2">
|
| 673 |
+
(<span class="text-amber-400">${xValC}</span>, <span class="text-cyan-400">${yValC}</span>)
|
| 674 |
+
</div>
|
| 675 |
+
<div class="absolute inset-0 bg-cyan-500/10 animate-pulse"></div>
|
| 676 |
+
</div>
|
| 677 |
+
<button onclick="plotCoordinate()" class="w-full h-16 bg-amber-500 hover:bg-amber-400 text-white rounded-xl font-bold text-xl shadow-lg animate-pulse whitespace-nowrap flex items-center justify-center">
|
| 678 |
+
描繪點 (PLOT)
|
| 679 |
+
</button>
|
| 680 |
+
</div>
|
| 681 |
+
</div>
|
| 682 |
+
</div>
|
| 683 |
`;
|
| 684 |
break;
|
| 685 |
|
| 686 |
case STATE.PHASE2_PATTERN:
|
| 687 |
+
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 688 |
html = `
|
| 689 |
+
<h3 class="text-3xl font-bold text-white mb-4">規律分析</h3>
|
| 690 |
<p class="text-slate-300 mb-6">觀察圖表上的這些點,它們呈現什麼樣的排列規律?</p>
|
| 691 |
<div class="grid grid-cols-3 gap-4">
|
| 692 |
<button onclick="checkPattern('A')" class="p-4 bg-slate-800 hover:bg-slate-700 rounded-xl border border-slate-600 font-bold text-slate-300 transition-all hover:border-cyan-400 hover:text-white">
|
|
|
|
| 703 |
break;
|
| 704 |
|
| 705 |
case STATE.PHASE2_PREDICTION_INTRO:
|
| 706 |
+
formulaBanner.classList.remove('hidden'); // Keep banner visible
|
| 707 |
html = `
|
| 708 |
<h3 class="text-xl font-bold text-green-400 mb-4">分析完成</h3>
|
| 709 |
<div class="bg-slate-800/80 p-6 rounded-xl border border-green-500/30 mb-6">
|
| 710 |
<p class="text-slate-200 text-lg leading-relaxed font-bold">
|
| 711 |
沒錯,所有點都連成一條直線...<br>
|
| 712 |
+
<span class="text-cyan-400 block mt-2 text-3xl">也就是我們能預測未來!</span>
|
| 713 |
</p>
|
| 714 |
</div>
|
| 715 |
<button onclick="enterPhase(STATE.PHASE3_CRISIS)" class="w-full py-4 bg-red-600 hover:bg-red-500 text-white rounded-xl font-bold text-xl shadow-lg animate-pulse">
|
|
|
|
| 719 |
break;
|
| 720 |
|
| 721 |
case STATE.PHASE3_CRISIS:
|
| 722 |
+
formulaBanner.classList.remove('hidden');
|
| 723 |
html = `
|
| 724 |
<div class="border-l-4 border-red-500 pl-6">
|
| 725 |
+
<h3 class="text-3xl font-bold text-red-500 mb-2 blink-red">狀況:工業用電暴增!</h3>
|
| 726 |
+
<p class="text-slate-200 mb-6 text-xl">
|
| 727 |
+
因為工業區需求激增,系統現在需要供給 <span class="text-cyan-400 font-bold text-4xl">203</span> 點電力 <span class="text-slate-400 text-base">(<span class="text-cyan-400 font-bold">y=203</span>)</span>。
|
| 728 |
<br>根據 y=2x+3,請問要投入多少晶石 <span class="text-amber-400 font-bold">(x)</span> 才足夠?
|
| 729 |
</p>
|
| 730 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl flex-wrap">
|
| 731 |
+
<span class="font-tech text-amber-400 text-4xl whitespace-nowrap">x = </span>
|
| 732 |
+
<input id="ans-input" type="number" class="tech-input flex-1 min-w-[100px] p-2 rounded text-4xl text-amber-400" placeholder="?" autofocus>
|
| 733 |
<button onclick="checkFinalAnswer()" class="px-6 py-3 bg-red-600 hover:bg-red-500 text-white rounded font-bold shadow-[0_0_15px_rgba(239,68,68,0.5)] whitespace-nowrap flex-shrink-0">
|
| 734 |
投入晶石
|
| 735 |
</button>
|
| 736 |
</div>
|
| 737 |
|
| 738 |
+
${errorMsg ? `<div class="text-amber-300 text-center font-bold mt-2 text-xl border-2 border-amber-300 border-dashed animate-pulse bg-slate-900/50 p-2 rounded">${errorMsg}</div>` : ''}
|
| 739 |
|
| 740 |
</div>
|
| 741 |
`;
|
|
|
|
| 744 |
case STATE.SUCCESS:
|
| 745 |
html = `
|
| 746 |
<div class="text-center">
|
| 747 |
+
<h2 class="text-4xl font-bold text-green-400 mb-2 font-tech">SYSTEM STABILIZED</h2>
|
| 748 |
+
<p class="text-slate-300 mb-6 text-xl">預測成功!核心運作恢復正常。</p>
|
| 749 |
|
| 750 |
<!-- Teaching Content -->
|
| 751 |
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500/30 mb-6 text-left">
|
| 752 |
+
<h3 class="text-3xl font-bold text-white mb-2 text-center">任務小結:什麼是「函數」?</h3>
|
| 753 |
+
<p class="text-slate-200 text-2xl leading-relaxed mb-3">
|
| 754 |
+
剛剛我們使用的「能量轉換法則」,在數學上就叫做<span class="text-amber-400 font-bold text-3xl mx-1">「函數」(Function)</span>。
|
| 755 |
</p>
|
| 756 |
+
<p class="text-slate-300 text-2xl leading-relaxed">
|
| 757 |
函數就像一座<span class="text-cyan-400 font-bold">工廠</span>:<br>
|
| 758 |
+
它會把原料 <span class="font-bold text-amber-400">x (晶石)</span>,藉由固定的規則,<br>轉換成產品 <span class="font-bold text-cyan-400">y (電力)</span>。
|
| 759 |
</p>
|
| 760 |
</div>
|
| 761 |
|
|
|
|
| 778 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 779 |
html = `
|
| 780 |
<div class="border-l-4 border-amber-500 pl-6">
|
| 781 |
+
<h3 class="text-3xl font-bold text-amber-500 mb-2 blink-red">警告:公式異變!</h3>
|
| 782 |
+
<p class="text-slate-200 mb-6 text-xl">
|
| 783 |
檢測到晶石純度下降,能量轉換公式已改變!<br>
|
| 784 |
系統無法自動運作,你必須重新推導公式。
|
| 785 |
</p>
|
| 786 |
<div class="bg-slate-800/50 p-4 rounded-xl mb-4 border border-slate-600">
|
| 787 |
+
<span class="font-tech text-4xl block text-center tracking-widest">
|
| 788 |
y = <span class="text-amber-400 animate-pulse">?</span>x + <span class="text-cyan-400 animate-pulse">?</span>
|
| 789 |
</span>
|
| 790 |
</div>
|
|
|
|
| 798 |
case STATE.PHASE4_TEST_B:
|
| 799 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 800 |
html = `
|
| 801 |
+
<h3 class="text-3xl font-bold text-white mb-2">步驟 1:數據校準 B</h3>
|
| 802 |
<p class="text-slate-300 mb-4">
|
| 803 |
你需要測試不同的晶石投入量,來推導出新的公式。
|
| 804 |
+
<br><span class="text-base text-slate-400">提示:什麼時候 <span class="text-red-400">a</span> 會消失不見? (試試投入 0 顆)</span>
|
| 805 |
</p>
|
| 806 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl justify-center flex-wrap">
|
| 807 |
+
<span class="font-tech text-amber-400 text-3xl whitespace-nowrap">投入 x = </span>
|
| 808 |
+
<input id="test-input" type="number" class="tech-input w-24 p-2 rounded text-3xl text-amber-400 text-center" placeholder="0" value="0">
|
| 809 |
<button onclick="runPhase4Test()" class="px-6 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded font-bold shadow-lg whitespace-nowrap">
|
| 810 |
啟動運作
|
| 811 |
</button>
|
| 812 |
+
${errorMsg ? `<div class="text-amber-400 text-center font-bold mt-2 text-xl border-2 border-amber-400 border-dashed animate-pulse p-2 rounded">${errorMsg}</div>` : ''}
|
| 813 |
+
|
| 814 |
+
</div>
|
| 815 |
+
`;
|
| 816 |
break;
|
| 817 |
|
| 818 |
case STATE.PHASE4_FIND_B:
|
| 819 |
+
formulaBanner.classList.remove('hidden');
|
| 820 |
html = `
|
| 821 |
+
<h3 class="text-3xl font-bold text-white mb-2">步驟 1:尋找初始值 (b)</h3>
|
| 822 |
+
<p class="text-slate-300 mb-4 text-2xl">
|
| 823 |
當投入 <span class="text-amber-400 font-bold">0</span> 顆時,
|
| 824 |
電力顯示為 <span class="text-cyan-400 font-bold">5</span>。
|
| 825 |
+
<br><span class="font-tech text-3xl block mt-2"><span class="text-cyan-400">5</span> = a × <span class="text-amber-400">0</span> + b</span>
|
| 826 |
+
<span class="font-tech text-3xl block"><span class="text-cyan-400">5</span> = b</span>
|
| 827 |
</p>
|
| 828 |
+
<div class="flex items-center justify-center gap-2 text-3xl font-tech mb-6 bg-slate-800 p-4 rounded-lg">
|
| 829 |
<span>b = </span>
|
| 830 |
<input id="ans-input" type="number" class="w-24 bg-slate-700 text-cyan-400 text-center rounded border border-slate-500 focus:border-cyan-400 outline-none p-1" placeholder="?" autofocus>
|
| 831 |
</div>
|
| 832 |
|
| 833 |
+
${errorMsg ? `<div class="text-red-400 text-center font-bold mb-4 border-2 border-red-400 border-dashed animate-pulse bg-slate-900/50 p-2 rounded">${errorMsg}</div>` : ''}
|
| 834 |
|
| 835 |
<button onclick="checkPhase4B()" class="w-full py-3 bg-cyan-600 hover:bg-cyan-500 text-white rounded-xl font-bold">
|
| 836 |
確認校準 Check
|
|
|
|
| 839 |
break;
|
| 840 |
|
| 841 |
case STATE.PHASE4_TEST_A:
|
| 842 |
+
formulaBanner.classList.remove('hidden');
|
| 843 |
html = `
|
| 844 |
+
<h3 class="text-3xl font-bold text-white mb-2">步驟 2:數據校準 A</h3>
|
| 845 |
<p class="text-slate-300 mb-4">
|
| 846 |
現在已知 b=5。接下來需要找出變化率 a。
|
| 847 |
+
<br><span class="text-base text-slate-400">提示:試試看投入 1 顆晶石?</span>
|
| 848 |
</p>
|
| 849 |
<div class="flex items-center gap-2 mt-4 bg-slate-800/50 p-4 rounded-xl justify-center flex-wrap">
|
| 850 |
+
<span class="font-tech text-amber-400 text-3xl whitespace-nowrap">投入 x = </span>
|
| 851 |
+
<input id="test-input-a" type="number" class="tech-input w-24 p-2 rounded text-3xl text-amber-400 text-center" placeholder="1" value="1">
|
| 852 |
<button onclick="runPhase4TestA()" class="px-6 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded font-bold shadow-lg whitespace-nowrap">
|
| 853 |
啟動運作
|
| 854 |
</button>
|
| 855 |
</div>
|
| 856 |
+
${errorMsg ? `<div class="text-amber-400 text-center font-bold mt-2 text-xl border-2 border-amber-400 border-dashed animate-pulse p-2 rounded">${errorMsg}</div>` : ''}
|
| 857 |
`;
|
| 858 |
break;
|
| 859 |
|
| 860 |
case STATE.PHASE4_FIND_A:
|
| 861 |
formulaBanner.classList.remove('hidden');
|
| 862 |
html = `
|
| 863 |
+
<h3 class="text-3xl font-bold text-white mb-2">步驟 2:解出 a (Solve for a)</h3>
|
| 864 |
<p class="text-slate-300 mb-4">
|
| 865 |
+
測試投入 <span class="text-amber-400 font-bold">${lastTestAX}</span> 顆晶石,
|
| 866 |
+
電力變為 <span class="text-cyan-400 font-bold">${lastTestAY}</span>。
|
| 867 |
</p>
|
| 868 |
<div class="bg-slate-800/50 p-4 rounded-xl mb-4 text-center border border-slate-700">
|
| 869 |
+
<span class="text-3xl font-tech block mt-2 text-white"><span class="text-cyan-400">${lastTestAY}</span> = a <span class="text-amber-400">× ${lastTestAX}</span> + 5</span>
|
|
|
|
| 870 |
</div>
|
| 871 |
<p class="text-slate-300 mb-4 text-center">請問 <span class="text-amber-400 font-bold">a</span> 是多少?</p>
|
| 872 |
+
<div class="flex flex-wrap gap-2 mb-2 items-center justify-center text-3xl font-tech text-amber-400">
|
| 873 |
<span>a =</span>
|
| 874 |
+
<input id="ans-input" type="number" class="tech-input w-24 p-2 rounded text-3xl text-center text-amber-400" placeholder="?" autofocus>
|
| 875 |
<button onclick="checkPhase4A()" class="w-full md:w-auto px-8 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded-xl font-bold shadow-lg transition-transform hover:scale-105 whitespace-nowrap text-lg font-sans">確認</button>
|
| 876 |
</div>
|
| 877 |
|
| 878 |
+
${errorMsg ? `<div class="text-red-400 text-center font-bold mb-2 border-2 border-red-400 border-dashed animate-pulse bg-slate-900/50 p-2 rounded">${errorMsg}</div>` : ''}
|
| 879 |
`;
|
| 880 |
break;
|
| 881 |
|
|
|
|
| 890 |
case STATE.PHASE4_VERIFY:
|
| 891 |
formulaBanner.classList.remove('hidden');
|
| 892 |
html = `
|
| 893 |
+
<h3 class="text-3xl font-bold text-white mb-2">步驟 3:最終驗算</h3>
|
| 894 |
<p class="text-slate-300 mb-4">
|
| 895 |
假設公式為 <span class="font-bold text-white">y = 3x + 5</span>。
|
| 896 |
<br>若投入 <span class="text-amber-400 font-bold">10</span> 顆晶石,電力應為多少?
|
| 897 |
</p>
|
| 898 |
+
<div class="flex flex-wrap gap-2 mt-6 items-center justify-center text-3xl font-tech text-white">
|
| 899 |
<span>y =</span>
|
| 900 |
+
<input id="ans-input" type="number" class="tech-input w-24 p-2 rounded text-3xl text-center text-white" placeholder="?" autofocus>
|
| 901 |
<button onclick="checkPhase4Verify()" class="w-full md:w-auto px-8 py-2 bg-green-600 hover:bg-green-500 text-white rounded-xl font-bold shadow-lg transition-transform hover:scale-105 whitespace-nowrap text-lg font-sans">驗證</button>
|
| 902 |
</div>
|
| 903 |
|
| 904 |
+
${errorMsg ? `<div class="text-red-400 text-center font-bold mt-4 border-2 border-red-400 border-dashed animate-pulse bg-slate-900/50 p-2 rounded">${errorMsg}</div>` : ''}
|
| 905 |
`;
|
| 906 |
break;
|
| 907 |
|
|
|
|
| 909 |
formulaBanner.classList.remove('hidden'); // Ensure banner is visible
|
| 910 |
html = `
|
| 911 |
<div class="text-center w-full">
|
| 912 |
+
<h3 class="text-3xl font-bold text-amber-400 mb-6 drop-shadow-lg">輸入核心密碼 (Core Override)</h3>
|
| 913 |
+
<div class="flex items-center justify-center gap-2 text-4xl md:text-4xl font-tech font-bold mb-8 bg-slate-900/80 p-6 rounded-3xl border-2 border-slate-700 w-full max-w-lg mx-auto">
|
| 914 |
<span class="text-cyan-400">y</span>
|
| 915 |
<span class="text-slate-500">=</span>
|
| 916 |
<input id="final-a" type="number" class="w-20 bg-slate-800 text-amber-400 text-center rounded border border-slate-600 focus:border-amber-400 outline-none p-2" placeholder="?">
|
|
|
|
| 921 |
<button onclick="unlockCore()" class="w-full py-4 bg-gradient-to-r from-amber-600 to-red-600 text-white rounded-xl font-bold text-xl shadow-lg hover:shadow-amber-500/50 transition-all border border-amber-500/30">
|
| 922 |
解鎖大門 (UNLOCK)
|
| 923 |
</button>
|
| 924 |
+
${errorMsg ? `<div class="text-red-400 text-center font-bold mt-4 border-2 border-red-400 border-dashed animate-pulse bg-slate-900/50 p-2 rounded text-xl">${errorMsg}</div>` : ''}
|
| 925 |
</div>
|
| 926 |
`;
|
| 927 |
break;
|
|
|
|
| 929 |
case STATE.PHASE6_INTRO:
|
| 930 |
formulaBanner.classList.add('hidden');
|
| 931 |
html = `
|
| 932 |
+
<h2 class="text-3xl font-bold text-cyan-400 mb-4">核心同步程序 (Core Synchronization)</h2>
|
| 933 |
+
<p class="text-slate-200 text-xl leading-relaxed mb-6">
|
| 934 |
核心已解鎖,但能量極不穩定!<br>
|
| 935 |
我們需要透過<span class="text-amber-400 font-bold">手動脈衝</span>來穩定核心頻率。
|
| 936 |
</p>
|
| 937 |
<div class="bg-slate-800/80 p-4 rounded-xl border border-cyan-500/30 mb-6 text-center">
|
| 938 |
<p class="text-slate-300 font-bold mb-2">操作說明</p>
|
| 939 |
+
<p class="text-base text-slate-400">
|
| 940 |
當光圈縮小至<span class="text-cyan-400">中心圓環</span>時,<br>
|
| 941 |
點擊滑鼠、螢幕或按下空白鍵。
|
| 942 |
</p>
|
|
|
|
| 955 |
break;
|
| 956 |
|
| 957 |
case STATE.PHASE7_SUMMARY:
|
| 958 |
+
// Break out of the container layout for Full Screen Center
|
| 959 |
+
// We modify the box styling directly to be fixed center
|
| 960 |
+
box.className = "fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[95%] md:w-[70%] glass-panel rounded-3xl p-8 transition-all duration-500 min-h-[400px] max-h-[90vh] overflow-y-auto flex flex-col justify-start shadow-[0_0_80px_rgba(0,0,0,0.8)] bg-slate-900/95 frame-border z-50 pointer-events-auto";
|
| 961 |
|
| 962 |
const currentScore = rhythmState.score;
|
| 963 |
const highScore = localStorage.getItem('math_city_score_function') || 0;
|
| 964 |
|
| 965 |
html = `
|
| 966 |
<div class="text-center w-full h-full flex flex-col items-center">
|
| 967 |
+
<h2 class="text-5xl md:text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-green-400 mb-6 drop-shadow-[0_0_15px_rgba(34,211,238,0.6)] tracking-widest">
|
| 968 |
任務完成 SYSTEM RESTORED
|
| 969 |
</h2>
|
| 970 |
+
<div class="text-amber-400 font-tech text-4xl mb-10 tracking-[0.5em]">MISSION ACCOMPLISHED</div>
|
| 971 |
|
| 972 |
<!-- Score Board -->
|
| 973 |
+
<div class="flex gap-16 mb-10 bg-slate-800/80 px-16 py-8 rounded-3xl border-2 border-slate-600 transform scale-110">
|
| 974 |
<div class="text-center">
|
| 975 |
+
<div class="text-xl text-slate-400 uppercase tracking-widest mb-2">本次得分</div>
|
| 976 |
<div class="text-4xl font-black text-cyan-400 font-tech shimmer">${currentScore}</div>
|
| 977 |
</div>
|
| 978 |
+
<div class="w-px bg-slate-500"></div>
|
| 979 |
<div class="text-center">
|
| 980 |
+
<div class="text-xl text-slate-400 uppercase tracking-widest mb-2">歷史最高</div>
|
| 981 |
<div class="text-4xl font-black text-amber-400 font-tech">${highScore}</div>
|
| 982 |
</div>
|
| 983 |
</div>
|
| 984 |
|
| 985 |
+
|
| 986 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 w-full text-left mb-12 max-w-6xl">
|
| 987 |
<!-- Insight 1 -->
|
| 988 |
+
<div class="bg-slate-800/50 p-10 rounded-3xl border border-cyan-500/20 hover:border-cyan-500/50 transition-colors">
|
| 989 |
+
<h3 class="flex items-center gap-4 text-4xl font-bold text-cyan-400 mb-6">
|
| 990 |
+
<span class="text-5xl">🔮</span> 預測未來 (Prediction)
|
| 991 |
</h3>
|
| 992 |
+
<p class="text-slate-300 text-3xl leading-relaxed mb-4">
|
| 993 |
+
「當我們知道規則 <span class="bg-slate-900 px-3 rounded text-cyan-300 font-tech">y=ax+b</span> 時,就能精準預測未來的結果。」
|
| 994 |
</p>
|
| 995 |
+
<div class="text-xl text-slate-500 border-t border-slate-700 pt-4">
|
| 996 |
回顧:就像你在階段三,算出需要投入多少晶石才能救急。
|
| 997 |
</div>
|
| 998 |
</div>
|
| 999 |
|
| 1000 |
<!-- Insight 2 -->
|
| 1001 |
+
<div class="bg-slate-800/50 p-10 rounded-3xl border border-amber-500/20 hover:border-amber-500/50 transition-colors">
|
| 1002 |
+
<h3 class="flex items-center gap-4 text-4xl font-bold text-amber-400 mb-6">
|
| 1003 |
+
<span class="text-5xl">📐</span> 數學建模 (Modeling)
|
| 1004 |
</h3>
|
| 1005 |
+
<p class="text-slate-300 text-3xl leading-relaxed mb-4">
|
| 1006 |
「當規則改變或未知時,我們也能透過觀察數據的變化,反推回公式本身。」
|
| 1007 |
</p>
|
| 1008 |
+
<div class="text-xl text-slate-500 border-t border-slate-700 pt-4">
|
| 1009 |
回顧:就像你在階段四,透過測試 x=0 和 x=1,重新找回消失的 a 與 b。
|
| 1010 |
</div>
|
| 1011 |
</div>
|
| 1012 |
</div>
|
| 1013 |
|
| 1014 |
+
<p class="text-slate-400 italic mb-12 text-3xl max-w-4xl whitespace-nowrap">
|
| 1015 |
「這就是科學家與工程師的超能力:觀察數據 <span class="text-white">→</span> 找出規則 <span class="text-white">→</span> 預測未來。」
|
| 1016 |
</p>
|
| 1017 |
|
| 1018 |
+
<a href="index.html" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-6 px-20 rounded-full text-4xl shadow-[0_0_40px_rgba(79,70,229,0.6)] transition-all transform hover:scale-105 hover:rotate-1">
|
| 1019 |
回到 Math City
|
| 1020 |
</a>
|
| 1021 |
</div>
|
|
|
|
| 1024 |
|
| 1025 |
case STATE.PHASE4_VERIFY_TEST:
|
| 1026 |
html = `
|
| 1027 |
+
<h3 class="text-3xl font-bold text-amber-400 mb-2 font-tech tracking-widest border-b border-amber-500/30 pb-2">
|
| 1028 |
<span class="mr-2">⚡</span> 最終驗證 FINAL VERIFICATION
|
| 1029 |
</h3>
|
| 1030 |
<p class="text-slate-300 leading-relaxed mb-6">
|
|
|
|
| 1033 |
</p>
|
| 1034 |
|
| 1035 |
<div class="flex items-center gap-4 mb-2">
|
| 1036 |
+
<span class="text-3xl font-tech text-amber-500">x =</span>
|
| 1037 |
<div class="relative flex-1">
|
| 1038 |
+
<input type="number" id="test-input-verify" class="tech-input w-full p-4 rounded-xl text-3xl font-bold" placeholder="輸入 10" autofocus onkeydown="if(event.key==='Enter') runPhase4VerifyManual()">
|
| 1039 |
<div class="absolute right-4 top-1/2 -translate-y-1/2 text-slate-500 text-sm">晶石</div>
|
| 1040 |
</div>
|
| 1041 |
</div>
|
|
|
|
| 1076 |
|
| 1077 |
if (val === correctY) {
|
| 1078 |
playSound('success');
|
| 1079 |
+
// Old: addPoint(targetX, correctY);
|
| 1080 |
+
// New: Go to Coordinate Confirmation Step
|
| 1081 |
+
enterPhase(STATE.PHASE2_COORDINATE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
} else {
|
| 1083 |
playSound('alarm');
|
| 1084 |
input.classList.add('animate-shake');
|
|
|
|
| 1088 |
}
|
| 1089 |
}
|
| 1090 |
|
| 1091 |
+
function substituteY() {
|
| 1092 |
+
playSound('swish'); // Optional sound
|
| 1093 |
+
document.getElementById('coord-step-1').classList.add('hidden');
|
| 1094 |
+
const step2 = document.getElementById('coord-step-2');
|
| 1095 |
+
step2.classList.remove('hidden');
|
| 1096 |
+
// Add a small pop animation or effect if desired
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
function plotCoordinate() {
|
| 1100 |
+
const targetX = checkPoints[qStep];
|
| 1101 |
+
const correctY = 2 * targetX + 3;
|
| 1102 |
+
|
| 1103 |
+
addPoint(targetX, correctY);
|
| 1104 |
+
|
| 1105 |
+
// Trigger Animation
|
| 1106 |
+
guideArrowState.active = true;
|
| 1107 |
+
guideArrowState.startTime = Date.now();
|
| 1108 |
+
guideArrowState.targetX = targetX;
|
| 1109 |
+
guideArrowState.targetY = correctY;
|
| 1110 |
+
|
| 1111 |
+
playSound('tick');
|
| 1112 |
+
|
| 1113 |
+
qStep++;
|
| 1114 |
+
if (qStep < checkPoints.length) {
|
| 1115 |
+
// Next question
|
| 1116 |
+
enterPhase(STATE.PHASE2_Q);
|
| 1117 |
+
} else {
|
| 1118 |
+
// All questions done
|
| 1119 |
+
setTimeout(() => enterPhase(STATE.PHASE2_PATTERN), 500);
|
| 1120 |
+
}
|
| 1121 |
+
}
|
| 1122 |
+
|
| 1123 |
function checkFinalAnswer() {
|
| 1124 |
const input = document.getElementById('ans-input');
|
| 1125 |
const val = parseInt(input.value);
|
|
|
|
| 1169 |
|
| 1170 |
function runPhase4Test() {
|
| 1171 |
const input = document.getElementById('test-input');
|
| 1172 |
+
const x = parseFloat(input.value);
|
| 1173 |
|
| 1174 |
if (isNaN(x)) return;
|
| 1175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1176 |
if (x === 0) {
|
| 1177 |
+
const y = 3 * x + 5;
|
| 1178 |
+
addPoint(x, y);
|
| 1179 |
+
playSound('tick');
|
| 1180 |
setTimeout(() => {
|
| 1181 |
enterPhase(STATE.PHASE4_FIND_B);
|
| 1182 |
}, 1000);
|
| 1183 |
+
} else {
|
| 1184 |
+
playSound('alarm');
|
| 1185 |
+
const y = 3 * x + 5;
|
| 1186 |
+
// Don't plot point to avoid confusing them? Or plot it but don't proceed?
|
| 1187 |
+
// User said "don't plot point... show hint".
|
| 1188 |
+
// Hint display logic
|
| 1189 |
+
const hintY = 14;
|
| 1190 |
+
// Wait, if x=3, y=14. Formula y=ax+b becomes 14 = 3a + b.
|
| 1191 |
+
updateUI(`錯誤!若投入 ${x} 顆,電力為 ${y}。<br>公式變為 <span class="text-amber-400 font-tech">${y} = ${x}a + b</span>。<br>a 沒有消失,無法算出 b。只有什麼數字才能讓 a 消失?`);
|
| 1192 |
}
|
| 1193 |
}
|
| 1194 |
|
|
|
|
| 1210 |
}
|
| 1211 |
}
|
| 1212 |
|
| 1213 |
+
// Global var to store X logic for Phase 4 Step 2
|
| 1214 |
+
let lastTestAX = 1;
|
| 1215 |
+
let lastTestAY = 8;
|
| 1216 |
+
|
| 1217 |
function runPhase4TestA() {
|
| 1218 |
const input = document.getElementById('test-input-a');
|
| 1219 |
const x = parseInt(input.value);
|
| 1220 |
|
| 1221 |
+
if (isNaN(x) || x < 1 || x > 9) {
|
| 1222 |
+
playSound('alarm');
|
| 1223 |
+
updateUI("為了方便觀察,請輸入 1 到 9 之間的整數。");
|
| 1224 |
+
input.value = '';
|
| 1225 |
+
return;
|
| 1226 |
+
}
|
| 1227 |
|
| 1228 |
const y = 3 * x + 5;
|
| 1229 |
addPoint(x, y);
|
| 1230 |
playSound('tick');
|
| 1231 |
|
| 1232 |
+
// Store for next step text
|
| 1233 |
+
lastTestAX = x;
|
| 1234 |
+
lastTestAY = y;
|
| 1235 |
+
|
| 1236 |
+
setTimeout(() => {
|
| 1237 |
+
enterPhase(STATE.PHASE4_FIND_A);
|
| 1238 |
+
}, 1000);
|
| 1239 |
}
|
| 1240 |
|
| 1241 |
function checkPhase4A() {
|
|
|
|
| 1249 |
enterPhase(STATE.PHASE4_VERIFY);
|
| 1250 |
} else {
|
| 1251 |
playSound('alarm');
|
| 1252 |
+
updateUI(`錯誤,${lastTestAY} = ${lastTestAX}a + 5 則 a = ?`);
|
| 1253 |
}
|
| 1254 |
}
|
| 1255 |
|
|
|
|
| 1259 |
playSound('success');
|
| 1260 |
// Auto verification visual
|
| 1261 |
addPoint(10, 35);
|
| 1262 |
+
updateUI("預測正確!投入 10 顆晶石,電力確實為 35!<br><span class='text-amber-400 text-base'>(確認點連成一線...)</span>");
|
| 1263 |
+
|
| 1264 |
+
// Disable button
|
| 1265 |
+
const btn = document.querySelector('#dialogue-box button');
|
| 1266 |
+
if (btn) {
|
| 1267 |
+
btn.disabled = true;
|
| 1268 |
+
btn.classList.add('opacity-50', 'cursor-not-allowed', 'pointer-events-none');
|
| 1269 |
+
btn.innerHTML = '驗證成功 Verified';
|
| 1270 |
+
}
|
| 1271 |
+
|
| 1272 |
+
// Add guide text to look at the graph
|
| 1273 |
+
const inputContainer = document.querySelector('#test-input-verify')?.parentElement?.parentElement;
|
| 1274 |
+
if (inputContainer) {
|
| 1275 |
+
const guide = document.createElement('div');
|
| 1276 |
+
guide.className = "text-center text-green-400 font-bold mt-2 text-xl animate-pulse";
|
| 1277 |
+
guide.innerHTML = "請觀察圖表上的點 (10,35)...";
|
| 1278 |
+
inputContainer.insertAdjacentElement('afterend', guide);
|
| 1279 |
+
}
|
| 1280 |
|
| 1281 |
+
// Delay for user to see the result matches the line before unlocking
|
| 1282 |
+
// Extended delay to 3.5s to ensure they see the point on the line
|
| 1283 |
+
setTimeout(() => enterPhase(STATE.PHASE5_UNLOCK), 3500);
|
| 1284 |
} else {
|
| 1285 |
playSound('alarm');
|
| 1286 |
// Use on-screen hint instead of alert
|
|
|
|
| 1298 |
enterPhase(STATE.PHASE6_INTRO);
|
| 1299 |
} else {
|
| 1300 |
playSound('alarm');
|
| 1301 |
+
const box = document.querySelector('.bg-slate-900\\/80'); // Target container
|
| 1302 |
+
if (box) box.classList.add('animate-shake');
|
| 1303 |
+
setTimeout(() => box && box.classList.remove('animate-shake'), 500);
|
| 1304 |
+
|
| 1305 |
+
// Show hint on screen (simulated by updateUI call inside loop, but here distinct)
|
| 1306 |
+
// Just let user retry, maybe add text
|
| 1307 |
+
// Since updateUI overwrites everything, we just re-render PHASE5_UNLOCK with Error?
|
| 1308 |
+
// But unlockCore is called from button. We can append error.
|
| 1309 |
+
const inputs = document.querySelectorAll('#final-a, #final-b');
|
| 1310 |
+
inputs.forEach(i => i.value = '');
|
| 1311 |
+
updateUI("密碼錯誤!提示:y = 3x + 5 (a=3, b=5)");
|
| 1312 |
}
|
| 1313 |
}
|
| 1314 |
|
|
|
|
| 1487 |
function handleMiss() {
|
| 1488 |
rhythmState.energy = Math.max(0, rhythmState.energy - 2);
|
| 1489 |
rhythmState.combo = 0;
|
| 1490 |
+
// Penalize score to prevent farming (losing energy to re-gain points)
|
| 1491 |
+
// Deduction = Potential gain of a Perfect Hit (500)
|
| 1492 |
+
rhythmState.score = Math.max(0, rhythmState.score - 500);
|
| 1493 |
spawnFloatingText("MISS", '#ef4444');
|
| 1494 |
playSound('alarm');
|
| 1495 |
}
|
|
|
|
| 1674 |
// Desktop: Graph takes up right 55% of screen. Mobile: Centered.
|
| 1675 |
const isDesktop = width > 768;
|
| 1676 |
|
| 1677 |
+
const gw = isDesktop ? width * 0.53 : Math.min(600, width - 40);
|
| 1678 |
const gh = isDesktop ? height * 0.6 : Math.min(400, height * 0.5);
|
| 1679 |
|
| 1680 |
+
// On Desktop, position graph on the right side (Start at 44% width)
|
| 1681 |
// On Mobile, center it
|
| 1682 |
+
const gx = isDesktop ? (width * 0.44) : (width - gw) / 2;
|
| 1683 |
const gy = isDesktop ? height * 0.2 : height * 0.1;
|
| 1684 |
|
| 1685 |
// BG - Darker background for graph area to make it pop against the global image
|
|
|
|
| 1705 |
|
| 1706 |
// Grid
|
| 1707 |
ctx.textAlign = 'center';
|
| 1708 |
+
ctx.font = 'bold 20px "Orbitron", sans-serif';
|
| 1709 |
|
| 1710 |
const jumpX = graphScale.xMax > 20 ? 10 : 1;
|
| 1711 |
for (let i = 0; i <= graphScale.xMax; i += jumpX) {
|
|
|
|
| 1749 |
ctx.stroke();
|
| 1750 |
}
|
| 1751 |
|
| 1752 |
+
// Phase 4: Draw line y = 3x + 5 (After finding a)
|
| 1753 |
+
if (currentState >= STATE.PHASE4_VERIFY) {
|
| 1754 |
+
ctx.beginPath();
|
| 1755 |
+
ctx.strokeStyle = '#fbbf24'; // Amber Line for new formula
|
| 1756 |
+
ctx.lineWidth = 2;
|
| 1757 |
+
ctx.setLineDash([5, 5]); // Dashed line to show it's a prediction/new model
|
| 1758 |
+
const endX = graphScale.xMax;
|
| 1759 |
+
const endY = 3 * endX + 5;
|
| 1760 |
+
ctx.moveTo(ox, oy - 5 * unitY); // Start at (0, 5)
|
| 1761 |
+
ctx.lineTo(ox + endX * unitX, oy - endY * unitY);
|
| 1762 |
+
ctx.stroke();
|
| 1763 |
+
ctx.setLineDash([]); // Reset dash
|
| 1764 |
+
}
|
| 1765 |
+
|
| 1766 |
collectedPoints.forEach(p => {
|
| 1767 |
const px = ox + p.x * unitX;
|
| 1768 |
const py = oy - p.y * unitY;
|
|
|
|
| 1774 |
ctx.stroke();
|
| 1775 |
|
| 1776 |
// Color coded coordinates: (x, y)
|
| 1777 |
+
ctx.font = 'bold 24px Orbitron'; // Point Numbers Increased
|
| 1778 |
+
const textY = py - 24; // Position higher up for larger font
|
| 1779 |
|
| 1780 |
// Measure text to center it
|
| 1781 |
const strX = `${p.x}`;
|
|
|
|
| 1795 |
ctx.fillStyle = '#22d3ee'; ctx.fillText(strY, cursorX + wY / 2, textY); cursorX += wY; // Cyan Y
|
| 1796 |
ctx.fillStyle = '#cbd5e1'; ctx.fillText(")", cursorX + w2 / 2, textY);
|
| 1797 |
});
|
| 1798 |
+
|
| 1799 |
+
// Draw Guide Arrows if active (Coordinate Animation)
|
| 1800 |
+
if (guideArrowState.active && (currentState === STATE.PHASE2_COORDINATE || currentState === STATE.PHASE2_Q)) {
|
| 1801 |
+
drawGuideArrows();
|
| 1802 |
+
}
|
| 1803 |
}
|
| 1804 |
|
| 1805 |
function updateParticles() {
|
|
|
|
| 1843 |
drawRhythmBackground();
|
| 1844 |
drawRhythmGame();
|
| 1845 |
drawCore();
|
| 1846 |
+
} else if (currentState >= STATE.PHASE6_INTRO) {
|
| 1847 |
+
// Intro & Summary Mode (Use Rhythm BG)
|
| 1848 |
drawRhythmBackground();
|
| 1849 |
drawCore();
|
| 1850 |
} else {
|
sequence.html
CHANGED
|
@@ -1010,6 +1010,11 @@
|
|
| 1010 |
if (isStairsVanished) return;
|
| 1011 |
isStairsVanished = true;
|
| 1012 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
platforms = platforms.filter(p => p.type !== 'temp_stair');
|
| 1014 |
|
| 1015 |
const hint = document.getElementById('story-hint');
|
|
|
|
| 1010 |
if (isStairsVanished) return;
|
| 1011 |
isStairsVanished = true;
|
| 1012 |
|
| 1013 |
+
// Fix: Disable jumping (including coyote time) to force fall
|
| 1014 |
+
player.isGrounded = false;
|
| 1015 |
+
player.coyoteTimer = 0;
|
| 1016 |
+
player.jumpCount = MAX_JUMPS;
|
| 1017 |
+
|
| 1018 |
platforms = platforms.filter(p => p.type !== 'temp_stair');
|
| 1019 |
|
| 1020 |
const hint = document.getElementById('story-hint');
|