分享和讨论Javascript相关的话题
回复
头像
ejsoon
一枝独秀
帖子: 6246
注册时间: 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
一枝独秀
帖子: 6246
注册时间: 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) 查看 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: Bing [Bot] 和 3 访客