分享和讨论Javascript相关的话题
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5943
注册时间: 2022年 11月 18日 17:36
联系:

Re: 「我為歌狂」遊戲製作

帖子 ejsoon »

代码: 全选

<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 preview
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5943
注册时间: 2022年 11月 18日 17:36
联系:

Re: 「我為歌狂」遊戲製作

帖子 ejsoon »

改進:
一,回合前進的問題
回合前進在行動階段是正確的,但如果回退到開局階段,之後再點擊回合前進,則會發生「遊戲板不能正確放置、立方體無故出現」等問題,查明原因。

二,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屬性並更改變量值。

需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。

代码: 全选

改進:
一,回合前進的問題
回合前進在行動階段是正確的,但如果回退到開局階段,之後再點擊回合前進,則會發生「遊戲板不能正確放置、立方體無故出現」等問題,查明原因。

二,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屬性並更改變量值。

需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。
附件
csinger2433.html.7z
(20.83 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5943
注册时间: 2022年 11月 18日 17:36
联系:

Re: 「我為歌狂」遊戲製作

帖子 ejsoon »

gemini 3.1 pro還沒有睡:

代码: 全选

這三個問題分別涉及到狀態同步機制、防呆還原邏輯以及參數綁定。以下為需要修改及替換的代碼片段:

### 一、 修復「回合前進」在開局階段引發的遊戲板與立方體顯示異常

問題在於從「對戰階段」跳躍回退/前進到「開局階段」時,視圖沒有對遊戲板和圓柱體進行同步與透明度還原。

請找到 `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');
        });
      });

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5943
注册时间: 2022年 11月 18日 17:36
联系:

Re: 「我為歌狂」遊戲製作

帖子 ejsoon »

改進:
一,回合後退和前進的問題:
當行動階段,雙方都已經進行了幾個回合之後,點擊「回合後退」和「回合前進」,會出現幾個錯誤。

一是當回到布局階段的最後一步時,藍方的立方體已放置但位置不對。

二是當回到布局階段的某一回合時,只要點擊「回合前進」,則會立即回到行動階段的開始,這是不對的,應能自由在布局階段來回進退。

三是當從布局階段回到行動階段的某個回合時,發現「行動標示」放置的地方是錯誤的,比如有圓柱體阻擋不應能穿過去。

二,手機端適配問題

手機端的div#history-controls確實是把寬度調整至95%,但是裡面的按鈕並不能完全顯示。

在保持每個按鈕的高寬比為1:1的前提下,縮小按鈕,以使所有的按鈕都能全部顯示。為中間的「<div style="width: 2px; background: #7f8c8d; margin: 0 5px"></div>」增加類名,並在手機端的時候隱藏。

需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。

代码: 全选

改進:
一,回合後退和前進的問題:
當行動階段,雙方都已經進行了幾個回合之後,點擊「回合後退」和「回合前進」,會出現幾個錯誤。

一是當回到布局階段的最後一步時,藍方的立方體已放置但位置不對。

二是當回到布局階段的某一回合時,只要點擊「回合前進」,則會立即回到行動階段的開始,這是不對的,應能自由在布局階段來回進退。

三是當從布局階段回到行動階段的某個回合時,發現「行動標示」放置的地方是錯誤的,比如有圓柱體阻擋不應能穿過去。

二,手機端適配問題

手機端的div#history-controls確實是把寬度調整至95%,但是裡面的按鈕並不能完全顯示。

在保持每個按鈕的高寬比為1:1的前提下,縮小按鈕,以使所有的按鈕都能全部顯示。為中間的「<div style="width: 2px; background: #7f8c8d; margin: 0 5px"></div>」增加類名,並在手機端的時候隱藏。

需要明確給出所有要修改的地方和要替換的代碼,不用給出全部代碼。
附件
csinger2434.html.7z
(21.1 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5943
注册时间: 2022年 11月 18日 17:36
联系:

Re: 「我為歌狂」遊戲製作

帖子 ejsoon »

gemini 3.1 pro將睡到20:52

代码: 全选

沒問題!我已經幫你找出這兩個主要問題的癥結點了。

這幾個 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();
      }

```
附件
csinger2435.html.7z
(21.33 KiB) 已下载 2 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: 没有注册用户 和 1 访客