Lashtw commited on
Commit
e9fe3c1
·
verified ·
1 Parent(s): 45e730e

Upload 42 files

Browse files
Files changed (3) hide show
  1. achievements.js +187 -467
  2. achievements_old.js +599 -0
  3. kingdom_map.js +18 -3
achievements.js CHANGED
@@ -1,262 +1,126 @@
1
- /**
2
- * 數學魔法王國:成就系統
3
- * 負責讀取成就資料、管理成就狀態、顯示成就卡片與通知
4
- */
5
-
6
- // 成就系統主類別
7
  class AchievementSystem {
8
  constructor() {
9
- this.achievements = []; // 從JSON載入的成就資料
10
- this.userAchievements = {}; // 使用者的成就進度
11
- this.toastQueue = []; // 成就通知佇列
12
- this.isProcessingToast = false; // 是否正在處理通知佇列
 
13
 
14
- // DOM元素
15
- this.achievementsGrid = document.getElementById('achievements-grid');
16
- this.achievementsStats = document.getElementById('achievements-stats');
17
- this.tabButtons = document.querySelectorAll('.tab-button');
18
- this.toastContainer = document.getElementById('toast-container') || this.createToastContainer();
19
- this.backButton = document.getElementById('back-button');
20
 
21
- // 初始化
22
- this.init();
23
 
24
- console.log('成就系統構完成');
25
- }
26
-
27
- // 初始化成就系統
28
- async init() {
29
- try {
30
- // 載入成就資料
31
- await this.loadAchievements();
32
-
33
- // 載入使用者成就進度
34
- this.loadUserProgress();
35
-
36
- // 如果在成就頁面,渲染成就卡片
37
- if (this.achievementsGrid) {
38
- // 渲染成就卡片
39
- this.renderAchievements('all');
40
-
41
- // 更新成就統計
42
- this.updateStats();
43
-
44
- // 設定分類標籤事件
45
- this.setupTabEvents();
46
-
47
- // 設定返回按鈕事件
48
- this.setupBackButton();
49
- }
50
-
51
- console.log('成就系統初始化完成');
52
- } catch (error) {
53
- console.error('成就系統初始化失敗:', error);
54
- }
55
- }
56
-
57
- // 創建通知容器
58
- createToastContainer() {
59
- console.log('創建通知容器');
60
- const toastContainer = document.createElement('div');
61
- toastContainer.className = 'toast-container';
62
- toastContainer.id = 'toast-container';
63
- document.body.appendChild(toastContainer);
64
- return toastContainer;
65
  }
66
 
67
  // 載入成就資料
68
  async loadAchievements() {
69
  try {
70
  const response = await fetch('achievements.json');
71
- if (!response.ok) {
72
- throw new Error(`HTTP error! status: ${response.status}`);
73
- }
74
- this.achievements = await response.json();
75
  console.log('成就資料載入成功:', this.achievements.length, '個成就');
76
  } catch (error) {
77
  console.error('載入成就資料失敗:', error);
78
- // 如果無法載入,使用空陣列
79
- this.achievements = [];
80
  }
81
  }
82
 
83
- // 載入使用者成就進度
84
- loadUserProgress() {
85
- try {
86
- // 從localStorage讀取使用者成就進度
87
- const savedProgress = localStorage.getItem('userAchievements');
88
- this.userAchievements = savedProgress ? JSON.parse(savedProgress) : {};
89
-
90
- console.log('使用者成就進度載入成功', this.userAchievements);
91
- } catch (error) {
92
- console.error('載入使用者成就進度失敗:', error);
93
- this.userAchievements = {};
94
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
 
97
- // ��存使成就進度
98
- saveUserProgress() {
99
- try {
100
- localStorage.setItem('userAchievements', JSON.stringify(this.userAchievements));
101
- console.log('使用者成就進度儲存成功');
102
- } catch (error) {
103
- console.error('儲存使用者成就進度失敗:', error);
 
 
104
  }
105
  }
106
 
107
- // 渲染成就卡片
108
- renderAchievements(type = 'all') {
109
- if (!this.achievementsGrid) return;
110
-
111
- // 清空成就網格
112
- this.achievementsGrid.innerHTML = '';
113
-
114
- // 篩選成就
115
- const filteredAchievements = type === 'all'
116
- ? this.achievements
117
- : this.achievements.filter(achievement => achievement.type === type);
118
-
119
- // 如果沒有成就,顯示提示訊息
120
- if (filteredAchievements.length === 0) {
121
- this.achievementsGrid.innerHTML = '<p class="no-achievements">此分類沒有成就</p>';
122
- return;
123
  }
124
-
125
- // 渲染每個成就卡片
126
- filteredAchievements.forEach(achievement => {
127
- const userProgress = this.userAchievements[achievement.id] || { unlocked: false };
128
- const isUnlocked = userProgress.unlocked;
129
-
130
- // 創建成就卡片
131
- const card = document.createElement('div');
132
- card.className = `achievement-card ${achievement.type}`;
133
- if (!isUnlocked) {
134
- card.classList.add('locked');
135
- }
136
-
137
- // 設定成就圖示 (使用emoji)
138
- let icon = '🏆';
139
- switch (achievement.type) {
140
- case 'journey':
141
- icon = '🧭';
142
- break;
143
- case 'trial':
144
- icon = '⚔️';
145
- break;
146
- case 'secret':
147
- icon = '🔮';
148
- break;
149
- }
150
- // 卡片內容
151
- card.innerHTML = `
152
- <div class="achievement-icon">${icon}</div>
153
- <h3 class="achievement-title">${achievement.title}</h3>
154
- <p class="achievement-description">${isUnlocked || achievement.type !== 'secret' ? achievement.description : '??????????????????'}</p>
155
- ${isUnlocked && userProgress.unlockedAt ?
156
- `<div class="achievement-unlock-time">解鎖於: ${userProgress.unlockedAt}</div>` : ''}
157
- ${!isUnlocked ? '<div class="lock-icon">🔒</div>' : ''}
158
- `;
159
- this.achievementsGrid.appendChild(card);
160
- });
161
  }
162
 
163
- // 更新成就統計
164
- updateStats() {
165
- if (!this.achievementsStats) return;
166
-
167
- const totalAchievements = this.achievements.length;
168
- const unlockedAchievements = Object.values(this.userAchievements)
169
- .filter(progress => progress.unlocked).length;
170
-
171
- this.achievementsStats.textContent = `已解鎖成就:${unlockedAchievements} / ${totalAchievements}`;
 
 
172
  }
173
 
174
- // 設定分類標籤事件
175
- setupTabEvents() {
176
- if (!this.tabButtons) return;
177
-
178
- this.tabButtons.forEach(button => {
179
- button.addEventListener('click', () => {
180
- // 移除所有標籤的active類別
181
- this.tabButtons.forEach(btn => btn.classList.remove('active'));
182
-
183
- // 添加active類別到當前標籤
184
- button.classList.add('active');
185
-
186
- // 獲取標籤類型並渲染對應成就
187
- const type = button.dataset.type;
188
- this.renderAchievements(type);
189
- });
190
- });
191
  }
192
 
193
- // 設定返回按鈕事件
194
- setupBackButton() {
195
- if (!this.backButton) return;
196
-
197
- // 返回按鈕已在HTML中直接設定為連結到kingdom_map.html
198
  }
199
 
200
- // 檢查成就是否解鎖
201
- checkAchievement(id) {
202
- const achievement = this.achievements.find(a => a.id === id);
203
- if (!achievement) return false;
204
-
205
- // 如果成就已經解鎖,直接返回true
206
- if (this.userAchievements[id]?.unlocked) return true;
207
-
208
- // 根據成就類型和條件檢查是否解鎖
209
- // 這裡需要根據實際遊戲邏輯實現
210
- // 此處僅為示例,實際應用中需要根據遊戲狀態檢查
211
- return false;
212
  }
213
 
214
- // 解鎖成就
215
- unlockAchievement(id) {
216
- console.log(`嘗試解鎖成就 ID: ${id}`);
217
-
218
- const achievement = this.achievements.find(a => a.id === id);
219
- if (!achievement) {
220
- console.error(`找不到ID為 ${id} 的成就`);
221
- return;
222
- }
223
-
224
- // 如果成就已經解鎖,不做任何事
225
- if (this.userAchievements[id]?.unlocked) {
226
- console.log(`成就 "${achievement.title}" 已經解鎖過了`);
227
- return;
228
- }
229
-
230
- // 設定成就為已解鎖
231
- const now = new Date();
232
- const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
233
-
234
- this.userAchievements[id] = {
235
- unlocked: true,
236
- unlockedAt: formattedDate
237
- };
238
-
239
- // 儲存使用者成就進度
240
- this.saveUserProgress();
241
-
242
- // 更新成就統計
243
- this.updateStats();
244
-
245
- // 如果當前在成就頁面且顯示的是該成就所屬類型,重新渲染
246
- if (this.achievementsGrid) {
247
- const activeTab = document.querySelector('.tab-button.active')?.dataset.type;
248
- if (activeTab === 'all' || activeTab === achievement.type) {
249
- this.renderAchievements(activeTab);
250
- }
251
- }
252
-
253
- // 顯示成就解鎖通知
254
- this.showAchievementToast(achievement);
255
- console.log(`成就 \"${achievement.title}\" 已解鎖!`);
256
- // 將解鎖的成就添加到待處理佇列
257
- this.addPendingAchievement(achievement.id);
258
  }
259
-
260
  // 將成就添加到待處理佇列
261
  addPendingAchievement(achievementId) {
262
  let pendingAchievements = JSON.parse(localStorage.getItem('pendingAchievements')) || [];
@@ -275,48 +139,29 @@ class AchievementSystem {
275
  }
276
 
277
  // 顯示成就解鎖通知
278
- showAchievementToast(achievement) {
279
  console.log(`顯示成就解鎖通知: ${achievement.title}`);
280
 
281
  // 確保通知容器存在
282
  if (!this.toastContainer) {
283
- this.toastContainer = this.createToastContainer();
284
- }
285
-
286
- // 將成就添加到通知佇列
287
- this.toastQueue.push(achievement);
288
-
289
- // 如果沒有正在處理通知,開始處理
290
- if (!this.isProcessingToast) {
291
- this.processToastQueue();
292
- }
293
- }
294
- // 處理通知佇列
295
- async processToastQueue() {
296
- if (this.toastQueue.length === 0) {
297
- this.isProcessingToast = false;
298
- return;
299
  }
300
 
301
- this.isProcessingToast = true;
302
- const achievement = this.toastQueue.shift();
303
-
304
  // 創建通知元素
305
  const toast = document.createElement('div');
306
- toast.className = `toast ${achievement.type}`;
307
 
308
- // 設定成就圖示 (使用emoji)
309
  let icon = '🏆';
310
- switch (achievement.type) {
311
- case 'journey':
312
- icon = '🧭';
313
- break;
314
- case 'trial':
315
- icon = '⚔️';
316
- break;
317
- case 'secret':
318
- icon = '🔮';
319
- break;
320
  }
321
 
322
  // 通知內容
@@ -335,27 +180,44 @@ class AchievementSystem {
335
  // 添加到通知容器
336
  this.toastContainer.appendChild(toast);
337
 
338
- // 等待用戶點擊確認
339
- await new Promise(resolve => {
340
  const confirmButton = document.createElement('button');
341
  confirmButton.textContent = '確認';
342
  confirmButton.className = 'toast-confirm-button';
 
 
 
 
 
 
 
 
 
 
343
  toast.appendChild(confirmButton);
344
- confirmButton.addEventListener('click', resolve);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  });
346
- toast.remove();
347
-
348
- // 處理下一個通知
349
- this.processToastQueue();
350
  }
351
-
352
  // 檢查所有成就條件
353
  checkAllAchievements(gameState) {
354
  console.log('檢查所有成就條件', gameState);
355
 
356
- // 這個方法應該在遊戲狀態更新時調用
357
- // gameState 是遊戲當前狀態,包含玩家進度、分數等信息
358
-
359
  this.achievements.forEach(achievement => {
360
  // 如果成就已經解鎖,跳過
361
  if (this.userAchievements[achievement.id]?.unlocked) return;
@@ -366,145 +228,71 @@ class AchievementSystem {
366
  if (isUnlocked) {
367
  console.log(`成就 "${achievement.title}" 條件已滿足,準備解鎖`);
368
  this.unlockAchievement(achievement.id);
 
 
369
  }
370
  });
371
  }
372
 
373
  // 檢查特定成就條件
374
  checkAchievementCondition(achievement, gameState) {
375
- // 這個方法需要根據實際遊戲邏輯實現
376
- // 此處僅為示例,實際應用中需要根據遊戲狀態檢查
377
-
378
- const { condition } = achievement;
379
- let result = false;
380
-
381
- switch (condition.type) {
382
- case 'login_count':
383
- result = gameState.loginCount >= condition.value;
384
- break;
385
-
386
- case 'complete_prologue':
387
- result = gameState.prologueCompleted === condition.value;
388
- break;
389
-
390
- case 'visit_all_trials':
391
- result = gameState.visitedTrials &&
392
- gameState.visitedTrials.includes('平方之泉') &&
393
- gameState.visitedTrials.includes('變換山谷') &&
394
- gameState.visitedTrials.includes('展開之塔');
395
- break;
396
-
397
- case 'complete_all_trials':
398
- result = gameState.completedTrials &&
399
- gameState.completedTrials['平方之泉']?.completed &&
400
- gameState.completedTrials['變換山谷']?.completed &&
401
- gameState.completedTrials['展開之塔']?.completed;
402
- break;
403
-
404
- case 'min_stars_all_trials':
405
- result = gameState.completedTrials &&
406
- gameState.completedTrials['平方之泉']?.stars >= condition.value &&
407
- gameState.completedTrials['變換山谷']?.stars >= condition.value &&
408
- gameState.completedTrials['展開之塔']?.stars >= condition.value;
409
- break;
410
-
411
- case 'all_trials_three_stars':
412
- result = gameState.completedTrials &&
413
- gameState.completedTrials['平方之泉']?.stars === 3 &&
414
- gameState.completedTrials['變換山谷']?.stars === 3 &&
415
- gameState.completedTrials['展開之塔']?.stars === 3;
416
- break;
417
-
418
- case 'consecutive_login_days':
419
- result = gameState.consecutiveLoginDays >= condition.value;
420
- break;
421
-
422
- case 'complete_trial':
423
- result = gameState.completedTrials &&
424
- gameState.completedTrials[condition.value]?.completed;
425
- break;
426
-
427
- case 'trial_stars':
428
- result = gameState.completedTrials &&
429
- gameState.completedTrials[condition.value.trial]?.stars >= condition.value.stars;
430
- break;
431
-
432
- case 'trial_no_errors':
433
- result = gameState.completedTrials &&
434
- gameState.completedTrials[condition.value]?.noErrors;
435
- break;
436
-
437
- case 'correct_streak':
438
- result = gameState.maxCorrectStreak >= condition.value;
439
- break;
440
-
441
- case 'trial_completion_time':
442
- result = gameState.fastestTrialTime &&
443
- gameState.fastestTrialTime <= condition.value;
444
- break;
445
-
446
- case 'question_answer_time':
447
- result = gameState.fastestQuestionTime &&
448
- gameState.fastestQuestionTime <= condition.value;
449
- break;
450
-
451
- case 'retry_after_failure':
452
- result = gameState.retriesAfterFailure >= condition.value;
453
- break;
454
-
455
- case 'complete_trial_after_time':
456
- const currentHour = new Date().getHours();
457
- const currentMinute = new Date().getMinutes();
458
- result = gameState.lastTrialCompletionTime &&
459
- (currentHour > condition.value.hour ||
460
- (currentHour === condition.value.hour && currentMinute >= condition.value.minute));
461
- break;
462
-
463
- case 'start_trial_before_time':
464
- const startHour = new Date().getHours();
465
- const startMinute = new Date().getMinutes();
466
- result = gameState.lastTrialStartTime &&
467
- (startHour < condition.value.hour ||
468
- (startHour === condition.value.hour && startMinute < condition.value.minute));
469
- break;
470
-
471
- case 'complete_all_trials_on_weekend':
472
- const day = new Date().getDay();
473
- result = gameState.completedTrials &&
474
- gameState.completedTrials['平方之泉']?.completed &&
475
- gameState.completedTrials['變換山谷']?.completed &&
476
- gameState.completedTrials['展開之塔']?.completed &&
477
- (day === 0 || day === 6); // 0是星期日,6是星期六
478
- break;
479
-
480
- case 'all_three_stars_same_day':
481
- result = gameState.allThreeStarsSameDay;
482
- break;
483
-
484
- case 'first_attempt_all_three_stars':
485
- result = gameState.firstAttemptAllThreeStars;
486
- break;
487
-
488
- case 'music_enabled_all_trials':
489
- result = gameState.musicEnabledAllTrials;
490
- break;
491
-
492
- case 'music_disabled_all_trials':
493
- result = gameState.musicDisabledAllTrials;
494
- break;
495
-
496
- default:
497
- result = false;
498
- break;
499
  }
500
 
501
- console.log(`檢查成就 "${achievement.title}" 條件: ${condition.type}, 結果: ${result}`);
502
- return result;
503
- }
504
-
505
- // 模擬解鎖成就 (僅用於測試)
506
- simulateUnlock(id) {
507
- this.unlockAchievement(id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  }
509
  }
510
 
@@ -513,73 +301,5 @@ document.addEventListener('DOMContentLoaded', () => {
513
  // 創建成就系統實例
514
  window.achievementSystem = new AchievementSystem();
515
  console.log('成就系統已掛載到全域 window 物件');
516
-
517
- // 測試用:添加模擬解鎖按鈕
518
- if (location.search.includes('debug=true')) {
519
- const debugButton = document.createElement('button');
520
- debugButton.textContent = '模擬解鎖成就';
521
- debugButton.style.position = 'fixed';
522
- debugButton.style.bottom = '20px';
523
- debugButton.style.left = '20px';
524
- debugButton.style.zIndex = '1000';
525
- debugButton.addEventListener('click', () => {
526
- // 隨機選擇一個未解鎖的成就
527
- const unlockedIds = Object.keys(window.achievementSystem.userAchievements)
528
- .filter(id => window.achievementSystem.userAchievements[id].unlocked);
529
-
530
- const availableAchievements = window.achievementSystem.achievements
531
- .filter(a => !unlockedIds.includes(a.id));
532
-
533
- if (availableAchievements.length > 0) {
534
- const randomIndex = Math.floor(Math.random() * availableAchievements.length);
535
- const achievementToUnlock = availableAchievements[randomIndex];
536
- window.achievementSystem.simulateUnlock(achievementToUnlock.id);
537
- console.log(`模擬解鎖成就: ${achievementToUnlock.title}`);
538
- } else {
539
- console.log('沒有可解鎖的成就了');
540
- }
541
- });
542
- document.body.appendChild(debugButton);
543
- }
544
  });
545
 
546
- // 提供全域函數,方便在其他頁面調用
547
- window.unlockAchievement = function(id) {
548
- if (window.achievementSystem) {
549
- window.achievementSystem.unlockAchievement(id);
550
- } else {
551
- console.error('成就系統尚未初始化,無法解鎖成就');
552
- }
553
- };
554
-
555
- window.checkAllAchievements = function(gameState) {
556
- if (window.achievementSystem) {
557
- window.achievementSystem.checkAllAchievements(gameState);
558
- } else {
559
- console.error('成就系統尚未初始化,無法檢查成就');
560
- }
561
- };
562
-
563
- /**
564
- * 成就系統整合指南
565
- *
566
- * 1. 在遊戲主頁添加成就按鈕,點擊後導向成就頁面:
567
- * <button onclick="window.location.href='achievements.html'">成就圖鑑</button>
568
- *
569
- * 2. 在遊戲狀態更新時檢查成就:
570
- * // 構建遊戲狀態
571
- * const gameState = {
572
- * // 遊戲相關資料...
573
- * };
574
- *
575
- * // 檢查成就
576
- * if (window.achievementSystem) {
577
- * window.achievementSystem.checkAllAchievements(gameState);
578
- * }
579
- *
580
- * 3. 手動解鎖特定成就:
581
- * // 例如,當玩家首次登入時
582
- * if (window.achievementSystem) {
583
- * window.achievementSystem.unlockAchievement('journey_1');
584
- * }
585
- */
 
1
+ // 成就系統類
 
 
 
 
 
2
  class AchievementSystem {
3
  constructor() {
4
+ this.achievements = [];
5
+ this.userAchievements = {};
6
+ this.toastContainer = null;
7
+ this.toastQueue = [];
8
+ this.isProcessingToast = false;
9
 
10
+ // 載入成就資料
11
+ this.loadAchievements();
 
 
 
 
12
 
13
+ // 載入用戶成就進度
14
+ this.loadUserAchievements();
15
 
16
+ // 創通知容器
17
+ this.createToastContainer();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
 
20
  // 載入成就資料
21
  async loadAchievements() {
22
  try {
23
  const response = await fetch('achievements.json');
24
+ const data = await response.json();
25
+ this.achievements = data.achievements;
 
 
26
  console.log('成就資料載入成功:', this.achievements.length, '個成就');
27
  } catch (error) {
28
  console.error('載入成就資料失敗:', error);
29
+ // 使用預設成就資料
30
+ this.achievements = this.getDefaultAchievements();
31
  }
32
  }
33
 
34
+ // 預設成就資料
35
+ getDefaultAchievements() {
36
+ return [
37
+ {
38
+ "id": "first_login",
39
+ "title": "初次登入",
40
+ "description": "歡迎來到數學魔法王國!",
41
+ "category": "journey",
42
+ "conditions": [{"type": "login_count", "value": 1}]
43
+ },
44
+ {
45
+ "id": "prologue_complete",
46
+ "title": "序章完成",
47
+ "description": "完成遊戲序章",
48
+ "category": "journey",
49
+ "conditions": [{"type": "complete_prologue", "value": true}]
50
+ },
51
+ {
52
+ "id": "spring_complete",
53
+ "title": "平方感知者",
54
+ "description": "完成平方之泉試煉",
55
+ "category": "trial",
56
+ "conditions": [{"type": "complete_trial", "trial": "spring"}]
57
+ }
58
+ ];
59
  }
60
 
61
+ // 載入成就進度
62
+ loadUserAchievements() {
63
+ const currentPlayerId = localStorage.getItem('currentPlayerId');
64
+ if (currentPlayerId) {
65
+ const savedAchievements = localStorage.getItem(`achievements_${currentPlayerId}`);
66
+ if (savedAchievements) {
67
+ this.userAchievements = JSON.parse(savedAchievements);
68
+ console.log('使用者成就進度載入成功:', Object.keys(this.userAchievements).length, '個成就');
69
+ }
70
  }
71
  }
72
 
73
+ // 保存用戶成就進度
74
+ saveUserAchievements() {
75
+ const currentPlayerId = localStorage.getItem('currentPlayerId');
76
+ if (currentPlayerId) {
77
+ localStorage.setItem(`achievements_${currentPlayerId}`, JSON.stringify(this.userAchievements));
78
+ console.log('使用者成就進度已保存');
 
 
 
 
 
 
 
 
 
 
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
 
82
+ // 創建通知容器
83
+ createToastContainer() {
84
+ let container = document.getElementById('toast-container');
85
+ if (!container) {
86
+ container = document.createElement('div');
87
+ container.id = 'toast-container';
88
+ container.className = 'toast-container';
89
+ document.body.appendChild(container);
90
+ }
91
+ this.toastContainer = container;
92
+ return container;
93
  }
94
 
95
+ // 解鎖成就
96
+ unlockAchievement(achievementId) {
97
+ if (!this.userAchievements[achievementId]) {
98
+ this.userAchievements[achievementId] = {
99
+ unlocked: true,
100
+ unlockedAt: new Date().toISOString()
101
+ };
102
+ this.saveUserAchievements();
103
+ console.log(`成就 ${achievementId} 已解鎖`);
104
+ return true;
105
+ }
106
+ return false;
 
 
 
 
 
107
  }
108
 
109
+ // 檢查成就是否已解鎖
110
+ isAchievementUnlocked(achievementId) {
111
+ return this.userAchievements[achievementId]?.unlocked || false;
 
 
112
  }
113
 
114
+ // 獲取已解鎖成就數量
115
+ getUnlockedCount() {
116
+ return Object.values(this.userAchievements).filter(a => a.unlocked).length;
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
+ // 獲取總成就數量
120
+ getTotalCount() {
121
+ return this.achievements.length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  }
123
+
124
  // 將成就添加到待處理佇列
125
  addPendingAchievement(achievementId) {
126
  let pendingAchievements = JSON.parse(localStorage.getItem('pendingAchievements')) || [];
 
139
  }
140
 
141
  // 顯示成就解鎖通知
142
+ async showAchievementToast(achievement) {
143
  console.log(`顯示成就解鎖通知: ${achievement.title}`);
144
 
145
  // 確保通知容器存在
146
  if (!this.toastContainer) {
147
+ this.toastContainer = document.getElementById('toast-container');
148
+ if (!this.toastContainer) {
149
+ console.error('找不到通知容器');
150
+ return;
151
+ }
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
 
 
 
154
  // 創建通知元素
155
  const toast = document.createElement('div');
156
+ toast.className = `toast ${achievement.category}`;
157
 
158
+ // 根據成就類別設置
159
  let icon = '🏆';
160
+ switch (achievement.category) {
161
+ case 'journey': icon = '🌟'; break;
162
+ case 'trial': icon = '⚔️'; break;
163
+ case 'secret': icon = '🔮'; break;
164
+ default: icon = '🏆'; break;
 
 
 
 
 
165
  }
166
 
167
  // 通知內容
 
180
  // 添加到通知容器
181
  this.toastContainer.appendChild(toast);
182
 
183
+ // 自動消失或等待用戶點擊
184
+ return new Promise(resolve => {
185
  const confirmButton = document.createElement('button');
186
  confirmButton.textContent = '確認';
187
  confirmButton.className = 'toast-confirm-button';
188
+ confirmButton.style.cssText = `
189
+ background: #ff8c00;
190
+ color: white;
191
+ border: none;
192
+ padding: 8px 16px;
193
+ border-radius: 4px;
194
+ cursor: pointer;
195
+ margin-top: 10px;
196
+ font-size: 14px;
197
+ `;
198
  toast.appendChild(confirmButton);
199
+
200
+ const handleConfirm = () => {
201
+ toast.remove();
202
+ resolve();
203
+ };
204
+
205
+ confirmButton.addEventListener('click', handleConfirm);
206
+
207
+ // 10秒後自動消失
208
+ setTimeout(() => {
209
+ if (toast.parentNode) {
210
+ toast.remove();
211
+ resolve();
212
+ }
213
+ }, 10000);
214
  });
 
 
 
 
215
  }
216
+
217
  // 檢查所有成就條件
218
  checkAllAchievements(gameState) {
219
  console.log('檢查所有成就條件', gameState);
220
 
 
 
 
221
  this.achievements.forEach(achievement => {
222
  // 如果成就已經解鎖,跳過
223
  if (this.userAchievements[achievement.id]?.unlocked) return;
 
228
  if (isUnlocked) {
229
  console.log(`成就 "${achievement.title}" 條件已滿足,準備解鎖`);
230
  this.unlockAchievement(achievement.id);
231
+ // 將成就添加到待處理佇列,而不是立即顯示
232
+ this.addPendingAchievement(achievement.id);
233
  }
234
  });
235
  }
236
 
237
  // 檢查特定成就條件
238
  checkAchievementCondition(achievement, gameState) {
239
+ if (!achievement.conditions || achievement.conditions.length === 0) {
240
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  }
242
 
243
+ // 所有條件都必須滿足
244
+ return achievement.conditions.every(condition => {
245
+ let result = false;
246
+
247
+ switch (condition.type) {
248
+ case 'login_count':
249
+ result = gameState.loginCount >= condition.value;
250
+ break;
251
+
252
+ case 'complete_prologue':
253
+ result = gameState.prologueCompleted === condition.value;
254
+ break;
255
+
256
+ case 'complete_trial':
257
+ result = gameState.completedTrials &&
258
+ gameState.completedTrials[condition.trial]?.completed;
259
+ break;
260
+
261
+ case 'visit_all_trials':
262
+ result = gameState.visitedTrials &&
263
+ gameState.visitedTrials.includes('spring') &&
264
+ gameState.visitedTrials.includes('valley') &&
265
+ gameState.visitedTrials.includes('tower');
266
+ break;
267
+
268
+ case 'complete_all_trials':
269
+ result = gameState.completedTrials &&
270
+ gameState.completedTrials.spring?.completed &&
271
+ gameState.completedTrials.valley?.completed &&
272
+ gameState.completedTrials.tower?.completed;
273
+ break;
274
+
275
+ case 'min_stars_all_trials':
276
+ result = gameState.completedTrials &&
277
+ gameState.completedTrials.spring?.stars >= condition.value &&
278
+ gameState.completedTrials.valley?.stars >= condition.value &&
279
+ gameState.completedTrials.tower?.stars >= condition.value;
280
+ break;
281
+
282
+ case 'all_trials_three_stars':
283
+ result = gameState.completedTrials &&
284
+ gameState.completedTrials.spring?.stars === 3 &&
285
+ gameState.completedTrials.valley?.stars === 3 &&
286
+ gameState.completedTrials.tower?.stars === 3;
287
+ break;
288
+
289
+ default:
290
+ console.warn(`未知的成就條件類型: ${condition.type}`);
291
+ result = false;
292
+ }
293
+
294
+ return result;
295
+ });
296
  }
297
  }
298
 
 
301
  // 創建成就系統實例
302
  window.achievementSystem = new AchievementSystem();
303
  console.log('成就系統已掛載到全域 window 物件');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  });
305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
achievements_old.js ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 數學魔法王國:成就系統
3
+ * 負責讀取成就資料、管理成就狀態、顯示成就卡片與通知
4
+ */
5
+
6
+ // 成就系統主類別
7
+ class AchievementSystem {
8
+ constructor() {
9
+ this.achievements = []; // 從JSON載入的成就資料
10
+ this.userAchievements = {}; // 使用者的成就進度
11
+ this.toastQueue = []; // 成就通知佇列
12
+ this.isProcessingToast = false; // 是否正在處理通知佇列
13
+
14
+ // DOM元素
15
+ this.achievementsGrid = document.getElementById('achievements-grid');
16
+ this.achievementsStats = document.getElementById('achievements-stats');
17
+ this.tabButtons = document.querySelectorAll('.tab-button');
18
+ this.toastContainer = document.getElementById('toast-container') || this.createToastContainer();
19
+ this.backButton = document.getElementById('back-button');
20
+
21
+ // 初始化
22
+ this.init();
23
+
24
+ console.log('成就系統建構完成');
25
+ }
26
+
27
+ // 初始化成就系統
28
+ async init() {
29
+ try {
30
+ // 載入成就資料
31
+ await this.loadAchievements();
32
+
33
+ // 載入使用者成就進度
34
+ this.loadUserProgress();
35
+
36
+ // 如果在成就頁面,渲染成就卡片
37
+ if (this.achievementsGrid) {
38
+ // 渲染成就卡片
39
+ this.renderAchievements('all');
40
+
41
+ // 更新成就統計
42
+ this.updateStats();
43
+
44
+ // 設定分類標籤事件
45
+ this.setupTabEvents();
46
+
47
+ // 設定返回按鈕事件
48
+ this.setupBackButton();
49
+ }
50
+
51
+ console.log('成就系統初始化完成');
52
+ } catch (error) {
53
+ console.error('成就系統初始化失敗:', error);
54
+ }
55
+ }
56
+
57
+ // 創建通知容器
58
+ createToastContainer() {
59
+ console.log('創建通知容器');
60
+ const toastContainer = document.createElement('div');
61
+ toastContainer.className = 'toast-container';
62
+ toastContainer.id = 'toast-container';
63
+ document.body.appendChild(toastContainer);
64
+ return toastContainer;
65
+ }
66
+
67
+ // 載入成就資料
68
+ async loadAchievements() {
69
+ try {
70
+ const response = await fetch('achievements.json');
71
+ if (!response.ok) {
72
+ throw new Error(`HTTP error! status: ${response.status}`);
73
+ }
74
+ this.achievements = await response.json();
75
+ console.log('成就資料載入成功:', this.achievements.length, '個成就');
76
+ } catch (error) {
77
+ console.error('載入成就資料失敗:', error);
78
+ // 如果無法載入,使用空陣列
79
+ this.achievements = [];
80
+ }
81
+ }
82
+
83
+ // 載入使用者成就進度
84
+ loadUserProgress() {
85
+ try {
86
+ // 從localStorage讀取使用者成就進度
87
+ const savedProgress = localStorage.getItem('userAchievements');
88
+ this.userAchievements = savedProgress ? JSON.parse(savedProgress) : {};
89
+
90
+ console.log('使用者成就進度載入成功', this.userAchievements);
91
+ } catch (error) {
92
+ console.error('載入使用者成就進度失敗:', error);
93
+ this.userAchievements = {};
94
+ }
95
+ }
96
+
97
+ // 儲存使用者成就進度
98
+ saveUserProgress() {
99
+ try {
100
+ localStorage.setItem('userAchievements', JSON.stringify(this.userAchievements));
101
+ console.log('使用者成就進度儲存成功');
102
+ } catch (error) {
103
+ console.error('儲存使用者成就進度失敗:', error);
104
+ }
105
+ }
106
+
107
+ // 渲染成就卡片
108
+ renderAchievements(type = 'all') {
109
+ if (!this.achievementsGrid) return;
110
+
111
+ // 清空成就網格
112
+ this.achievementsGrid.innerHTML = '';
113
+
114
+ // 篩選成就
115
+ const filteredAchievements = type === 'all'
116
+ ? this.achievements
117
+ : this.achievements.filter(achievement => achievement.type === type);
118
+
119
+ // 如果沒有成就,顯示提示訊息
120
+ if (filteredAchievements.length === 0) {
121
+ this.achievementsGrid.innerHTML = '<p class="no-achievements">此分類沒有成就</p>';
122
+ return;
123
+ }
124
+
125
+ // 渲染每個成就卡片
126
+ filteredAchievements.forEach(achievement => {
127
+ const userProgress = this.userAchievements[achievement.id] || { unlocked: false };
128
+ const isUnlocked = userProgress.unlocked;
129
+
130
+ // 創建成就卡片
131
+ const card = document.createElement('div');
132
+ card.className = `achievement-card ${achievement.type}`;
133
+ if (!isUnlocked) {
134
+ card.classList.add('locked');
135
+ }
136
+
137
+ // 設定成就圖示 (使用emoji)
138
+ let icon = '🏆';
139
+ switch (achievement.type) {
140
+ case 'journey':
141
+ icon = '🧭';
142
+ break;
143
+ case 'trial':
144
+ icon = '⚔️';
145
+ break;
146
+ case 'secret':
147
+ icon = '🔮';
148
+ break;
149
+ }
150
+ // 卡片內容
151
+ card.innerHTML = `
152
+ <div class="achievement-icon">${icon}</div>
153
+ <h3 class="achievement-title">${achievement.title}</h3>
154
+ <p class="achievement-description">${isUnlocked || achievement.type !== 'secret' ? achievement.description : '??????????????????'}</p>
155
+ ${isUnlocked && userProgress.unlockedAt ?
156
+ `<div class="achievement-unlock-time">解鎖於: ${userProgress.unlockedAt}</div>` : ''}
157
+ ${!isUnlocked ? '<div class="lock-icon">🔒</div>' : ''}
158
+ `;
159
+ this.achievementsGrid.appendChild(card);
160
+ });
161
+ }
162
+
163
+ // 更新成就統計
164
+ updateStats() {
165
+ if (!this.achievementsStats) return;
166
+
167
+ const totalAchievements = this.achievements.length;
168
+ const unlockedAchievements = Object.values(this.userAchievements)
169
+ .filter(progress => progress.unlocked).length;
170
+
171
+ this.achievementsStats.textContent = `已解鎖成就:${unlockedAchievements} / ${totalAchievements}`;
172
+ }
173
+
174
+ // 設定分類標籤事件
175
+ setupTabEvents() {
176
+ if (!this.tabButtons) return;
177
+
178
+ this.tabButtons.forEach(button => {
179
+ button.addEventListener('click', () => {
180
+ // 移除所有標籤的active類別
181
+ this.tabButtons.forEach(btn => btn.classList.remove('active'));
182
+
183
+ // 添加active類別到當前標籤
184
+ button.classList.add('active');
185
+
186
+ // 獲取標籤類型並渲染對應成就
187
+ const type = button.dataset.type;
188
+ this.renderAchievements(type);
189
+ });
190
+ });
191
+ }
192
+
193
+ // 設定返回按鈕事件
194
+ setupBackButton() {
195
+ if (!this.backButton) return;
196
+
197
+ // 返回按鈕已在HTML中直接設定為連結到kingdom_map.html
198
+ }
199
+
200
+ // 檢查成就是否解鎖
201
+ checkAchievement(id) {
202
+ const achievement = this.achievements.find(a => a.id === id);
203
+ if (!achievement) return false;
204
+
205
+ // 如果成就已經解鎖,直接返回true
206
+ if (this.userAchievements[id]?.unlocked) return true;
207
+
208
+ // 根據成就類型和條件檢查是否解鎖
209
+ // 這裡需要根據實際遊戲邏輯實現
210
+ // 此處僅為示例,實際應用中需要根據遊戲狀態檢查
211
+ return false;
212
+ }
213
+
214
+ // 解鎖成就
215
+ unlockAchievement(id) {
216
+ console.log(`嘗試解鎖成就 ID: ${id}`);
217
+
218
+ const achievement = this.achievements.find(a => a.id === id);
219
+ if (!achievement) {
220
+ console.error(`找不到ID為 ${id} 的成就`);
221
+ return;
222
+ }
223
+
224
+ // 如果成就已經解鎖,不做任何事
225
+ if (this.userAchievements[id]?.unlocked) {
226
+ console.log(`成就 "${achievement.title}" 已經解鎖過了`);
227
+ return;
228
+ }
229
+
230
+ // 設定成就為已解鎖
231
+ const now = new Date();
232
+ const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
233
+
234
+ this.userAchievements[id] = {
235
+ unlocked: true,
236
+ unlockedAt: formattedDate
237
+ };
238
+
239
+ // 儲存使用者成就進度
240
+ this.saveUserProgress();
241
+
242
+ // 更新成就統計
243
+ this.updateStats();
244
+
245
+ // 如果當前在成就頁面且顯示的是該成就所屬類型,重新渲染
246
+ if (this.achievementsGrid) {
247
+ const activeTab = document.querySelector('.tab-button.active')?.dataset.type;
248
+ if (activeTab === 'all' || activeTab === achievement.type) {
249
+ this.renderAchievements(activeTab);
250
+ }
251
+ }
252
+
253
+ // 顯示成就解鎖通知
254
+ this.showAchievementToast(achievement);
255
+ console.log(`成就 \"${achievement.title}\" 已解鎖!`);
256
+ // 將解鎖的成就添加到待處理佇列
257
+ this.addPendingAchievement(achievement.id);
258
+ }
259
+
260
+ // 將成就添加到待處理佇列
261
+ addPendingAchievement(achievementId) {
262
+ let pendingAchievements = JSON.parse(localStorage.getItem('pendingAchievements')) || [];
263
+ if (!pendingAchievements.includes(achievementId)) {
264
+ pendingAchievements.push(achievementId);
265
+ localStorage.setItem('pendingAchievements', JSON.stringify(pendingAchievements));
266
+ console.log(`成就 ${achievementId} 已添加到待處理佇列`);
267
+ }
268
+ }
269
+
270
+ // 獲取並清除待處理成就
271
+ getAndClearPendingAchievements() {
272
+ const pendingAchievements = JSON.parse(localStorage.getItem('pendingAchievements')) || [];
273
+ localStorage.removeItem('pendingAchievements');
274
+ return pendingAchievements;
275
+ }
276
+
277
+ // 顯示成就解鎖通知
278
+ showAchievementToast(achievement) {
279
+ console.log(`顯示成就解鎖通知: ${achievement.title}`);
280
+
281
+ // 確保通知容器存在
282
+ if (!this.toastContainer) {
283
+ this.toastContainer = this.createToastContainer();
284
+ }
285
+
286
+ // 將成就添加到通知佇列
287
+ this.toastQueue.push(achievement);
288
+
289
+ // 如果沒有正在處理通知,開始處理
290
+ if (!this.isProcessingToast) { // 顯示成就解鎖通知
291
+ async showAchievementToast(achievement) {
292
+ console.log(`顯示成就解鎖通知: ${achievement.title}`);
293
+
294
+ // 確保通知容器存在
295
+ if (!this.toastContainer) {
296
+ this.toastContainer = document.getElementById('toast-container');
297
+ if (!this.toastContainer) {
298
+ console.error('找不到通知容器');
299
+ return;
300
+ }
301
+ }
302
+
303
+ // 創建通知元素
304
+ const toast = document.createElement('div');
305
+ toast.className = `toast ${achievement.category}`;
306
+
307
+ // 根據成就類別設置圖標
308
+ let icon = '🏆';
309
+ switch (achievement.category) {
310
+ case 'journey': icon = '🌟'; break;
311
+ case 'trial': icon = '⚔️'; break;
312
+ case 'secret': icon = '🔮'; break;
313
+ default: icon = '🏆'; break;
314
+ }
315
+
316
+ // 通知內容
317
+ toast.innerHTML = `
318
+ <div class="toast-icon">${icon}</div>
319
+ <div class="toast-content">
320
+ <div class="toast-title">成就解鎖!</div>
321
+ <div>【${achievement.title}】:${achievement.description}</div>
322
+ </div>
323
+ `;
324
+
325
+ // 播放音效
326
+ const audio = new Audio('ding.mp3');
327
+ audio.play().catch(e => console.error('播放音效失敗:', e));
328
+
329
+ // 添加到通知容器
330
+ this.toastContainer.appendChild(toast);
331
+
332
+ // 自動消失或等待用戶點擊
333
+ return new Promise(resolve => {
334
+ const confirmButton = document.createElement('button');
335
+ confirmButton.textContent = '確認';
336
+ confirmButton.className = 'toast-confirm-button';
337
+ confirmButton.style.cssText = `
338
+ background: #ff8c00;
339
+ color: white;
340
+ border: none;
341
+ padding: 8px 16px;
342
+ border-radius: 4px;
343
+ cursor: pointer;
344
+ margin-top: 10px;
345
+ font-size: 14px;
346
+ `;
347
+ toast.appendChild(confirmButton);
348
+
349
+ const handleConfirm = () => {
350
+ toast.remove();
351
+ resolve();
352
+ };
353
+
354
+ confirmButton.addEventListener('click', handleConfirm);
355
+
356
+ // 10秒後自動消失
357
+ setTimeout(() => {
358
+ if (toast.parentNode) {
359
+ toast.remove();
360
+ resolve();
361
+ }
362
+ }, 10000);
363
+ });
364
+ } // 檢查所有成就條件
365
+ checkAllAchievements(gameState) {
366
+ console.log('檢查所有成就條件', gameState);
367
+
368
+ // 這個方法應該在遊戲狀態更新時調用
369
+ // gameState 是遊戲當前狀態,包含玩家進度、分數等信息
370
+
371
+ this.achievements.forEach(achievement => {
372
+ // 如果成就已經解鎖,跳過
373
+ if (this.userAchievements[achievement.id]?.unlocked) return;
374
+
375
+ // 根據成就條件檢查是否解鎖
376
+ const isUnlocked = this.checkAchievementCondition(achievement, gameState);
377
+
378
+ if (isUnlocked) {
379
+ console.log(`成就 "${achievement.title}" 條件已滿足,準備解鎖`);
380
+ this.unlockAchievement(achievement.id);
381
+ // 將成就添加到待處理佇列,而不是立即顯示
382
+ this.addPendingAchievement(achievement.id);
383
+ }
384
+ });
385
+ }
386
+
387
+ // 檢查特定成就條件
388
+ checkAchievementCondition(achievement, gameState) {
389
+ // 這個方法需要根據實際遊戲邏輯實現
390
+ // 此處僅為示例,實際應用中需要根據遊戲狀態檢查
391
+
392
+ const { condition } = achievement;
393
+ let result = false;
394
+
395
+ switch (condition.type) {
396
+ case 'login_count':
397
+ result = gameState.loginCount >= condition.value;
398
+ break;
399
+
400
+ case 'complete_prologue':
401
+ result = gameState.prologueCompleted === condition.value;
402
+ break;
403
+
404
+ case 'visit_all_trials':
405
+ result = gameState.visitedTrials &&
406
+ gameState.visitedTrials.includes('平方之泉') &&
407
+ gameState.visitedTrials.includes('變換山谷') &&
408
+ gameState.visitedTrials.includes('展開之塔');
409
+ break;
410
+
411
+ case 'complete_all_trials':
412
+ result = gameState.completedTrials &&
413
+ gameState.completedTrials['平方之泉']?.completed &&
414
+ gameState.completedTrials['變換山谷']?.completed &&
415
+ gameState.completedTrials['展開之塔']?.completed;
416
+ break;
417
+
418
+ case 'min_stars_all_trials':
419
+ result = gameState.completedTrials &&
420
+ gameState.completedTrials['平方之泉']?.stars >= condition.value &&
421
+ gameState.completedTrials['變換山谷']?.stars >= condition.value &&
422
+ gameState.completedTrials['展開之塔']?.stars >= condition.value;
423
+ break;
424
+
425
+ case 'all_trials_three_stars':
426
+ result = gameState.completedTrials &&
427
+ gameState.completedTrials['平方之泉']?.stars === 3 &&
428
+ gameState.completedTrials['變換山谷']?.stars === 3 &&
429
+ gameState.completedTrials['展開之塔']?.stars === 3;
430
+ break;
431
+
432
+ case 'consecutive_login_days':
433
+ result = gameState.consecutiveLoginDays >= condition.value;
434
+ break;
435
+
436
+ case 'complete_trial':
437
+ result = gameState.completedTrials &&
438
+ gameState.completedTrials[condition.value]?.completed;
439
+ break;
440
+
441
+ case 'trial_stars':
442
+ result = gameState.completedTrials &&
443
+ gameState.completedTrials[condition.value.trial]?.stars >= condition.value.stars;
444
+ break;
445
+
446
+ case 'trial_no_errors':
447
+ result = gameState.completedTrials &&
448
+ gameState.completedTrials[condition.value]?.noErrors;
449
+ break;
450
+
451
+ case 'correct_streak':
452
+ result = gameState.maxCorrectStreak >= condition.value;
453
+ break;
454
+
455
+ case 'trial_completion_time':
456
+ result = gameState.fastestTrialTime &&
457
+ gameState.fastestTrialTime <= condition.value;
458
+ break;
459
+
460
+ case 'question_answer_time':
461
+ result = gameState.fastestQuestionTime &&
462
+ gameState.fastestQuestionTime <= condition.value;
463
+ break;
464
+
465
+ case 'retry_after_failure':
466
+ result = gameState.retriesAfterFailure >= condition.value;
467
+ break;
468
+
469
+ case 'complete_trial_after_time':
470
+ const currentHour = new Date().getHours();
471
+ const currentMinute = new Date().getMinutes();
472
+ result = gameState.lastTrialCompletionTime &&
473
+ (currentHour > condition.value.hour ||
474
+ (currentHour === condition.value.hour && currentMinute >= condition.value.minute));
475
+ break;
476
+
477
+ case 'start_trial_before_time':
478
+ const startHour = new Date().getHours();
479
+ const startMinute = new Date().getMinutes();
480
+ result = gameState.lastTrialStartTime &&
481
+ (startHour < condition.value.hour ||
482
+ (startHour === condition.value.hour && startMinute < condition.value.minute));
483
+ break;
484
+
485
+ case 'complete_all_trials_on_weekend':
486
+ const day = new Date().getDay();
487
+ result = gameState.completedTrials &&
488
+ gameState.completedTrials['平方之泉']?.completed &&
489
+ gameState.completedTrials['變換山谷']?.completed &&
490
+ gameState.completedTrials['展開之塔']?.completed &&
491
+ (day === 0 || day === 6); // 0是星期日,6是星期六
492
+ break;
493
+
494
+ case 'all_three_stars_same_day':
495
+ result = gameState.allThreeStarsSameDay;
496
+ break;
497
+
498
+ case 'first_attempt_all_three_stars':
499
+ result = gameState.firstAttemptAllThreeStars;
500
+ break;
501
+
502
+ case 'music_enabled_all_trials':
503
+ result = gameState.musicEnabledAllTrials;
504
+ break;
505
+
506
+ case 'music_disabled_all_trials':
507
+ result = gameState.musicDisabledAllTrials;
508
+ break;
509
+
510
+ default:
511
+ result = false;
512
+ break;
513
+ }
514
+
515
+ console.log(`檢查成就 "${achievement.title}" 條件: ${condition.type}, 結果: ${result}`);
516
+ return result;
517
+ }
518
+
519
+ // 模擬解鎖成就 (僅用於測試)
520
+ simulateUnlock(id) {
521
+ this.unlockAchievement(id);
522
+ }
523
+ }
524
+
525
+ // 當頁面載入完成後初始化成就系統
526
+ document.addEventListener('DOMContentLoaded', () => {
527
+ // 創建成就系統實例
528
+ window.achievementSystem = new AchievementSystem();
529
+ console.log('成就系統已掛載到全域 window 物件');
530
+
531
+ // 測試用:添加模擬解鎖按鈕
532
+ if (location.search.includes('debug=true')) {
533
+ const debugButton = document.createElement('button');
534
+ debugButton.textContent = '模擬解鎖成就';
535
+ debugButton.style.position = 'fixed';
536
+ debugButton.style.bottom = '20px';
537
+ debugButton.style.left = '20px';
538
+ debugButton.style.zIndex = '1000';
539
+ debugButton.addEventListener('click', () => {
540
+ // 隨機選擇一個未解鎖的成就
541
+ const unlockedIds = Object.keys(window.achievementSystem.userAchievements)
542
+ .filter(id => window.achievementSystem.userAchievements[id].unlocked);
543
+
544
+ const availableAchievements = window.achievementSystem.achievements
545
+ .filter(a => !unlockedIds.includes(a.id));
546
+
547
+ if (availableAchievements.length > 0) {
548
+ const randomIndex = Math.floor(Math.random() * availableAchievements.length);
549
+ const achievementToUnlock = availableAchievements[randomIndex];
550
+ window.achievementSystem.simulateUnlock(achievementToUnlock.id);
551
+ console.log(`模擬解鎖成就: ${achievementToUnlock.title}`);
552
+ } else {
553
+ console.log('沒有可解鎖的成就了');
554
+ }
555
+ });
556
+ document.body.appendChild(debugButton);
557
+ }
558
+ });
559
+
560
+ // 提供全域函數,方便在其他頁面調用
561
+ window.unlockAchievement = function(id) {
562
+ if (window.achievementSystem) {
563
+ window.achievementSystem.unlockAchievement(id);
564
+ } else {
565
+ console.error('成就系統尚未初始化,無法解鎖成就');
566
+ }
567
+ };
568
+
569
+ window.checkAllAchievements = function(gameState) {
570
+ if (window.achievementSystem) {
571
+ window.achievementSystem.checkAllAchievements(gameState);
572
+ } else {
573
+ console.error('成就系統尚未初始化,無法檢查成就');
574
+ }
575
+ };
576
+
577
+ /**
578
+ * 成就系統整合指南
579
+ *
580
+ * 1. 在遊戲主頁添加成就按鈕,點擊後導向成就頁面:
581
+ * <button onclick="window.location.href='achievements.html'">成就圖鑑</button>
582
+ *
583
+ * 2. 在遊戲狀態更新時檢查成就:
584
+ * // 構建遊戲狀態
585
+ * const gameState = {
586
+ * // 遊戲相關資料...
587
+ * };
588
+ *
589
+ * // 檢查成就
590
+ * if (window.achievementSystem) {
591
+ * window.achievementSystem.checkAllAchievements(gameState);
592
+ * }
593
+ *
594
+ * 3. 手動解鎖特定成就:
595
+ * // 例如,當玩家首次登入時
596
+ * if (window.achievementSystem) {
597
+ * window.achievementSystem.unlockAchievement('journey_1');
598
+ * }
599
+ */
kingdom_map.js CHANGED
@@ -1,16 +1,31 @@
1
  document.addEventListener('DOMContentLoaded', function() {
2
  // 檢查是否有玩家資料
3
  const currentPlayerId = localStorage.getItem('currentPlayerId');
4
- const playerName = localStorage.getItem('playerName');
5
- const playerProfession = localStorage.getItem('playerProfession');
6
 
7
  // 如果沒有玩家資料,重定向到首頁
8
- if (!currentPlayerId || !playerName || !playerProfession) {
9
  alert('請先登入遊戲!');
10
  window.location.href = 'index.html';
11
  return;
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  // 獲取DOM元素
15
  const springLocation = document.getElementById('spring-location');
16
  const valleyLocation = document.getElementById('valley-location');
 
1
  document.addEventListener('DOMContentLoaded', function() {
2
  // 檢查是否有玩家資料
3
  const currentPlayerId = localStorage.getItem('currentPlayerId');
 
 
4
 
5
  // 如果沒有玩家資料,重定向到首頁
6
+ if (!currentPlayerId) {
7
  alert('請先登入遊戲!');
8
  window.location.href = 'index.html';
9
  return;
10
  }
11
 
12
+ // 檢查並顯示待處理的成就通知
13
+ if (window.achievementSystem) {
14
+ const pendingAchievements = window.achievementSystem.getAndClearPendingAchievements();
15
+ if (pendingAchievements.length > 0) {
16
+ console.log('發現待處理成就:', pendingAchievements);
17
+ // 延遲顯示成就通知,確保頁面完全載入
18
+ setTimeout(() => {
19
+ pendingAchievements.forEach(achievementId => {
20
+ const achievement = window.achievementSystem.achievements.find(a => a.id === achievementId);
21
+ if (achievement) {
22
+ window.achievementSystem.showAchievementToast(achievement);
23
+ }
24
+ });
25
+ }, 1000);
26
+ }
27
+ }
28
+
29
  // 獲取DOM元素
30
  const springLocation = document.getElementById('spring-location');
31
  const valleyLocation = document.getElementById('valley-location');