Spaces:
Running
Running
Upload 40 files
Browse files- .gitattributes +1 -0
- Theme.mp3 +3 -0
- index.html +2 -3
- tower.js +228 -205
.gitattributes
CHANGED
|
@@ -42,3 +42,4 @@ valley.jpg filter=lfs diff=lfs merge=lfs -text
|
|
| 42 |
valley.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
tower.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
tower.mp3 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 42 |
valley.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
tower.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
tower.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
Theme.mp3 filter=lfs diff=lfs merge=lfs -text
|
Theme.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:91fe1ace3e637b39f05c9d5067b91c78d06d4a62217416dffd2998e75633304f
|
| 3 |
+
size 2676118
|
index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
| 9 |
<body class="home-page">
|
| 10 |
<!-- 音效元素 -->
|
| 11 |
<audio id="bgm" loop>
|
| 12 |
-
<source src="
|
| 13 |
</audio>
|
| 14 |
|
| 15 |
<div class="game-container">
|
|
@@ -24,8 +24,7 @@
|
|
| 24 |
|
| 25 |
<!-- 音樂來源註記 -->
|
| 26 |
<div class="music-credit">
|
| 27 |
-
<p>背景音樂:
|
| 28 |
-
<p>推廣:J&B無版權音樂庫 <a href="https://bit.ly/2YfWIhw" target="_blank">https://bit.ly/2YfWIhw</a></p>
|
| 29 |
<button id="toggle-bgm" class="small-btn">音樂開/關</button>
|
| 30 |
</div>
|
| 31 |
</div>
|
|
|
|
| 9 |
<body class="home-page">
|
| 10 |
<!-- 音效元素 -->
|
| 11 |
<audio id="bgm" loop>
|
| 12 |
+
<source src="Theme.mp3" type="audio/mpeg">
|
| 13 |
</audio>
|
| 14 |
|
| 15 |
<div class="game-container">
|
|
|
|
| 24 |
|
| 25 |
<!-- 音樂來源註記 -->
|
| 26 |
<div class="music-credit">
|
| 27 |
+
<p>背景音樂:由Suno智做</p>
|
|
|
|
| 28 |
<button id="toggle-bgm" class="small-btn">音樂開/關</button>
|
| 29 |
</div>
|
| 30 |
</div>
|
tower.js
CHANGED
|
@@ -316,7 +316,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 316 |
const dropZoneSortable = new Sortable(dropZone, {
|
| 317 |
group: {
|
| 318 |
name: 'formula',
|
| 319 |
-
pull:
|
| 320 |
put: true
|
| 321 |
},
|
| 322 |
animation: 100,
|
|
@@ -352,6 +352,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 352 |
evt.from.classList.remove('filled');
|
| 353 |
evt.from.style.backgroundColor = 'rgba(100, 100, 100, 0.5)';
|
| 354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
// 更新保存的答案
|
| 356 |
updateSavedAnswers();
|
| 357 |
}
|
|
@@ -382,6 +387,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 382 |
dragItem.id = `drag-item-${index}`;
|
| 383 |
dragItem.textContent = item;
|
| 384 |
dragItem.dataset.value = item;
|
|
|
|
|
|
|
| 385 |
|
| 386 |
// 添加點擊事件,點擊可移除(如果在拖放區內)
|
| 387 |
dragItem.addEventListener('click', function() {
|
|
@@ -406,8 +413,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 406 |
const dragItemsSortable = new Sortable(dragItemsContainer, {
|
| 407 |
group: {
|
| 408 |
name: 'formula',
|
| 409 |
-
pull: 'clone', // 設置為clone模式,拖拽
|
| 410 |
-
put:
|
| 411 |
},
|
| 412 |
animation: 100,
|
| 413 |
delay: 0,
|
|
@@ -415,20 +422,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 415 |
ghostClass: "drag-ghost",
|
| 416 |
forceFallback: true,
|
| 417 |
fallbackTolerance: 3,
|
| 418 |
-
sort: false, // 禁止
|
| 419 |
filter: '.disabled', // 禁止拖拽的元素
|
| 420 |
-
onStart: function(evt) {
|
| 421 |
-
evt.item.style.opacity = '0.8';
|
| 422 |
-
},
|
| 423 |
-
onEnd: function(evt) {
|
| 424 |
-
evt.item.style.opacity = '1';
|
| 425 |
-
},
|
| 426 |
-
// 當元素被複製時
|
| 427 |
onClone: function(evt) {
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
-
//
|
| 431 |
-
clone.addEventListener('click', function() {
|
| 432 |
const parent = this.parentElement;
|
| 433 |
if (parent && parent.classList.contains('drop-zone')) {
|
| 434 |
parent.classList.remove('filled');
|
|
@@ -446,19 +450,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 446 |
|
| 447 |
sortableInstances.push(dragItemsSortable);
|
| 448 |
|
| 449 |
-
//
|
| 450 |
-
if (savedAnswers.length > 0) {
|
| 451 |
savedAnswers.forEach((value, index) => {
|
| 452 |
-
if (index < dropZones.length) {
|
| 453 |
// 找到對應值的拖拽項
|
| 454 |
-
const
|
| 455 |
-
if (
|
| 456 |
-
//
|
| 457 |
-
const
|
| 458 |
-
|
| 459 |
|
| 460 |
// 添加點擊事件,點擊可移除
|
| 461 |
-
|
| 462 |
const parent = this.parentElement;
|
| 463 |
if (parent && parent.classList.contains('drop-zone')) {
|
| 464 |
parent.classList.remove('filled');
|
|
@@ -473,7 +477,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 473 |
});
|
| 474 |
|
| 475 |
// 添加到拖放區
|
| 476 |
-
dropZones[index].appendChild(
|
| 477 |
dropZones[index].classList.add('filled');
|
| 478 |
dropZones[index].style.backgroundColor = 'rgba(255, 140, 0, 0.3)';
|
| 479 |
}
|
|
@@ -482,247 +486,243 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 482 |
}
|
| 483 |
|
| 484 |
// 顯示檢查答案按鈕,隱藏下一題按鈕
|
| 485 |
-
checkAnswerBtn.style.display = 'block';
|
| 486 |
nextQuestionBtn.style.display = 'none';
|
| 487 |
|
| 488 |
-
//
|
|
|
|
| 489 |
feedbackContainer.style.display = 'none';
|
| 490 |
-
feedbackContainer.className = 'feedback-container';
|
| 491 |
}
|
| 492 |
|
| 493 |
// 更新保存的答案
|
| 494 |
function updateSavedAnswers() {
|
| 495 |
const answers = [];
|
| 496 |
|
| 497 |
-
// 獲取所有拖放區
|
| 498 |
dropZones.forEach(zone => {
|
| 499 |
-
const items =
|
| 500 |
if (items.length > 0) {
|
| 501 |
-
|
| 502 |
-
answers.push(value);
|
| 503 |
} else {
|
| 504 |
answers.push(null);
|
| 505 |
}
|
| 506 |
});
|
| 507 |
|
| 508 |
-
// 過濾掉null值
|
| 509 |
-
const filteredAnswers = answers.filter(answer => answer !== null);
|
| 510 |
-
|
| 511 |
// 保存答案
|
| 512 |
if (currentStage === 1) {
|
| 513 |
-
stage1Answers[currentQuestionIndex] =
|
| 514 |
} else {
|
| 515 |
-
stage2Answers[currentQuestionIndex] =
|
| 516 |
}
|
| 517 |
}
|
| 518 |
|
| 519 |
-
// 檢查答案
|
| 520 |
-
|
| 521 |
// 獲取當前階段的問題數據
|
| 522 |
const stageData = currentStage === 1 ? questions[currentQuestionIndex].stage1 : questions[currentQuestionIndex].stage2;
|
| 523 |
|
| 524 |
// 獲取用戶答案
|
| 525 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
|
| 527 |
-
// 檢查是否
|
| 528 |
-
if (
|
| 529 |
-
feedbackContainer.
|
| 530 |
-
feedbackContainer.className = 'feedback-container incorrect';
|
| 531 |
feedbackContainer.style.display = 'block';
|
| 532 |
-
|
| 533 |
-
return;
|
| 534 |
}
|
| 535 |
|
| 536 |
// 檢查答案是否正確
|
| 537 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
if (isCorrect) {
|
| 540 |
-
//
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
|
| 546 |
-
// 如果是第一階段,啟用第二階段按鈕
|
| 547 |
if (currentStage === 1) {
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
// 顯示下一題按鈕,隱藏檢查答案按鈕
|
| 551 |
-
checkAnswerBtn.style.display = 'none';
|
| 552 |
-
nextQuestionBtn.style.display = 'block';
|
| 553 |
nextQuestionBtn.textContent = '進入第二階段';
|
| 554 |
nextQuestionBtn.onclick = function() {
|
| 555 |
loadQuestion(currentQuestionIndex, 2);
|
| 556 |
};
|
| 557 |
} else {
|
| 558 |
-
//
|
| 559 |
-
correctAnswers++;
|
| 560 |
-
|
| 561 |
-
// 打開對應的門
|
| 562 |
-
if (currentQuestionIndex < doorsProgress.length) {
|
| 563 |
-
doorsProgress[currentQuestionIndex].classList.add('opened');
|
| 564 |
-
}
|
| 565 |
-
|
| 566 |
-
// 如果還有下一題,顯示下一題按鈕
|
| 567 |
if (currentQuestionIndex < questions.length - 1) {
|
| 568 |
-
checkAnswerBtn.style.display = 'none';
|
| 569 |
-
nextQuestionBtn.style.display = 'block';
|
| 570 |
nextQuestionBtn.textContent = '下一題';
|
| 571 |
nextQuestionBtn.onclick = function() {
|
| 572 |
loadQuestion(currentQuestionIndex + 1, 1);
|
| 573 |
};
|
| 574 |
} else {
|
| 575 |
-
|
| 576 |
-
|
|
|
|
|
|
|
| 577 |
}
|
| 578 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
} else {
|
| 580 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
wrongAttempts++;
|
| 582 |
-
wrongSound.play();
|
| 583 |
|
|
|
|
| 584 |
if (currentStage === 1) {
|
| 585 |
-
//
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
} else {
|
| 594 |
-
// 第二次或更多次
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
const correctAnswerText = questions[currentQuestionIndex].stage1.correctAnswer;
|
| 598 |
-
feedbackContainer.innerHTML = `答錯了,請參考上方提示修正答案!<br><br>正確答案應為:<br>「${correctAnswerText}」<br>請依照此答案填入,才能進入第二階段。`;
|
| 599 |
-
feedbackContainer.className = 'feedback-container incorrect';
|
| 600 |
-
feedbackContainer.style.display = 'block';
|
| 601 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
} else {
|
| 603 |
-
// 第二階段
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
|
|
|
| 607 |
}
|
| 608 |
-
}
|
| 609 |
-
});
|
| 610 |
-
|
| 611 |
-
// 顯示提示圖片
|
| 612 |
-
function showHintImage() {
|
| 613 |
-
// 移除現有的提示容器
|
| 614 |
-
const existingHintContainers = document.querySelectorAll('.hint-container');
|
| 615 |
-
existingHintContainers.forEach(container => container.remove());
|
| 616 |
-
|
| 617 |
-
// 創建提示容器
|
| 618 |
-
const hintContainer = document.createElement('div');
|
| 619 |
-
hintContainer.className = 'hint-container';
|
| 620 |
-
hintContainer.style.marginBottom = '15px';
|
| 621 |
-
hintContainer.style.padding = '10px';
|
| 622 |
-
hintContainer.style.backgroundColor = 'rgba(60, 60, 60, 0.7)';
|
| 623 |
-
hintContainer.style.borderRadius = '8px';
|
| 624 |
-
hintContainer.style.border = '1px solid rgba(255, 140, 0, 0.5)';
|
| 625 |
-
hintContainer.style.textAlign = 'center';
|
| 626 |
-
|
| 627 |
-
// 創建提示圖片
|
| 628 |
-
const hintImage = document.createElement('img');
|
| 629 |
-
hintImage.src = `towerhint${currentQuestionIndex + 1}1.jpg`;
|
| 630 |
-
hintImage.style.maxWidth = '50%';
|
| 631 |
-
hintImage.style.height = 'auto';
|
| 632 |
-
hintImage.style.marginBottom = '10px';
|
| 633 |
-
hintContainer.appendChild(hintImage);
|
| 634 |
-
|
| 635 |
-
// 創建提示文字
|
| 636 |
-
const hintText = document.createElement('div');
|
| 637 |
-
hintText.textContent = '請仔細觀察上方提示圖,按照分配律的方式展開填入正確答案。';
|
| 638 |
-
hintText.style.color = 'white';
|
| 639 |
-
hintText.style.fontSize = '1rem';
|
| 640 |
-
hintContainer.appendChild(hintText);
|
| 641 |
-
|
| 642 |
-
// 插入到公式模板前面
|
| 643 |
-
formulaTemplate.parentNode.insertBefore(hintContainer, formulaTemplate);
|
| 644 |
-
}
|
| 645 |
-
|
| 646 |
-
// 檢查答案是否正確
|
| 647 |
-
function checkAnswer(userAnswers, correctAnswerSets) {
|
| 648 |
-
// 如果用戶答案長度不符合任何一組正確答案,直接返回false
|
| 649 |
-
const isLengthMatch = correctAnswerSets.some(set => set.length === userAnswers.length);
|
| 650 |
-
if (!isLengthMatch) return false;
|
| 651 |
-
|
| 652 |
-
// 檢查每組正確答案
|
| 653 |
-
for (const correctSet of correctAnswerSets) {
|
| 654 |
-
// 如果長度不同,跳過這組
|
| 655 |
-
if (correctSet.length !== userAnswers.length) continue;
|
| 656 |
-
|
| 657 |
-
// 複製用戶答案和正確答案,以便進行排序和比較
|
| 658 |
-
const sortedUserAnswers = [...userAnswers].sort();
|
| 659 |
-
const sortedCorrectAnswers = [...correctSet].sort();
|
| 660 |
-
|
| 661 |
-
// 比較排序後的答案
|
| 662 |
-
const isMatch = sortedUserAnswers.every((answer, index) => answer === sortedCorrectAnswers[index]);
|
| 663 |
|
| 664 |
-
|
| 665 |
-
if (isMatch) return true;
|
| 666 |
}
|
| 667 |
-
|
| 668 |
-
// 如果沒有匹配的答案組,返回false
|
| 669 |
-
return false;
|
| 670 |
}
|
| 671 |
|
| 672 |
-
// 階段按鈕點擊事件
|
| 673 |
-
stage1Btn.addEventListener('click', function() {
|
| 674 |
-
if (currentStage !== 1) {
|
| 675 |
-
loadQuestion(currentQuestionIndex, 1);
|
| 676 |
-
}
|
| 677 |
-
});
|
| 678 |
-
|
| 679 |
-
stage2Btn.addEventListener('click', function() {
|
| 680 |
-
// 只有在第一階段答對後才能進入第二階段
|
| 681 |
-
if (currentStage !== 2 && checkAnswer(stage1Answers[currentQuestionIndex], questions[currentQuestionIndex].stage1.answers)) {
|
| 682 |
-
loadQuestion(currentQuestionIndex, 2);
|
| 683 |
-
}
|
| 684 |
-
});
|
| 685 |
-
|
| 686 |
// 顯示結果
|
| 687 |
function showResult() {
|
| 688 |
quizContainer.style.display = 'none';
|
| 689 |
resultContainer.style.display = 'block';
|
| 690 |
|
| 691 |
-
// 計算得分(
|
| 692 |
-
const score = Math.min(3, Math.max(
|
| 693 |
|
| 694 |
-
// 更新
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
failButtons.style.display = 'none';
|
| 702 |
} else {
|
| 703 |
-
resultTitle.textContent = '
|
| 704 |
-
resultMessage.
|
| 705 |
-
|
| 706 |
-
// 顯示失敗按鈕
|
| 707 |
successButtons.style.display = 'none';
|
| 708 |
-
failButtons.style.display = '
|
| 709 |
}
|
| 710 |
|
| 711 |
-
// 更新星星
|
| 712 |
-
starResults.forEach((star, index) => {
|
| 713 |
-
if (index < score) {
|
| 714 |
-
star.classList.add('earned');
|
| 715 |
-
} else {
|
| 716 |
-
star.classList.remove('earned');
|
| 717 |
-
}
|
| 718 |
-
});
|
| 719 |
-
|
| 720 |
// 更新遊戲進度
|
| 721 |
-
gameProgress.completedTrials["展開之塔"].completed = score >=
|
| 722 |
gameProgress.completedTrials["展開之塔"].score = score;
|
| 723 |
|
| 724 |
-
// 標記已完成
|
| 725 |
-
for (let i = 0; i <
|
| 726 |
gameProgress.completedTrials["展開之塔"].challengesCompleted[i] = true;
|
| 727 |
}
|
| 728 |
|
|
@@ -730,37 +730,39 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 730 |
gameProgress.lastSaved = new Date().toISOString();
|
| 731 |
localStorage.setItem(`gameProgress_${currentPlayerId}`, JSON.stringify(gameProgress));
|
| 732 |
|
| 733 |
-
// 檢查成就
|
| 734 |
if (window.achievementSystem) {
|
| 735 |
const gameState = {
|
| 736 |
currentTrial: "展開之塔",
|
| 737 |
totalStars: score,
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
};
|
| 742 |
|
| 743 |
window.achievementSystem.checkAllAchievements(gameState);
|
| 744 |
}
|
| 745 |
}
|
| 746 |
|
| 747 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
retryBtn.addEventListener('click', function() {
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
currentStage = 1;
|
| 752 |
correctAnswers = 0;
|
| 753 |
-
stage1Answers = Array(questions.length).fill(null).map(() => []);
|
| 754 |
-
stage2Answers = Array(questions.length).fill(null).map(() => []);
|
| 755 |
|
| 756 |
-
// 重置
|
| 757 |
Array.from(doorsProgress).forEach(door => {
|
| 758 |
-
door.classList.remove('
|
| 759 |
});
|
| 760 |
|
| 761 |
-
//
|
| 762 |
-
|
| 763 |
-
|
| 764 |
|
| 765 |
// 載入第一題
|
| 766 |
loadQuestion(0, 1);
|
|
@@ -775,4 +777,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 775 |
nextTrialBtn.addEventListener('click', function() {
|
| 776 |
window.location.href = 'kingdom_map.html';
|
| 777 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
});
|
|
|
|
| 316 |
const dropZoneSortable = new Sortable(dropZone, {
|
| 317 |
group: {
|
| 318 |
name: 'formula',
|
| 319 |
+
pull: 'clone', // 允許從答案區拖出,但會被移除
|
| 320 |
put: true
|
| 321 |
},
|
| 322 |
animation: 100,
|
|
|
|
| 352 |
evt.from.classList.remove('filled');
|
| 353 |
evt.from.style.backgroundColor = 'rgba(100, 100, 100, 0.5)';
|
| 354 |
|
| 355 |
+
// 從答案格拖出的方塊直接移除,不回到拖拉區
|
| 356 |
+
if (evt.item && evt.item.parentNode) {
|
| 357 |
+
evt.item.remove();
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
// 更新保存的答案
|
| 361 |
updateSavedAnswers();
|
| 362 |
}
|
|
|
|
| 387 |
dragItem.id = `drag-item-${index}`;
|
| 388 |
dragItem.textContent = item;
|
| 389 |
dragItem.dataset.value = item;
|
| 390 |
+
dragItem.style.willChange = 'transform';
|
| 391 |
+
dragItem.style.touchAction = 'none';
|
| 392 |
|
| 393 |
// 添加點擊事件,點擊可移除(如果在拖放區內)
|
| 394 |
dragItem.addEventListener('click', function() {
|
|
|
|
| 413 |
const dragItemsSortable = new Sortable(dragItemsContainer, {
|
| 414 |
group: {
|
| 415 |
name: 'formula',
|
| 416 |
+
pull: 'clone', // 設置為clone模式,拖拽項不會消失
|
| 417 |
+
put: false // 不允許放回拖拽區
|
| 418 |
},
|
| 419 |
animation: 100,
|
| 420 |
delay: 0,
|
|
|
|
| 422 |
ghostClass: "drag-ghost",
|
| 423 |
forceFallback: true,
|
| 424 |
fallbackTolerance: 3,
|
| 425 |
+
sort: false, // 禁止排序
|
| 426 |
filter: '.disabled', // 禁止拖拽的元素
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
onClone: function(evt) {
|
| 428 |
+
// 克隆元素時添加相同的樣式和數據
|
| 429 |
+
evt.clone.className = evt.item.className;
|
| 430 |
+
evt.clone.dataset.value = evt.item.dataset.value;
|
| 431 |
+
evt.clone.style.willChange = 'transform';
|
| 432 |
+
evt.clone.style.touchAction = 'none';
|
| 433 |
|
| 434 |
+
// 添加點擊事件,點擊可移除(如果在拖放區內)
|
| 435 |
+
evt.clone.addEventListener('click', function() {
|
| 436 |
const parent = this.parentElement;
|
| 437 |
if (parent && parent.classList.contains('drop-zone')) {
|
| 438 |
parent.classList.remove('filled');
|
|
|
|
| 450 |
|
| 451 |
sortableInstances.push(dragItemsSortable);
|
| 452 |
|
| 453 |
+
// 如果有已保存的答案,恢復它們
|
| 454 |
+
if (savedAnswers && savedAnswers.length > 0) {
|
| 455 |
savedAnswers.forEach((value, index) => {
|
| 456 |
+
if (index < dropZones.length && value) {
|
| 457 |
// 找到對應值的拖拽項
|
| 458 |
+
const matchingItem = Array.from(dragItemElements).find(item => item.dataset.value === value);
|
| 459 |
+
if (matchingItem) {
|
| 460 |
+
// 克隆拖拽項
|
| 461 |
+
const clone = matchingItem.cloneNode(true);
|
| 462 |
+
clone.dataset.value = value;
|
| 463 |
|
| 464 |
// 添加點擊事件,點擊可移除
|
| 465 |
+
clone.addEventListener('click', function() {
|
| 466 |
const parent = this.parentElement;
|
| 467 |
if (parent && parent.classList.contains('drop-zone')) {
|
| 468 |
parent.classList.remove('filled');
|
|
|
|
| 477 |
});
|
| 478 |
|
| 479 |
// 添加到拖放區
|
| 480 |
+
dropZones[index].appendChild(clone);
|
| 481 |
dropZones[index].classList.add('filled');
|
| 482 |
dropZones[index].style.backgroundColor = 'rgba(255, 140, 0, 0.3)';
|
| 483 |
}
|
|
|
|
| 486 |
}
|
| 487 |
|
| 488 |
// 顯示檢查答案按鈕,隱藏下一題按鈕
|
| 489 |
+
checkAnswerBtn.style.display = 'inline-block';
|
| 490 |
nextQuestionBtn.style.display = 'none';
|
| 491 |
|
| 492 |
+
// 清空反饋容器
|
| 493 |
+
feedbackContainer.innerHTML = '';
|
| 494 |
feedbackContainer.style.display = 'none';
|
|
|
|
| 495 |
}
|
| 496 |
|
| 497 |
// 更新保存的答案
|
| 498 |
function updateSavedAnswers() {
|
| 499 |
const answers = [];
|
| 500 |
|
| 501 |
+
// 獲取所有拖放區的值
|
| 502 |
dropZones.forEach(zone => {
|
| 503 |
+
const items = zone.querySelectorAll('.drag-item');
|
| 504 |
if (items.length > 0) {
|
| 505 |
+
answers.push(items[0].dataset.value);
|
|
|
|
| 506 |
} else {
|
| 507 |
answers.push(null);
|
| 508 |
}
|
| 509 |
});
|
| 510 |
|
|
|
|
|
|
|
|
|
|
| 511 |
// 保存答案
|
| 512 |
if (currentStage === 1) {
|
| 513 |
+
stage1Answers[currentQuestionIndex] = answers;
|
| 514 |
} else {
|
| 515 |
+
stage2Answers[currentQuestionIndex] = answers;
|
| 516 |
}
|
| 517 |
}
|
| 518 |
|
| 519 |
+
// 檢查答案
|
| 520 |
+
function checkAnswer() {
|
| 521 |
// 獲取當前階段的問題數據
|
| 522 |
const stageData = currentStage === 1 ? questions[currentQuestionIndex].stage1 : questions[currentQuestionIndex].stage2;
|
| 523 |
|
| 524 |
// 獲取用戶答案
|
| 525 |
+
const userAnswerValues = [];
|
| 526 |
+
dropZones.forEach(zone => {
|
| 527 |
+
const items = zone.querySelectorAll('.drag-item');
|
| 528 |
+
if (items.length > 0) {
|
| 529 |
+
userAnswerValues.push(items[0].dataset.value);
|
| 530 |
+
} else {
|
| 531 |
+
userAnswerValues.push(null);
|
| 532 |
+
}
|
| 533 |
+
});
|
| 534 |
|
| 535 |
+
// 檢查是否有空白答案
|
| 536 |
+
if (userAnswerValues.includes(null)) {
|
| 537 |
+
feedbackContainer.innerHTML = '<p class="error">請填寫所有空格!</p>';
|
|
|
|
| 538 |
feedbackContainer.style.display = 'block';
|
| 539 |
+
return false;
|
|
|
|
| 540 |
}
|
| 541 |
|
| 542 |
// 檢查答案是否正確
|
| 543 |
+
let isCorrect = false;
|
| 544 |
+
|
| 545 |
+
if (currentStage === 1) {
|
| 546 |
+
// 第一階段:檢查兩組答案是否匹配
|
| 547 |
+
const group1 = [userAnswerValues[0], userAnswerValues[1]];
|
| 548 |
+
const group2 = [userAnswerValues[2], userAnswerValues[3]];
|
| 549 |
+
|
| 550 |
+
// 檢查每個答案組合是否匹配任何一個正確答案組合
|
| 551 |
+
isCorrect = stageData.answers.some(answer => {
|
| 552 |
+
return (
|
| 553 |
+
(group1.includes(answer[0]) && group1.includes(answer[1])) &&
|
| 554 |
+
(group2.includes(answer[0]) && group2.includes(answer[1]))
|
| 555 |
+
);
|
| 556 |
+
});
|
| 557 |
+
} else {
|
| 558 |
+
// 第二階段:檢查三個值是否匹配
|
| 559 |
+
isCorrect = stageData.answers.some(answer => {
|
| 560 |
+
// 第一個值必須是2
|
| 561 |
+
if (userAnswerValues[0] !== answer[0]) return false;
|
| 562 |
+
|
| 563 |
+
// 其他兩個值順序不限
|
| 564 |
+
const otherValues = [userAnswerValues[1], userAnswerValues[2]];
|
| 565 |
+
return (
|
| 566 |
+
otherValues.includes(answer[1]) &&
|
| 567 |
+
otherValues.includes(answer[2])
|
| 568 |
+
);
|
| 569 |
+
});
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
// 顯示反饋
|
| 573 |
+
feedbackContainer.innerHTML = '';
|
| 574 |
+
feedbackContainer.style.display = 'block';
|
| 575 |
|
| 576 |
if (isCorrect) {
|
| 577 |
+
// 播放正確音效
|
| 578 |
+
correctSound.currentTime = 0;
|
| 579 |
+
correctSound.play().catch(e => console.log('播放音效失敗:', e));
|
| 580 |
+
|
| 581 |
+
// 顯示正確反饋
|
| 582 |
+
const correctFeedback = document.createElement('p');
|
| 583 |
+
correctFeedback.className = 'success';
|
| 584 |
+
correctFeedback.textContent = '答對了!';
|
| 585 |
+
feedbackContainer.appendChild(correctFeedback);
|
| 586 |
+
|
| 587 |
+
// 更新UI
|
| 588 |
+
checkAnswerBtn.style.display = 'none';
|
| 589 |
|
|
|
|
| 590 |
if (currentStage === 1) {
|
| 591 |
+
// 第一階段完成,進入第二階段
|
|
|
|
|
|
|
|
|
|
|
|
|
| 592 |
nextQuestionBtn.textContent = '進入第二階段';
|
| 593 |
nextQuestionBtn.onclick = function() {
|
| 594 |
loadQuestion(currentQuestionIndex, 2);
|
| 595 |
};
|
| 596 |
} else {
|
| 597 |
+
// 第二階段完成,進入下一題或完成挑戰
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
if (currentQuestionIndex < questions.length - 1) {
|
|
|
|
|
|
|
| 599 |
nextQuestionBtn.textContent = '下一題';
|
| 600 |
nextQuestionBtn.onclick = function() {
|
| 601 |
loadQuestion(currentQuestionIndex + 1, 1);
|
| 602 |
};
|
| 603 |
} else {
|
| 604 |
+
nextQuestionBtn.textContent = '完成挑戰';
|
| 605 |
+
nextQuestionBtn.onclick = function() {
|
| 606 |
+
showResult();
|
| 607 |
+
};
|
| 608 |
}
|
| 609 |
}
|
| 610 |
+
|
| 611 |
+
nextQuestionBtn.style.display = 'inline-block';
|
| 612 |
+
|
| 613 |
+
// 更新進度
|
| 614 |
+
if (currentStage === 2) {
|
| 615 |
+
doorsProgress[currentQuestionIndex].classList.add('completed');
|
| 616 |
+
correctAnswers++;
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
return true;
|
| 620 |
} else {
|
| 621 |
+
// 播放錯誤音效
|
| 622 |
+
wrongSound.currentTime = 0;
|
| 623 |
+
wrongSound.play().catch(e => console.log('播放音效失敗:', e));
|
| 624 |
+
|
| 625 |
+
// 增加錯誤嘗試次數
|
| 626 |
wrongAttempts++;
|
|
|
|
| 627 |
|
| 628 |
+
// 第一階段特殊處理
|
| 629 |
if (currentStage === 1) {
|
| 630 |
+
// 創建提示容器(如果不存在)
|
| 631 |
+
let hintContainer = document.querySelector('.hint-container');
|
| 632 |
+
if (!hintContainer) {
|
| 633 |
+
hintContainer = document.createElement('div');
|
| 634 |
+
hintContainer.className = 'hint-container';
|
| 635 |
+
hintContainer.style.marginBottom = '15px';
|
| 636 |
+
hintContainer.style.padding = '10px';
|
| 637 |
+
hintContainer.style.backgroundColor = 'rgba(60, 60, 60, 0.7)';
|
| 638 |
+
hintContainer.style.borderRadius = '8px';
|
| 639 |
+
hintContainer.style.border = '1px solid rgba(255, 0, 0, 0.5)';
|
| 640 |
+
hintContainer.style.textAlign = 'center';
|
| 641 |
|
| 642 |
+
// 插入到公式模板前面
|
| 643 |
+
formulaTemplate.parentNode.insertBefore(hintContainer, formulaTemplate);
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
// 清空提示容器
|
| 647 |
+
hintContainer.innerHTML = '';
|
| 648 |
+
|
| 649 |
+
// 添加提示圖片
|
| 650 |
+
const hintImage = document.createElement('img');
|
| 651 |
+
hintImage.src = `towerhint${currentQuestionIndex + 1}1.jpg`;
|
| 652 |
+
hintImage.style.maxWidth = '50%';
|
| 653 |
+
hintImage.style.display = 'block';
|
| 654 |
+
hintImage.style.margin = '0 auto 10px';
|
| 655 |
+
hintContainer.appendChild(hintImage);
|
| 656 |
+
|
| 657 |
+
// 添加提示文字
|
| 658 |
+
const hintText = document.createElement('p');
|
| 659 |
+
hintText.style.color = 'white';
|
| 660 |
+
hintText.style.margin = '5px 0';
|
| 661 |
+
|
| 662 |
+
if (wrongAttempts === 1) {
|
| 663 |
+
// 第一次錯誤
|
| 664 |
+
hintText.textContent = '請仔細觀察上方提示圖,按照分配律的方式展開填入正確答案。';
|
| 665 |
} else {
|
| 666 |
+
// 第二次或更多次錯誤
|
| 667 |
+
hintText.textContent = `正確答案應為:\n「${stageData.correctAnswer}」\n請依照此答案填入,才能進入第二階段。`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
}
|
| 669 |
+
|
| 670 |
+
hintContainer.appendChild(hintText);
|
| 671 |
+
|
| 672 |
+
// 顯示錯誤反饋
|
| 673 |
+
const errorFeedback = document.createElement('p');
|
| 674 |
+
errorFeedback.className = 'error';
|
| 675 |
+
errorFeedback.textContent = '答錯了,請參考上方提示修正答案!';
|
| 676 |
+
feedbackContainer.appendChild(errorFeedback);
|
| 677 |
} else {
|
| 678 |
+
// 第二階段錯誤
|
| 679 |
+
const errorFeedback = document.createElement('p');
|
| 680 |
+
errorFeedback.className = 'error';
|
| 681 |
+
errorFeedback.textContent = '答錯了,請再試一次!';
|
| 682 |
+
feedbackContainer.appendChild(errorFeedback);
|
| 683 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
|
| 685 |
+
return false;
|
|
|
|
| 686 |
}
|
|
|
|
|
|
|
|
|
|
| 687 |
}
|
| 688 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
// 顯示結果
|
| 690 |
function showResult() {
|
| 691 |
quizContainer.style.display = 'none';
|
| 692 |
resultContainer.style.display = 'block';
|
| 693 |
|
| 694 |
+
// 計算得分(滿分3星)
|
| 695 |
+
const score = Math.min(3, Math.max(1, Math.ceil(correctAnswers / questions.length * 3)));
|
| 696 |
|
| 697 |
+
// 更新星星顯示
|
| 698 |
+
for (let i = 0; i < starResults.length; i++) {
|
| 699 |
+
starResults[i].style.opacity = i < score ? '1' : '0.3';
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
// 更新結果文字
|
| 703 |
+
if (score === 3) {
|
| 704 |
+
resultTitle.textContent = '恭喜完成挑戰!';
|
| 705 |
+
resultMessage.textContent = '你已完美掌握平方展開的技巧!';
|
| 706 |
+
successButtons.style.display = 'block';
|
| 707 |
+
failButtons.style.display = 'none';
|
| 708 |
+
} else if (score === 2) {
|
| 709 |
+
resultTitle.textContent = '挑戰成功!';
|
| 710 |
+
resultMessage.textContent = '你已基本掌握平方展開的技巧,繼續加油!';
|
| 711 |
+
successButtons.style.display = 'block';
|
| 712 |
failButtons.style.display = 'none';
|
| 713 |
} else {
|
| 714 |
+
resultTitle.textContent = '再接再厲!';
|
| 715 |
+
resultMessage.textContent = '平方展開需要更多練習,不要氣餒!';
|
|
|
|
|
|
|
| 716 |
successButtons.style.display = 'none';
|
| 717 |
+
failButtons.style.display = 'block';
|
| 718 |
}
|
| 719 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
// 更新遊戲進度
|
| 721 |
+
gameProgress.completedTrials["展開之塔"].completed = score >= 2;
|
| 722 |
gameProgress.completedTrials["展開之塔"].score = score;
|
| 723 |
|
| 724 |
+
// 標記所有問題為已完成
|
| 725 |
+
for (let i = 0; i < questions.length; i++) {
|
| 726 |
gameProgress.completedTrials["展開之塔"].challengesCompleted[i] = true;
|
| 727 |
}
|
| 728 |
|
|
|
|
| 730 |
gameProgress.lastSaved = new Date().toISOString();
|
| 731 |
localStorage.setItem(`gameProgress_${currentPlayerId}`, JSON.stringify(gameProgress));
|
| 732 |
|
| 733 |
+
// 檢查成就
|
| 734 |
if (window.achievementSystem) {
|
| 735 |
const gameState = {
|
| 736 |
currentTrial: "展開之塔",
|
| 737 |
totalStars: score,
|
| 738 |
+
currentTrialCompleted: true,
|
| 739 |
+
currentTrialErrors: questions.length - correctAnswers,
|
| 740 |
+
currentTrialCombo: correctAnswers
|
| 741 |
};
|
| 742 |
|
| 743 |
window.achievementSystem.checkAllAchievements(gameState);
|
| 744 |
}
|
| 745 |
}
|
| 746 |
|
| 747 |
+
// 檢查答案按鈕點擊事件
|
| 748 |
+
checkAnswerBtn.addEventListener('click', function() {
|
| 749 |
+
checkAnswer();
|
| 750 |
+
});
|
| 751 |
+
|
| 752 |
+
// 重試按鈕點擊事件
|
| 753 |
retryBtn.addEventListener('click', function() {
|
| 754 |
+
resultContainer.style.display = 'none';
|
| 755 |
+
quizContainer.style.display = 'block';
|
|
|
|
| 756 |
correctAnswers = 0;
|
|
|
|
|
|
|
| 757 |
|
| 758 |
+
// 重置進度指示器
|
| 759 |
Array.from(doorsProgress).forEach(door => {
|
| 760 |
+
door.classList.remove('completed');
|
| 761 |
});
|
| 762 |
|
| 763 |
+
// 重置答案
|
| 764 |
+
stage1Answers = Array(questions.length).fill(null).map(() => []);
|
| 765 |
+
stage2Answers = Array(questions.length).fill(null).map(() => []);
|
| 766 |
|
| 767 |
// 載入第一題
|
| 768 |
loadQuestion(0, 1);
|
|
|
|
| 777 |
nextTrialBtn.addEventListener('click', function() {
|
| 778 |
window.location.href = 'kingdom_map.html';
|
| 779 |
});
|
| 780 |
+
|
| 781 |
+
// 階段按鈕點擊事件(僅用於顯示,不可切換)
|
| 782 |
+
stage1Btn.addEventListener('click', function() {
|
| 783 |
+
if (currentStage !== 1) {
|
| 784 |
+
// 檢查是否已完成第一階段
|
| 785 |
+
if (stage1Answers[currentQuestionIndex].length > 0) {
|
| 786 |
+
loadQuestion(currentQuestionIndex, 1);
|
| 787 |
+
}
|
| 788 |
+
}
|
| 789 |
+
});
|
| 790 |
+
|
| 791 |
+
stage2Btn.addEventListener('click', function() {
|
| 792 |
+
if (currentStage !== 2) {
|
| 793 |
+
// 檢查是否已完成第一階段
|
| 794 |
+
if (stage1Answers[currentQuestionIndex].length > 0) {
|
| 795 |
+
loadQuestion(currentQuestionIndex, 2);
|
| 796 |
+
} else {
|
| 797 |
+
alert('請先完成第一階段!');
|
| 798 |
+
}
|
| 799 |
+
}
|
| 800 |
+
});
|
| 801 |
});
|