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

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

gemini 3.1 pro

代码: 全选

這兩個是非常專業且切中要害的優化方向。針對您的需求,我已經為您準備了對應的代碼修改。

關於第二點(`rolloutPolicy`),我仔細檢查了現有的代碼:目前的條件是 `36 - movesPlayed <= mctsHeuristicN`。因為在 Rollout 的 `while(true)` 迴圈中,每模擬一手棋,`workPieces` 就會減少,導致 `movesPlayed` 增加,因此 `36 - movesPlayed`(剩餘手數)會不斷遞減。**這意味著只要進入了倒數第 $n$ 手,後續的每一手都會自動符合這個條件,現有邏輯確實已經涵蓋了「從第 $n$ 手直到結束」的所有步驟。**

**但是,這段邏輯中有一個隱藏的效能與邏輯盲區:**
目前的代碼寫法是 `let checkCount = Math.min(moves.length, 6);` 然後直接跑 `for (let j = 0; j < checkCount; j++)`。因為 `getValidMoves` 產生的合法著法陣列是**固定順序(由左上到右下)**,這會導致 AI 在局末預判時,**永遠只檢查棋盤左上角的前 6 個格子**,完全忽略了盤面其他地方的得分或阻擋機會。

我們需要加入「局部洗牌(Partial Shuffle)」來確保隨機抽樣整個盤面,同時完成您要求的第一點「探索完畢提前中止」。

以下是具體需要修改與替換的代碼段落:

### 一、 新增 AI 探索完畢提前中止搜尋

**1. 修改 `MCTSNode` 的構造函數**
在 `class MCTSNode { constructor(...) { ... } }` 中,大約 1493 行,加入 `isFullyExplored` 屬性。

```javascript
// 替換這段
if (this.unexpanded.length === 0) {
  this.isTerminal = true;
  let scores = state.calculateScores();
  this.terminalScore = scores.p1Score - scores.p2Score;
} else {
  this.isTerminal = false;
}

// 改為:
if (this.unexpanded.length === 0) {
  this.isTerminal = true;
  this.isFullyExplored = true; // [新增] 終局節點視為已完全探索
  let scores = state.calculateScores();
  this.terminalScore = scores.p1Score - scores.p2Score;
} else {
  this.isTerminal = false;
  this.isFullyExplored = false; // [新增] 
}

```

**2. 修改 `runMCTSBatch` 中的 Backpropagation 階段**
在 `runMCTSBatch` 函數底部(大約 1630 行),加入向上回溯時判斷節點是否窮盡的邏輯。

```javascript
// 替換這段
// 4. Backpropagation
let scaledAdvantage = advantage / 20.0;
let curr = node;
while (curr !== null) {
  curr.visits++;
  if (curr.player === 1) {
    curr.wins += -scaledAdvantage;
  } else {
    curr.wins += scaledAdvantage;
  }
  curr = curr.parent;
}

// 改為:
// 4. Backpropagation
let scaledAdvantage = advantage / 20.0;
let curr = node;
while (curr !== null) {
  curr.visits++;
  if (curr.player === 1) {
    curr.wins += -scaledAdvantage;
  } else {
    curr.wins += scaledAdvantage;
  }
  
  // [新增] 檢查該節點是否已經被完全探索 (無未展開著法,且所有子節點皆已完全探索)
  if (curr.unexpanded.length === 0 && curr.children.length > 0) {
    let allExplored = true;
    for (let i = 0; i < curr.children.length; i++) {
      if (!curr.children[i].isFullyExplored) {
        allExplored = false;
        break;
      }
    }
    if (allExplored) curr.isFullyExplored = true;
  }
  
  curr = curr.parent;
}

```

**3. 在控制迴圈中攔截並提前跳出**
分別在 `triggerAITurn()`(大約 1664 行)和 `runAISearch()`(大約 1762 行)的 `while` 迴圈中修改跳出條件。

在 `triggerAITurn` 中:

```javascript
// 替換這段
let ran = runMCTSBatch(root, rootState, workPieces, 20);
iterations += ran;
if (ran === 0) break; // 若樹已走盡則跳出

// 改為:
let ran = runMCTSBatch(root, rootState, workPieces, 20);
iterations += ran;
if (ran === 0 || root.isFullyExplored) break; // [修改] 若根節點已被完全探索,立即中斷思考

```

在 `runAISearch` 中:

```javascript
// 替換這段
let ran = runMCTSBatch(root, rootState, workPieces, 30);
iterations += ran;
if (ran === 0) break;

// 改為:
let ran = runMCTSBatch(root, rootState, workPieces, 30);
iterations += ran;
if (ran === 0 || root.isFullyExplored) break; // [修改] 若根節點已被完全探索,立即中斷分析

```

---

### 二、 修復並完善預判策略 (Rollout Policy) 的抽樣盲區

在 `runMCTSBatch` 內(大約 1568 行),在進行「條件一」與「條件二」的檢查前,我們利用 Fisher-Yates 洗牌算法進行局部洗牌,確保 AI 隨機抽取盤面上任何地方的合法著法進行預判。

```javascript
// 替換這段
let myScoringMoves = [];
// 為了極致效能,每次最多只檢查 6 個隨機著法,避免嚴重拖慢搜尋
let checkCount = Math.min(moves.length, 6);

// 條件一:找出能使我方得分的步
for (let j = 0; j < checkCount; j++) {
  let move = moves[j];
// ... (略過中間) ...

} else {
  // 條件二:找尋能阻擋對方得分的步
  let oppMoves = simState.getValidMoves(oppPlayer, workPieces);
  let blockingMoves = [];
  let oppCheckCount = Math.min(oppMoves.length, 6);
  for (let j = 0; j < oppCheckCount; j++) {
    let move = oppMoves[j];


// 改為:
let myScoringMoves = [];
let checkCount = Math.min(moves.length, 6);

// [新增] 局部洗牌:隨機抽出前 checkCount 個著法,避免永遠只檢查左上角的死角
for (let i = 0; i < checkCount; i++) {
  let r = i + Math.floor(Math.random() * (moves.length - i));
  let temp = moves[i];
  moves[i] = moves[r];
  moves[r] = temp;
}

// 條件一:找出能使我方得分的步
for (let j = 0; j < checkCount; j++) {
  let move = moves[j];
// ... (保留原本的邏輯) ...

} else {
  // 條件二:找尋能阻擋對方得分的步
  let oppMoves = simState.getValidMoves(oppPlayer, workPieces);
  let blockingMoves = [];
  let oppCheckCount = Math.min(oppMoves.length, 6);
  
  // [新增] 對對手的著法也進行局部洗牌抽樣
  for (let i = 0; i < oppCheckCount; i++) {
    let r = i + Math.floor(Math.random() * (oppMoves.length - i));
    let temp = oppMoves[i];
    oppMoves[i] = oppMoves[r];
    oppMoves[r] = temp;
  }
  
  for (let j = 0; j < oppCheckCount; j++) {
    let move = oppMoves[j];

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

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

這盤棋正式宣告:三菱棋AI碾壓人類!我謹代表所有地球人,向AI俯首稱臣!

代码: 全选

[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,2),(0,1)] [(-1,3),(0,3)] [(0,-3),(-1,-3)] [(1,-2),(1,-1)] [(-1,1),(-1,0)] [(-2,0),(-2,1)] [(-1,-1),(-1,-2)] [(-2,2),(-1,2)] [(1,2),(1,1)] [(-2,-2),(-2,-1)] [(2,-3),(1,-3)] [(2,-2),(3,-2)] [(3,-1),(3,0)] [(1,3),(2,3)] [(2,0),(2,-1)] [(0,4),(1,4)] [(0,-4),(1,-4)] [(2,1),(2,2)] [(3,1),(3,2)] [(1,-6),(1,-5)] [(4,-3),(3,-3)] [(-1,-5),(-1,-4)] [(4,1),(4,0)] [(4,2),(5,2)] [(3,-4),(2,-4)] [(3,-6),(3,-5)] [(1,5),(1,6)] [(4,-5),(4,-4)] [(-1,5),(-1,4)] [(2,-6),(2,-5)] [(0,5),(0,6)] [(0,-6),(0,-5)] [(4,-2),(4,-1)] [(5,0),(5,1)]
附件
Screenshot from 2026-07-08 01-10-26.png
Screenshot from 2026-07-08 01-10-26.png (199.12 KiB) 查看 29 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6253
注册时间: 2022年 11月 18日 17:36
联系:

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

修復和改進:
在runMCTSBatch中,目前的代碼寫法是 `let checkCount = Math.min(moves.length, 6);` 然後直接跑 `for (let j = 0; j < checkCount; j++)`。因為 `getValidMoves` 產生的合法著法陣列是**固定順序(由左上到右下)**,這會導致 AI 在局末預判時,**永遠只檢查棋盤左上角的前 6 個格子**,完全忽略了盤面其他地方的得分或阻擋機會。

我們需要加入「局部洗牌(Partial Shuffle)」來確保隨機抽樣整個盤面。

後來你給我的更改建議是:

```
let myScoringMoves = [];
let checkCount = Math.min(moves.length, 6);

// [新增] 局部洗牌:隨機抽出前 checkCount 個著法,避免永遠只檢查左上角的死角
for (let i = 0; i < checkCount; i++) {
let r = i + Math.floor(Math.random() * (moves.length - i));
let temp = moves;
moves = moves[r];
moves[r] = temp;
}

// 條件一:找出能使我方得分的步
for (let j = 0; j < checkCount; j++) {
let move = moves[j];
// ... (保留原本的邏輯) ...

} else {
// 條件二:找尋能阻擋對方得分的步
let oppMoves = simState.getValidMoves(oppPlayer, workPieces);
let blockingMoves = [];
let oppCheckCount = Math.min(oppMoves.length, 6);

// [新增] 對對手的著法也進行局部洗牌抽樣
for (let i = 0; i < oppCheckCount; i++) {
let r = i + Math.floor(Math.random() * (oppMoves.length - i));
let temp = oppMoves;
oppMoves = oppMoves[r];
oppMoves[r] = temp;
}

for (let j = 0; j < oppCheckCount; j++) {
let move = oppMoves[j];

```

但是我感覺for循環不是很高效。你要把它改成更高效的打亂方式。

同時把這個「6」寫成變量,並把它放到AI設置窗口中,各等級的值為「簡單6,困難12,專家24,自定48」。

注意翻譯也要同步更新。

需要明確給出所要修改的地方和所要替換的代碼。

代码: 全选

修復和改進:
在runMCTSBatch中,目前的代碼寫法是 `let checkCount = Math.min(moves.length, 6);` 然後直接跑 `for (let j = 0; j < checkCount; j++)`。因為 `getValidMoves` 產生的合法著法陣列是**固定順序(由左上到右下)**,這會導致 AI 在局末預判時,**永遠只檢查棋盤左上角的前 6 個格子**,完全忽略了盤面其他地方的得分或阻擋機會。

我們需要加入「局部洗牌(Partial Shuffle)」來確保隨機抽樣整個盤面。

後來你給我的更改建議是:

```
let myScoringMoves = [];
let checkCount = Math.min(moves.length, 6);

// [新增] 局部洗牌:隨機抽出前 checkCount 個著法,避免永遠只檢查左上角的死角
for (let i = 0; i < checkCount; i++) {
  let r = i + Math.floor(Math.random() * (moves.length - i));
  let temp = moves[i];
  moves[i] = moves[r];
  moves[r] = temp;
}

// 條件一:找出能使我方得分的步
for (let j = 0; j < checkCount; j++) {
  let move = moves[j];
// ... (保留原本的邏輯) ...

} else {
  // 條件二:找尋能阻擋對方得分的步
  let oppMoves = simState.getValidMoves(oppPlayer, workPieces);
  let blockingMoves = [];
  let oppCheckCount = Math.min(oppMoves.length, 6);
  
  // [新增] 對對手的著法也進行局部洗牌抽樣
  for (let i = 0; i < oppCheckCount; i++) {
    let r = i + Math.floor(Math.random() * (oppMoves.length - i));
    let temp = oppMoves[i];
    oppMoves[i] = oppMoves[r];
    oppMoves[r] = temp;
  }
  
  for (let j = 0; j < oppCheckCount; j++) {
    let move = oppMoves[j];

```

但是我感覺for循環不是很高效。你要把它改成更高效的打亂方式。

同時把這個「6」寫成變量,並把它放到AI設置窗口中,各等級的值為「簡單6,困難12,專家24,自定48」。

注意翻譯也要同步更新。

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

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

貪食蛇AI大戰5.html

用claude改的。
附件
貪食蛇AI大戰5.html.7z
(11.45 KiB) 已下载 1 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
帖子: 6253
注册时间: 2022年 11月 18日 17:36
联系:

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

gemini 3.1 pro 跟我飆英文:

代码: 全选

I completely understand wanting to ditch a `for` loop for something that feels more elegant or modern. However, I have to be completely candid with you: **the partial `for` loop you provided (a partial Fisher-Yates shuffle) is actually the absolute most efficient way to do this in JavaScript.** Other methods like `moves.sort(() => 0.5 - Math.random()).slice(0, checkCount)` might look cleaner, but they force the engine to shuffle the *entire* array (which takes $O(N \log N)$ time) just to pick a few items. Your `for` loop only runs `checkCount` times ($O(k)$ time), which is exactly what a high-performance MCTS engine needs!

That said, we *can* optimize it slightly for the MCTS loop by using a bitwise operator (`| 0`) instead of `Math.floor` to shave off some floating-point math overhead.

Here are the exact modifications to make the check count configurable in the UI and apply the highly optimized partial shuffle.

### 1. 更新 AI 設定預設值與全域變數

在 `<script>` 區塊的開頭,找到 `aiConfigs` 與全域變數宣告的地方,加入 `heuristicChecks` 參數:

**找到這段:**

```javascript
      const aiConfigs = {
        simple: {
          time: 12000,
          c: 2.4,
          heuristicN: 4,
          icon: ' <rect x="5" y="7" width="14" height="11" rx="3" fill="none" stroke="currentColor" stroke-width="2" /> <line x1="12" y1="2" x2="12" y2="6" stroke="currentColor" stroke-width="2" /> <circle cx="9" cy="12" r="1.2" fill="currentColor" /> <circle cx="15" cy="12" r="1.2" fill="currentColor" /> '
        },
        hard: {
          time: 24000,
          c: 3.6,
          heuristicN: 8,
          icon: ' <rect x="2" y="9" width="2" height="6" rx="1" fill="currentColor" /> <rect x="20" y="9" width="2" height="6" rx="1" fill="currentColor" /> <rect x="5" y="6" width="14" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="2" /> <circle cx="9" cy="11" r="1.2" fill="currentColor" /> <circle cx="15" cy="11" r="1.2" fill="currentColor" /> <rect x="9" y="14" width="6" height="2" fill="currentColor" /> '
        },
        expert: {
          time: 36000,
          c: 4.8,
          heuristicN: 12,
          icon: '<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" fill="currentColor"/>'
        },
        custom: {
          time: 48000,
          c: 6.0,
          heuristicN: 16,
          icon: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="currentColor"/>'
        },
        human: {
          icon: '<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>'
        }
      };
      let isAiThinking = false;
      let abortAiSearch = false;
      let mctsExplorationConstant = 3.6;
      let mctsHeuristicN = 8;

```

**替換為:**

```javascript
      const aiConfigs = {
        simple: {
          time: 12000,
          c: 2.4,
          heuristicN: 4,
          heuristicChecks: 6,
          icon: ' <rect x="5" y="7" width="14" height="11" rx="3" fill="none" stroke="currentColor" stroke-width="2" /> <line x1="12" y1="2" x2="12" y2="6" stroke="currentColor" stroke-width="2" /> <circle cx="9" cy="12" r="1.2" fill="currentColor" /> <circle cx="15" cy="12" r="1.2" fill="currentColor" /> '
        },
        hard: {
          time: 24000,
          c: 3.6,
          heuristicN: 8,
          heuristicChecks: 12,
          icon: ' <rect x="2" y="9" width="2" height="6" rx="1" fill="currentColor" /> <rect x="20" y="9" width="2" height="6" rx="1" fill="currentColor" /> <rect x="5" y="6" width="14" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="2" /> <circle cx="9" cy="11" r="1.2" fill="currentColor" /> <circle cx="15" cy="11" r="1.2" fill="currentColor" /> <rect x="9" y="14" width="6" height="2" fill="currentColor" /> '
        },
        expert: {
          time: 36000,
          c: 4.8,
          heuristicN: 12,
          heuristicChecks: 24,
          icon: '<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" fill="currentColor"/>'
        },
        custom: {
          time: 48000,
          c: 6.0,
          heuristicN: 16,
          heuristicChecks: 48,
          icon: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="currentColor"/>'
        },
        human: {
          icon: '<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>'
        }
      };
      let isAiThinking = false;
      let abortAiSearch = false;
      let mctsExplorationConstant = 3.6;
      let mctsHeuristicN = 8;
      let mctsHeuristicChecks = 12;

```

---

### 2. 更新多語言字典 (I18N) 與 UI 切換邏輯

在 `const I18N = { ... }` 內,分別為中文和英文加上翻譯:

**在 `zh` 內加入:**

```javascript
          heuristicChecks: '預判檢查數 (局部洗牌)',

```

**在 `en` 內加入:**

```javascript
          heuristicChecks: 'Heuristic Checks (Shuffle)',

```

接著在 `toggleLanguage()` 函數中,加入更新 UI 標籤的語法:

**找到:**

```javascript
        if (document.getElementById('label-heuristic-n'))
          document.getElementById('label-heuristic-n').innerText = getText('heuristicN');

```

**在下方加入:**

```javascript
        if (document.getElementById('label-heuristic-checks'))
          document.getElementById('label-heuristic-checks').innerText = getText('heuristicChecks');

```

---

### 3. 將控制項加入 HTML 介面

在 HTML 的 `#ai-modal` 中,找到 `heuristic-n` 的 `setting-row` 區塊:

**在以下這段的 *下方* 加入新代碼:**

```html
        <div class="setting-row">
          <label id="label-heuristic-n">智能預判步數 (局末介入)</label>
          <div class="num-ctrl">
            ...
          </div>
        </div>

```

**加入:**

```html
        <div class="setting-row">
          <label id="label-heuristic-checks">預判檢查數 (局部洗牌)</label>
          <div class="num-ctrl">
            <button class="num-btn" onclick="adjustGlobalParam('heuristic-checks', -1, 1, 100, 0)">-</button>
            <input
              type="number"
              id="heuristic-checks"
              class="num-input"
              value="12"
              step="1"
              onchange="updateGlobalParams()"
            />
            <button class="num-btn" onclick="adjustGlobalParam('heuristic-checks', 1, 1, 100, 0)">+</button>
          </div>
        </div>

```

---

### 4. 同步 UI 事件與全域變數

更新與 AI Tab 連動的三個函數 `selectAiTab`、`markCustom` 和 `updateGlobalParams`,讓它們能夠控制這個新變數:

**找到這三個函數並完全替換為:**

```javascript
      // === UI 控制:選擇 AI 強度標籤 ===
      function selectAiTab(preset) {
        document.querySelectorAll('.ai-tab').forEach(tab => {
          if (tab.dataset.preset === preset) tab.classList.add('active');
          else tab.classList.remove('active');
        });
        if (aiConfigs[preset]) {
          document.getElementById('ai-time').value = aiConfigs[preset].time / 1000;
          document.getElementById('mcts-c').value = aiConfigs[preset].c.toFixed(1);
          document.getElementById('heuristic-n').value = aiConfigs[preset].heuristicN;
          document.getElementById('heuristic-checks').value = aiConfigs[preset].heuristicChecks;
          updateGlobalParams();
        }
      }

      // === UI 控制:數值變更時自動切換至「自訂」標籤 ===
      function markCustom() {
        let t = parseFloat(document.getElementById('ai-time').value) * 1000;
        let c = parseFloat(document.getElementById('mcts-c').value);
        let n = parseInt(document.getElementById('heuristic-n').value, 10);
        let checks = parseInt(document.getElementById('heuristic-checks').value, 10);

        let matchedPreset = 'custom';
        for (let k of ['simple', 'hard', 'expert']) {
          if (aiConfigs[k].time === t && Math.abs(aiConfigs[k].c - c) < 0.01 && aiConfigs[k].heuristicN === n && aiConfigs[k].heuristicChecks === checks) {
            matchedPreset = k;
            break;
          }
        }
        document.querySelectorAll('.ai-tab').forEach(tab => {
          if (tab.dataset.preset === matchedPreset) tab.classList.add('active');
          else tab.classList.remove('active');
        });
        aiConfigs.custom.time = t;
        aiConfigs.custom.c = c;
        aiConfigs.custom.heuristicN = n;
        aiConfigs.custom.heuristicChecks = checks;
      }

      function updateGlobalParams() {
        mctsExplorationConstant = parseFloat(document.getElementById('mcts-c').value);
        mctsHeuristicN = parseInt(document.getElementById('heuristic-n').value, 10);
        mctsHeuristicChecks = parseInt(document.getElementById('heuristic-checks').value, 10);
        markCustom();
      }

```

---

### 5. 更新核心邏輯 (`runMCTSBatch`)

最後,我們將高效的位元運算局部洗牌(Bitwise Partial Fisher-Yates)放入模擬迴圈中,並接上剛才創建的 UI 變數 `mctsHeuristicChecks`。

**找到 `runMCTSBatch` 中關於啟發式探索的這段代碼:**

```javascript
              // 啟發式探索:在局末 n 手內介入
              if (36 - movesPlayed <= mctsHeuristicN) {
                let baseScores = simState.calculateScores();
                let myBase = simPlayer === 1 ? baseScores.p1Score : baseScores.p2Score;
                let oppPlayer = simPlayer === 1 ? 2 : 1;
                let oppBase = oppPlayer === 1 ? baseScores.p1Score : baseScores.p2Score;

                let myScoringMoves = [];
                // 為了極致效能,每次最多只檢查 6 個隨機著法,避免嚴重拖慢搜尋
                let checkCount = Math.min(moves.length, 6);

                // 條件一:找出能使我方得分的步
                for (let j = 0; j < checkCount; j++) {
                  let move = moves[j];
                  // ... 後續尋找阻擋步的邏輯 ...

```

**將整個 `if (36 - movesPlayed <= mctsHeuristicN) { ... }` 區塊完全替換為:**

```javascript
              // 啟發式探索:在局末 n 手內介入
              if (36 - movesPlayed <= mctsHeuristicN) {
                let baseScores = simState.calculateScores();
                let myBase = simPlayer === 1 ? baseScores.p1Score : baseScores.p2Score;
                let oppPlayer = simPlayer === 1 ? 2 : 1;
                let oppBase = oppPlayer === 1 ? baseScores.p1Score : baseScores.p2Score;

                let myScoringMoves = [];
                // 使用 UI 設定的預判檢查數
                let checkCount = moves.length < mctsHeuristicChecks ? moves.length : mctsHeuristicChecks;

                // [優化] 高效局部洗牌 (Partial Fisher-Yates),使用位元運算 (|0) 加速
                for (let i = 0; i < checkCount; i++) {
                  let r = i + (Math.random() * (moves.length - i) | 0);
                  let temp = moves[i];
                  moves[i] = moves[r];
                  moves[r] = temp;
                }

                // 條件一:找出能使我方得分的步
                for (let j = 0; j < checkCount; j++) {
                  let move = moves[j];
                  let simPid = (move >> 24) & 0xff;
                  let p1Idx = (move >> 12) & 0xfff;
                  let p2Idx = move & 0xfff;
                  let res = simState.tryPlacePiece(
                    simPid + 1,
                    simState._idxToCoord(p1Idx),
                    simState._idxToCoord(p2Idx),
                    false
                  );
                  if (res.success) {
                    let newScore = simPlayer === 1 ? res.scores.p1Score : res.scores.p2Score;
                    simState.undo();
                    if (newScore > myBase) myScoringMoves.push(move);
                  }
                }

                if (myScoringMoves.length > 0) {
                  m = myScoringMoves[Math.random() * myScoringMoves.length | 0];
                } else {
                  // 條件二:找尋能阻擋對方得分的步
                  let oppMoves = simState.getValidMoves(oppPlayer, workPieces);
                  let blockingMoves = [];
                  let oppCheckCount = oppMoves.length < mctsHeuristicChecks ? oppMoves.length : mctsHeuristicChecks;

                  // [優化] 對手步也進行高效局部洗牌
                  for (let i = 0; i < oppCheckCount; i++) {
                    let r = i + (Math.random() * (oppMoves.length - i) | 0);
                    let temp = oppMoves[i];
                    oppMoves[i] = oppMoves[r];
                    oppMoves[r] = temp;
                  }

                  for (let j = 0; j < oppCheckCount; j++) {
                    let move = oppMoves[j];
                    let simPid = (move >> 24) & 0xff;
                    let p1Idx = (move >> 12) & 0xfff;
                    let p2Idx = move & 0xfff;
                    let res = simState.tryPlacePiece(
                      simPid + 1,
                      simState._idxToCoord(p1Idx),
                      simState._idxToCoord(p2Idx),
                      false
                    );
                    if (res.success) {
                      let newScore = oppPlayer === 1 ? res.scores.p1Score : res.scores.p2Score;
                      simState.undo();
                      if (newScore > oppBase) {
                        for (let k = 0; k < checkCount; k++) { // 只需對照已洗牌過的 moves
                          let myP1 = (moves[k] >> 12) & 0xfff;
                          let myP2 = moves[k] & 0xfff;
                          if (myP1 === p1Idx || myP1 === p2Idx || myP2 === p1Idx || myP2 === p2Idx) {
                            blockingMoves.push(moves[k]);
                          }
                        }
                      }
                    }
                  }
                  if (blockingMoves.length > 0) {
                    m = blockingMoves[Math.random() * blockingMoves.length | 0];
                  } else {
                    // 退回從洗好的安全池子裡隨便選一個
                    m = moves[Math.random() * checkCount | 0];
                  }
                }
              }

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

Re: 準備為三菱棋做遊戲和MCTS搜尋

帖子 ejsoon »

我現在應該是沒法贏AI了,甚至逼和都很難。

又輸一局:
Screenshot from 2026-07-08 16-48-40.png
Screenshot from 2026-07-08 16-48-40.png (200.74 KiB) 查看 2 次

代码: 全选

[(0,0),(1,0)] [(0,-2),(0,-1)] [(0,1),(0,2)] [(1,-2),(1,-1)] [(0,-3),(-1,-3)] [(-1,3),(0,3)] [(-1,1),(-1,0)] [(-2,0),(-2,1)] [(1,2),(1,1)] [(-2,2),(-1,2)] [(2,-3),(1,-3)] [(2,-1),(2,0)] [(3,-1),(3,0)] [(1,3),(2,3)] [(0,-4),(1,-4)] [(0,4),(1,4)] [(3,-2),(2,-2)] [(3,1),(4,1)] [(1,5),(1,6)] [(0,6),(0,7)] [(-1,7),(-1,6)] [(-3,3),(-2,3)] [(2,5),(2,4)] [(0,-6),(0,-5)] [(0,5),(-1,5)] [(2,-5),(2,-4)] [(-1,-1),(-1,-2)] [(-2,-2),(-2,-1)] [(4,2),(4,3)] [(-1,-5),(-1,-4)] [(3,-4),(3,-5)] [(1,-6),(1,-5)] [(2,1),(2,2)] [(4,-1),(4,0)] [(3,3),(3,2)] [(2,-7),(2,-6)]
附件
trirhombus286.html.7z
(26.5 KiB) 已下载 1 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: ejsoon 和 10 访客