代码: 全选
<svg id="btn-maximize" style="cursor:pointer; width:32px; height:32px; fill:white;" viewBox="0 0 24 24"><path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zm6 16h-6v-2h4v-4h2v6zM4 20v-6h2v4h4v2H4z"></path></svg>代码: 全选
<svg id="btn-maximize" style="cursor:pointer; width:32px; height:32px; fill:white;" viewBox="0 0 24 24"><path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zm6 16h-6v-2h4v-4h2v6zM4 20v-6h2v4h4v2H4z"></path></svg>代码: 全选
改進:
一,回合前進的問題
回合前進在行動階段是正確的,但如果回退到開局階段,之後再點擊回合前進,則會發生「遊戲板不能正確放置、立方體無故出現」等問題,查明原因。
二,AI防呆之後的問題
在以下這段代碼執行之後:
```
if (!aiPlannedActions || aiPlannedActions.length === 0) {
console.warn('MCTS 未能計算出動作,使用防呆預設');
return;
}
```
AI再也不能重啟。整個視圖也不再能旋轉和移動。
一個原因是,當AI嘗試要行動時,當前行動方已經行走了一步或多步,而AI只能是從回合開始行動。如果是這種情況,則AI應該嘗試先執行「行走撤銷」,回到回合開始的狀態。
如果還有其它可能原因,使其仍然不能行動,則應解除當前視圖旋轉控制的封鎖,允許AI重啟之後再次嘗試進行思考和行動。
三,更改AI強度按鈕的功能設置
簡單、困難、專家等級,除了時間之外,還應加上必殺深度SURE_KILL_DEPTH這個變量的設置,簡單為3,困難為5,專家為7。
button#ai-strength-trigger應加上初始為「困難等級」的時間和必殺深度data屬性。頁面刷新時,程式應讀取其data屬性並更改變量值。
需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。代码: 全选
這三個問題分別涉及到狀態同步機制、防呆還原邏輯以及參數綁定。以下為需要修改及替換的代碼片段:
### 一、 修復「回合前進」在開局階段引發的遊戲板與立方體顯示異常
問題在於從「對戰階段」跳躍回退/前進到「開局階段」時,視圖沒有對遊戲板和圓柱體進行同步與透明度還原。
請找到 `restoreSnapshot(snap)` 函數,將其替換為以下代碼:
```javascript
function restoreSnapshot(snap) {
currentPlayer = snap.currentPlayer;
walkCount = snap.walkCount;
lastDirection = snap.lastDirection;
turnStopPositions = snap.turnStopPositions;
gameplayActive = snap.gameplayActive;
// 恢復棋譜狀態
globalMoveHistory = [...snap.globalMoveHistory];
currentTurnCoords = [...snap.currentTurnCoords];
clearMarkers();
// 【修改1】提取共同的修復與同步邏輯,確保所有階段都能正確重置遊戲板與圓柱
const fixOpacity = m => {
if (Array.isArray(m.material))
m.material.forEach(mat => {
mat.opacity = 1;
mat.transparent = false;
});
else {
m.material.opacity = 1;
m.material.transparent = false;
}
};
// ① 總是同步遊戲板 (boardMeshes)
boardMeshes.forEach(mesh => {
if (!snap.boardMeshesRefs.includes(mesh)) scene.remove(mesh);
});
snap.boardMeshesRefs.forEach(mesh => {
if (!boardMeshes.includes(mesh)) scene.add(mesh);
fixOpacity(mesh); // 修復透明度與動畫殘留
});
boardMeshes = [...snap.boardMeshesRefs];
// ② 總是同步圓柱 (cylinderMeshes)
cylinderMeshes.forEach(mesh => {
if (!snap.cylinderMeshesRefs.includes(mesh)) scene.remove(mesh);
});
snap.cylinderMeshesRefs.forEach(mesh => {
if (!cylinderMeshes.includes(mesh)) scene.add(mesh);
fixOpacity(mesh);
});
cylinderMeshes = [...snap.cylinderMeshesRefs];
// 開局階段:利用 3D 參照比對,完美支援開局的進退
if (!gameplayActive) {
currentPhaseFn = snap.currentPhaseFn;
blueCornerType = snap.blueCornerType;
orangeCornerType = snap.orangeCornerType;
placedEdges = { ...snap.placedEdges };
placedCorners = { ...snap.placedCorners };
edgeTurn = snap.edgeTurn;
cylindersData = JSON.parse(JSON.stringify(snap.cylindersData));
// ③ 同步開局可能放置的立方體 (cubes)
cubes.forEach(mesh => {
if (!snap.openingCubesRefs.includes(mesh)) scene.remove(mesh);
});
snap.openingCubesRefs.forEach(mesh => {
if (!cubes.includes(mesh)) scene.add(mesh);
fixOpacity(mesh);
});
cubes = [...snap.openingCubesRefs];
// 重新觸發當前步驟的點擊提示圈
if (currentPhaseFn) currentPhaseFn();
return; // 開局階段處理完畢,直接返回
}
// 以下為原本【對戰階段】的還原邏輯(保持不變)...
cubes.forEach(c => scene.remove(c));
cubes = [];
microphones.forEach(m => scene.remove(m));
microphones = [];
snap.cubes.forEach(sCube => {
placeCube(sCube.col, sCube.row, sCube.color);
const newCube = cubes[cubes.length - 1];
newCube.userData.score = sCube.score;
newCube.userData.currentNumber = sCube.currentNumber;
newCube.userData.moving = false;
newCube.userData.animating = false;
newCube.quaternion.copy(sCube.quat);
const onOwnMic = snap.microphones.some(
m => m.color === sCube.color && m.col === sCube.col && m.row === sCube.row
);
const targetY = onOwnMic ? itemY + 5 : itemY;
newCube.position.y = targetY;
newCube.userData.targetY = targetY;
});
snap.microphones.forEach(sMic => {
createMicrophoneMesh(sMic.color, sMic.col, sMic.row);
});
if (snap.subPhase === 'walk') {
showWalkOptions();
} else {
showMicrophonePlacementOptions();
}
}
```
---
### 二、 修復 AI 防呆後的死鎖問題並嘗試自動撤銷
當 AI 因為中途啟動或遇到邊界條件無法計算出路徑時,需先解除封鎖並嘗試自動回退一步讓其重新啟動計算。
請找到 `doMCTSAI()` 函數,替換其中的這段防呆邏輯:
**將原本的:**
```javascript
if (!aiPlannedActions || aiPlannedActions.length === 0) {
console.warn('MCTS 未能計算出動作,使用防呆預設');
return;
}
```
**替換為:**
```javascript
if (!aiPlannedActions || aiPlannedActions.length === 0) {
console.warn('MCTS 未能計算出動作,嘗試復原狀態');
resetAIState(); // 【修改2】解除 AI 思考狀態及畫面操作鎖定
// 如果不在回合起點,嘗試執行一次行走撤銷
if (walkCount > 1 || (markers.length > 0 && markers[0].position.y === 6)) {
setTimeout(() => {
walkUndo();
}, 300);
}
return;
}
```
---
### 三、 增強 AI 強度按鈕的屬性連動功能
需要將時間強度與 `SURE_KILL_DEPTH` 綁定,在 HTML 增加資料屬性,並在讀取與點擊事件中更新。
#### 1. 修改 HTML 結構中的 `custom-select-container`
請替換 `<div class="custom-select-container">` 與其內部的按鈕和選項:
```html
<div class="custom-select-container">
<button id="ai-strength-trigger" class="hist-btn custom-select-trigger" title="AI 強度" data-value="3600" data-depth="5">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
<path
d="M12,2 A10,10 0 1,0 22,12 A10,10 0 0,0 12,2 Z M15,14 H9 V10 H15 Z M12,6 A2,2 0 1,1 10,8 A2,2 0 0,1 12,6 Z"
/>
</svg>
</button>
<div id="ai-strength-options" class="custom-select-options">
<div class="csinger-option strength-option" data-value="1200" data-depth="3">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
<path
d="M12,2 A10,10 0 1,0 22,12 A10,10 0 0,0 12,2 Z M14,14 H10 V12 H14 Z M12,8 A1.5,1.5 0 1,1 10.5,9.5 A1.5,1.5 0 0,1 12,8 Z"
/>
</svg>
簡單 (1.2s)
</div>
<div class="csinger-option strength-option" data-value="3600" data-depth="5">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
<path
d="M12,2 A10,10 0 1,0 22,12 A10,10 0 0,0 12,2 Z M15,14 H9 V10 H15 Z M12,6 A2,2 0 1,1 10,8 A2,2 0 0,1 12,6 Z"
/>
</svg>
困難 (3.6s)
</div>
<div class="csinger-option strength-option" data-value="7200" data-depth="7">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
<path
d="M12,2 A10,10 0 1,0 22,12 A10,10 0 0,0 12,2 Z M16,15 H8 V9 H16 Z M12,4 A2.5,2.5 0 1,1 9.5,6.5 A2.5,2.5 0 0,1 12,4 Z"
/>
</svg>
專家 (7.2s)
</div>
</div>
</div>
```
#### 2. 初始化 `SURE_KILL_DEPTH` 變數
在 JavaScript 區塊中尋找 `let SURE_KILL_DEPTH = 3;`,將其預設值修改為 5 以配合預設的困難模式:
```javascript
// =========================================================
// MCTS 核心演算法 (Async 入口)
// =========================================================
let SURE_KILL_DEPTH = 5; // 【修改3】必勝計算深度預設改為 5
```
#### 3. 修改按鈕事件監聽器
在主迴圈事件綁定的末尾處(`document.querySelectorAll('.strength-option')` 的事件),加入深度值的讀取與賦值:
```javascript
// 初始化頁面時讀取預設設定
if (aiStrengthTrigger) {
aiStrength = parseInt(aiStrengthTrigger.getAttribute('data-value') || '3600');
SURE_KILL_DEPTH = parseInt(aiStrengthTrigger.getAttribute('data-depth') || '5');
}
document.querySelectorAll('.strength-option').forEach(opt => {
opt.addEventListener('click', () => {
aiStrength = parseInt(opt.getAttribute('data-value'));
SURE_KILL_DEPTH = parseInt(opt.getAttribute('data-depth')); // 【修改4】同步必殺深度
aiStrengthTrigger.innerHTML = opt.querySelector('svg').outerHTML;
// 同步更新 Trigger 的屬性
aiStrengthTrigger.setAttribute('data-value', aiStrength);
aiStrengthTrigger.setAttribute('data-depth', SURE_KILL_DEPTH);
aiStrengthOptions.classList.remove('open');
});
});
```代码: 全选
改進:
一,回合後退和前進的問題:
當行動階段,雙方都已經進行了幾個回合之後,點擊「回合後退」和「回合前進」,會出現幾個錯誤。
一是當回到布局階段的最後一步時,藍方的立方體已放置但位置不對。
二是當回到布局階段的某一回合時,只要點擊「回合前進」,則會立即回到行動階段的開始,這是不對的,應能自由在布局階段來回進退。
三是當從布局階段回到行動階段的某個回合時,發現「行動標示」放置的地方是錯誤的,比如有圓柱體阻擋不應能穿過去。
二,手機端適配問題
手機端的div#history-controls確實是把寬度調整至95%,但是裡面的按鈕並不能完全顯示。
在保持每個按鈕的高寬比為1:1的前提下,縮小按鈕,以使所有的按鈕都能全部顯示。為中間的「<div style="width: 2px; background: #7f8c8d; margin: 0 5px"></div>」增加類名,並在手機端的時候隱藏。
需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。代码: 全选
沒問題!我已經幫你找出這兩個主要問題的癥結點了。
這幾個 Bug 的核心原因在於:
1. **撤銷/重做邏輯缺陷:** `turnRedo` 在搜尋目標時強制跳過了「佈局階段(`gameplayActive = false`)」的所有步驟;而 `restoreSnapshot` 在還原「行動階段」的快照時,遺漏了更新 `cylindersData`(導致穿透圓柱體),也沒有在佈局階段正確重設立方體的位置。
2. **手機版 UI 擁擠:** 原本按鈕的內邊距(Padding)在小螢幕上佔據太多空間,且中間的分隔線壓縮了按鈕,導致無法維持 1:1 的完整顯示。
以下是針對這兩個問題,具體需要替換的代碼片段(請直接尋找對應的區塊並覆蓋):
### 一、 手機端 UI 與分隔線適配修改
**1. CSS 樣式替換:**
請找到 `<style>` 標籤中原本的 `.ai-toggle svg` 與 `@media (max-width: 768px)` 區塊,將其替換為以下代碼,加入新的 `.divider` 類別並優化手機端按鈕:
```css
.ai-toggle svg {
pointer-events: none;
}
/* 新增分隔線類別 */
.divider {
width: 2px;
background: #7f8c8d;
margin: 0 5px;
}
@media (max-width: 768px) {
#history-controls {
width: 100%;
justify-content: center;
gap: 4px; /* 縮小間距 */
bottom: 10px;
flex-wrap: nowrap; /* 強制不換行 */
}
.hist-btn {
padding: 0;
/* 強制 1:1 比例並縮小 */
width: 36px;
height: 36px;
}
.hist-btn svg {
width: 20px;
height: 20px;
}
.divider {
display: none; /* 手機端隱藏分隔線以節省空間 */
}
#message {
font-size: 16px;
top: 10px;
}
#btn-start {
padding: 15px 30px;
font-size: 20px;
}
}
```
**2. HTML 按鈕分隔線替換:**
請在 HTML 結構中找到兩處 `#history-controls` 裡面的分隔線:
`<div style="width: 2px; background: #7f8c8d; margin: 0 5px"></div>`
將它們全部替換為:
```html
<div class="divider"></div>
```
---
### 二、 歷史記錄進退場邏輯修改
**1. `restoreSnapshot` 的還原修復:**
請找到 `function restoreSnapshot(snap)` 中「`// ② 總是同步圓柱`」與「`// 以下為原本【對戰階段】的還原邏輯`」之間的程式碼,替換成以下修復邏輯(確保 `cylindersData` 無論哪一階段都還原,並修正佈局期的立方體位置):
```javascript
// ② 總是同步圓柱 (cylinderMeshes)
cylinderMeshes.forEach(mesh => {
if (!snap.cylinderMeshesRefs.includes(mesh)) scene.remove(mesh);
});
snap.cylinderMeshesRefs.forEach(mesh => {
if (!cylinderMeshes.includes(mesh)) scene.add(mesh);
fixOpacity(mesh);
});
cylinderMeshes = [...snap.cylinderMeshesRefs];
// 【修復:不論是開局還是對戰階段,都必須還原這些變數,確保回到行動階段時有正確的障礙物資料】
currentPhaseFn = snap.currentPhaseFn;
blueCornerType = snap.blueCornerType;
orangeCornerType = snap.orangeCornerType;
placedEdges = { ...snap.placedEdges };
placedCorners = { ...snap.placedCorners };
edgeTurn = snap.edgeTurn;
cylindersData = JSON.parse(JSON.stringify(snap.cylindersData));
// 開局階段:利用 3D 參照比對,完美支援開局的進退
if (!gameplayActive) {
// ③ 同步開局可能放置的立方體 (cubes)
cubes.forEach(mesh => {
if (!snap.openingCubesRefs.includes(mesh)) scene.remove(mesh);
});
snap.openingCubesRefs.forEach(mesh => {
if (!cubes.includes(mesh)) scene.add(mesh);
fixOpacity(mesh);
});
cubes = [...snap.openingCubesRefs];
// 【修復:修正回到開局階段最後一步時立方體位置錯亂的問題】
cubes.forEach(cube => {
let sCube = snap.cubes.find(sc => sc.color === cube.userData.color);
if (sCube) {
cube.userData.col = sCube.col;
cube.userData.row = sCube.row;
const pos = getCellWorldPos(sCube.col, sCube.row);
cube.position.set(pos.x, itemY, pos.z);
cube.quaternion.copy(sCube.quat);
cube.userData.moving = false;
cube.userData.animating = false;
}
});
// 重新觸發當前步驟的點擊提示圈
if (currentPhaseFn) currentPhaseFn();
return; // 開局階段處理完畢,直接返回
}
// 以下為原本【對戰階段】的還原邏輯(保持不變)...
```
**2. `turnUndo` 與 `turnRedo` 階段分離處理:**
請找到 `function turnUndo()` 與 `function turnRedo()` 兩個函式,替換為以下邏輯(如果在佈局階段點擊,將自動降級為執行「單步進退」,允許自由來回):
```javascript
function turnUndo() {
// 【修復:在佈局階段,回合後退等同於單步撤銷】
if (!gameplayActive) {
if (historyUndoStack.length === 0) return;
historyRedoStack.push(saveSnapshot());
restoreSnapshot(historyUndoStack.pop());
updateUndoRedoButtons();
return;
}
let targetIdx = -1;
for (let i = historyUndoStack.length - 1; i >= 0; i--) {
let snap = historyUndoStack[i];
if (
!snap.gameplayActive ||
(snap.currentPlayer !== currentPlayer && snap.walkCount === 1 && snap.subPhase === 'walk')
) {
targetIdx = i;
break;
}
}
if (targetIdx === -1) return;
historyRedoStack.push(saveSnapshot());
while (historyUndoStack.length > targetIdx + 1) {
historyRedoStack.push(historyUndoStack.pop());
}
restoreSnapshot(historyUndoStack.pop());
updateUndoRedoButtons();
}
function turnRedo() {
// 【修復:在佈局階段,回合前進等同於單步重做,自由來回進退】
if (!gameplayActive) {
if (historyRedoStack.length === 0) return;
historyUndoStack.push(saveSnapshot());
restoreSnapshot(historyRedoStack.pop());
updateUndoRedoButtons();
return;
}
let targetIdx = -1;
// 尋找下一個回合的起點
for (let i = historyRedoStack.length - 1; i >= 0; i--) {
let snap = historyRedoStack[i];
if (
snap.gameplayActive &&
(!gameplayActive || snap.currentPlayer !== currentPlayer) &&
snap.walkCount === 1 &&
snap.subPhase === 'walk'
) {
targetIdx = i;
break;
}
}
if (targetIdx === -1) {
return;
}
historyUndoStack.push(saveSnapshot());
while (historyRedoStack.length > targetIdx + 1) {
historyUndoStack.push(historyRedoStack.pop());
}
restoreSnapshot(historyRedoStack.pop());
updateUndoRedoButtons();
}
```正浏览此版面之用户: 没有注册用户 和 1 访客