QuanAI只读快照

共享会话

🔧 修復仲裁邏輯錯誤

分享于 2026年8月16日 16:50
提问者

修復和更正 現程式對「仲裁」的理解有誤。一方在回合結束時,必須保證對方下回合至少有一組「共活」,即保證對方能有兩個棋子能夠落在本方的同一個棋子的兩個不同的邊上,否則對方就能「仲裁」成功,即使對方存在「單連」或「雙接」的下法。

更改現在的「仲裁」以及MCTS和minimax算法邏輯,只替換相關的地方,不要影響當前正確的流程。

回答要求:

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

trigolden_game262.htmltext/plain · 252 KB预览下载
QuanAI

以下修改只調整「仲裁成立條件」以及 MCTS/minimax 對仲裁終局的理解,不改動目前正常落子、單接、雙接、跨目標頂鑫例外與計分流程。

一、修正後的判定原則

一般兩子回合中:

  • 「共活」只能是兩個棋子:

    • 落在同一個對方棋子上;
    • 落在該棋子的兩條不同邊;
    • 兩子彼此相容;
    • 棋子庫存足夠。
  • 即使存在:

    • 單接;
    • 雙接;
    • 跨不同目標、依頂鑫例外可以實際落下的合法回合;

    只要不存在至少一組上述「共活」,當前方就能仲裁成功。

  • 搜尋中,某一方完成回合後,如果沒有給下一方留下共活,該局面必須立即視為「下一方仲裁獲勝」。

  • 最後只剩一子的特殊回合仍沿用目前正確流程:存在任一合法單子即可,不要求兩子共活。

  • 棋子已全部下完時是正常終局,不再進行仲裁判定。


二、新增「仲裁防禦/共活」專用生成函式

修改位置

找到:

js
function generateLegalTurns(state) {

保留這個函式不變。

在整個 generateLegalTurns(state) 函式結束後、下面這個函式之前:

js
function evaluateSearchState(state, rootPlayer, rootBaseDiff) {

插入以下程式碼:

js
      /*
       * 仲裁專用判定。
       *
       * 這裡與一般合法回合 generateLegalTurns 不同:
       *
       * 1. 一般兩子回合只承認「共活」:
       *    兩子必須落在同一個對方棋子的兩條不同邊上。
       *
       * 2. 單接、雙接或跨目標頂鑫例外雖然仍可作為正常落子,
       *    但不能用來阻止仲裁成功。
       *
       * 3. 若行動方只剩最後一子,沿用現有最後一子規則:
       *    存在任一合法單子即可。
       */
      function generateArbitrationDefenceTurns(state) {
        const required = getRequiredPiecesForState(state);

        /*
         * 最後只剩一子的特殊回合:
         * 仲裁防禦條件為至少存在一個合法單子。
         */
        if (required === 1) {
          if (state.boardPieces.length === 0) return [];

          return generateLegalSinglePlacements(state, state.mover).map(ghost => [ghost]);
        }

        const singles = generateLegalSinglePlacements(state, state.mover);

        if (singles.length < 2) return [];

        const moves = [];
        const seen = new Set();

        /*
         * 只組合同一 targetId 的兩個落點。
         * 不收錄任何跨目標單接/雙接/頂鑫例外。
         */
        const targetMap = new Map();

        for (const ghost of singles) {
          if (!targetMap.has(ghost.targetId)) {
            targetMap.set(ghost.targetId, []);
          }

          targetMap.get(ghost.targetId).push(ghost);
        }

        for (const targetGhosts of targetMap.values()) {
          for (let i = 0; i < targetGhosts.length; i++) {
            for (let j = i + 1; j < targetGhosts.length; j++) {
              const first = targetGhosts[i];
              const second = targetGhosts[j];

              if (!pairFitsInventory(state, first, second)) {
                continue;
              }

              const firstTargetEdge =
                first.targetEdge !== undefined ? first.targetEdge : first.edgeOnOpp;

              const secondTargetEdge =
                second.targetEdge !== undefined ? second.targetEdge : second.edgeOnOpp;

              /*
               * 共活要求落在同一個棋子的兩條不同邊。
               */
              if (firstTargetEdge === secondTargetEdge) {
                continue;
              }

              if (!twoGhostsCompatible(first, second)) {
                continue;
              }

              const move = [first, second];

              /*
               * 仍使用正式合法性引擎做最後驗證,
               * 避免仲裁判定繞過庫存或幾何規則。
               */
              const validation = validateTurnOnState(state, move);

              if (!validation.ok) {
                continue;
              }

              const signature = turnSignature(move);

              if (seen.has(signature)) {
                continue;
              }

              seen.add(signature);
              moves.push(move);
            }
          }
        }

        return moves;
      }

      /*
       * 判斷 state.mover 是否有阻止仲裁成立的下法。
       *
       * 已正常完局時不再判定仲裁,因此回傳 true。
       */
      function hasArbitrationDefence(state) {
        if (isSearchGameComplete(state)) {
          return true;
        }

        return generateArbitrationDefenceTurns(state).length > 0;
      }

這是本次修正的核心:把「一般合法回合」和「能阻止仲裁的共活」分離。


三、修改 minimax/MCTS 的仲裁終局評估函式

修改位置

找到原本的:

js
      function evaluateNoMoveTerminal(state, rootPlayer, rootBaseDiff) {
        /*
         * 棋子全部下完是正常終局,以分數判定。
         * 尚未完局但行動方沒有合法回合,行動方可仲裁獲勝。
         */
        if (isSearchGameComplete(state)) {
          return evaluateSearchState(state, rootPlayer, rootBaseDiff);
        }

        return state.mover === rootPlayer ? SEARCH_TERMINAL_VALUE : -SEARCH_TERMINAL_VALUE;
      }

整段替換為

js
      /*
       * 仲裁終局評估。
       *
       * 呼叫此函式代表:
       * - 棋局尚未正常結束;
       * - 輪到 state.mover;
       * - 但上一方沒有給 state.mover 留下至少一組共活。
       *
       * 因此 state.mover 可以仲裁成功並直接獲勝。
       */
      function evaluateArbitrationTerminal(state, rootPlayer, rootBaseDiff) {
        if (isSearchGameComplete(state)) {
          return evaluateSearchState(state, rootPlayer, rootBaseDiff);
        }

        return state.mover === rootPlayer
          ? SEARCH_TERMINAL_VALUE
          : -SEARCH_TERMINAL_VALUE;
      }

四、修改戰術 alpha-beta 的終局判定順序

仲裁判定必須早於 depth <= 0

否則剛好搜索到深度邊界時,即使上一手沒有留下共活,也只會被當成普通分數葉節點。

修改位置

在:

js
function tacticalAlphaBeta(state, depth, alpha, beta, rootPlayer, rootBaseDiff, deadline) {

找到原本:

js
        if (isSearchGameComplete(state) || depth <= 0) {
          return evaluateSearchState(state, rootPlayer, rootBaseDiff);
        }

        const legalMoves = generateLegalTurns(state);

        if (legalMoves.length === 0) {
          return evaluateNoMoveTerminal(state, rootPlayer, rootBaseDiff);
        }

替換為

js
        /*
         * 正常完局優先按分數判定。
         */
        if (isSearchGameComplete(state)) {
          return evaluateSearchState(state, rootPlayer, rootBaseDiff);
        }

        /*
         * 即使存在單接、雙接或跨目標合法下法,
         * 只要沒有共活,行動方即可仲裁獲勝。
         *
         * 此判定必須放在 depth <= 0 之前。
         */
        if (!hasArbitrationDefence(state)) {
          return evaluateArbitrationTerminal(state, rootPlayer, rootBaseDiff);
        }

        if (depth <= 0) {
          return evaluateSearchState(state, rootPlayer, rootBaseDiff);
        }

        const legalMoves = generateLegalTurns(state);

        /*
         * 理論上有仲裁防禦就一定至少有一個合法回合。
         * 保留此保險判定,避免異常資料造成空分支。
         */
        if (legalMoves.length === 0) {
          return evaluateArbitrationTerminal(state, rootPlayer, rootBaseDiff);
        }

五、修改 minimax 主 alpha-beta 的終局判定

修改位置

在:

js
async function runStateMinimax(...)

內部的:

js
function alphaBetaState(state, depth, alpha, beta) {

找到原本:

js
          if (isSearchGameComplete(state) || depth <= 0) {
            return evaluateSearchState(state, rootPlayer, rootBaseDiff);
          }

          let legalMoves = generateLegalTurns(state);

          if (legalMoves.length === 0) {
            return evaluateNoMoveTerminal(state, rootPlayer, rootBaseDiff);
          }

替換為

js
          /*
           * 棋子全部下完是正常終局。
           */
          if (isSearchGameComplete(state)) {
            return evaluateSearchState(state, rootPlayer, rootBaseDiff);
          }

          /*
           * 上一方若沒有留下至少一組共活,
           * 則目前行動方立即可以仲裁獲勝。
           *
           * 單接、雙接及跨目標合法下法不能阻止仲裁。
           * 必須在深度截斷之前檢查。
           */
          if (!hasArbitrationDefence(state)) {
            return evaluateArbitrationTerminal(state, rootPlayer, rootBaseDiff);
          }

          if (depth <= 0) {
            return evaluateSearchState(state, rootPlayer, rootBaseDiff);
          }

          let legalMoves = generateLegalTurns(state);

          if (legalMoves.length === 0) {
            return evaluateArbitrationTerminal(state, rootPlayer, rootBaseDiff);
          }

後面的:

js
legalMoves = orderMovesForSearch(state, legalMoves);

以及其餘 minimax 流程全部保留不變。


六、修改 MCTS 節點終局判定

修改位置

在:

js
async function runStateMCTS(...)

內部找到:

js
function ensureNodeMoves(node, isRoot = false) {

原本是:

js
        function ensureNodeMoves(node, isRoot = false) {
          if (node.orderedMoves !== null) return;

          if (isSearchGameComplete(node.state)) {
            node.orderedMoves = [];
            node.terminalValue = evaluateSearchState(node.state, rootPlayer, rootBaseDiff);
            return;
          }

          let moves = isRoot ? rootMoves.slice() : generateLegalTurns(node.state);

          if (moves.length === 0) {
            node.orderedMoves = [];
            node.terminalValue = evaluateNoMoveTerminal(node.state, rootPlayer, rootBaseDiff);
            return;
          }

          node.orderedMoves = orderMovesForSearch(node.state, moves, isRoot ? tacticalScores : null);
        }

整段替換為

js
        function ensureNodeMoves(node, isRoot = false) {
          if (node.orderedMoves !== null) return;

          /*
           * 棋子全部下完:正常按分數終局。
           */
          if (isSearchGameComplete(node.state)) {
            node.orderedMoves = [];
            node.terminalValue = evaluateSearchState(
              node.state,
              rootPlayer,
              rootBaseDiff
            );
            return;
          }

          /*
           * 仲裁終局:
           * 不論是否還存在單接、雙接或跨目標合法回合,
           * 只要沒有至少一組共活,node.state.mover 即可仲裁獲勝。
           */
          if (!hasArbitrationDefence(node.state)) {
            node.orderedMoves = [];
            node.terminalValue = evaluateArbitrationTerminal(
              node.state,
              rootPlayer,
              rootBaseDiff
            );
            return;
          }

          let moves = isRoot
            ? rootMoves.slice()
            : generateLegalTurns(node.state);

          /*
           * 保險處理:正常情況下,共活本身就是合法回合,
           * 因此通過 hasArbitrationDefence 後不應為空。
           */
          if (moves.length === 0) {
            node.orderedMoves = [];
            node.terminalValue = evaluateArbitrationTerminal(
              node.state,
              rootPlayer,
              rootBaseDiff
            );
            return;
          }

          node.orderedMoves = orderMovesForSearch(
            node.state,
            moves,
            isRoot ? tacticalScores : null
          );
        }

七、修改 AI 根節點的仲裁判定

目前 AI 使用:

js
let rootMoves = generateLegalTurns(rootState);

if (rootMoves.length === 0) {
  aiThinking = false;
  aiTriggerArbitrationSuccess();
  return;
}

這會把單接、雙接、跨目標頂鑫例外也當成可以阻止仲裁,因此需要先獨立檢查共活。

修改位置

startAI() 中找到:

js
        /*
         * 根節點使用全部合法回合,不再先用單接、雙接、
         * 破壞對方或共活規則刪除候選。
         */
        let rootMoves = generateLegalTurns(rootState);

        if (rootMoves.length === 0) {
          aiThinking = false;
          aiTriggerArbitrationSuccess();
          return;
        }

替換為

js
        /*
         * 仲裁判定只看「共活」。
         *
         * 即使目前存在單接、雙接或跨目標頂鑫例外下法,
         * 只要上一方沒有留下至少一組共活,
         * AI 仍應立即申請仲裁並獲勝。
         */
        const arbitrationDefences =
          generateArbitrationDefenceTurns(rootState);

        if (
          !isSearchGameComplete(rootState) &&
          arbitrationDefences.length === 0
        ) {
          aiThinking = false;
          aiTriggerArbitrationSuccess();
          return;
        }

        /*
         * 通過仲裁檢查後,AI 實際搜索仍使用所有正常合法回合。
         * 單接、雙接及跨目標頂鑫例外仍然可以正常成為候選。
         */
        let rootMoves = generateLegalTurns(rootState);

        if (rootMoves.length === 0) {
          aiThinking = false;
          aiTriggerArbitrationSuccess();
          return;
        }

因此:

  • 仲裁檢查只認共活;
  • 通過仲裁檢查後,AI 仍可選擇單接、雙接或跨目標合法回合;
  • 但搜尋會正確發現:如果選擇某一步後沒有給對手留下共活,對手下一節點將仲裁獲勝。

八、修改 getAllValidMoves(),使人工仲裁只列出共活

修改位置

在檔案後段找到原本:

js
      function getAllValidMoves(player) {
        const state = createSearchStateFromGlobals(player);
        return generateLegalTurns(state);
      }

整段替換為

js
      /*
       * 此函式目前只供仲裁及舊仲裁相容流程使用。
       *
       * 仲裁只承認共活,不承認單接、雙接或跨目標頂鑫例外。
       */
      function getAllValidMoves(player) {
        const state = createSearchStateFromGlobals(player);
        return generateArbitrationDefenceTurns(state);
      }

這一處替換會同步修正以下現有呼叫:

js
arbValidMoves = getAllValidMoves(currentPlayer);

包括:

  • executeArbitration()
  • aiTriggerArbitrationSuccess()
  • 原有仲裁預覽
  • arbValidMoves 的數量顯示

因此人工仲裁結果將變成:

  • 有共活:仲裁失敗,預覽共活組合;
  • 沒有共活:仲裁成功;
  • 即使有單接/雙接/跨目標頂鑫下法,也不會導致仲裁失敗。

九、建議同步修正註解,避免未來再次混淆

找到 validateTurnOnState 上方原註解中的:

js
       * 「是否讓對方下一回合無棋可下」不在此硬刪除。
       * 搜尋會把它評估為對方仲裁獲勝,而不是用啟發式直接刪除候選。

替換為:

js
       * 「是否給對方留下至少一組共活」不屬於本回合本身的落子合法性,
       * 因此不在此處刪除候選。
       *
       * 某回合即使符合單接、雙接或跨目標頂鑫例外,
       * 若完成後沒有給對方留下共活,對方下一回合仍可仲裁獲勝。
       * 此結果由仲裁及 minimax/MCTS 終局判定處理。

十、修改後的完整邏輯關係

流程图
正在绘制流程图…

其中「仲裁防禦」在一般回合嚴格等於:

text
同一個對方棋子
+ 兩條不同邊
+ 兩個合法棋子
+ 兩子彼此相容
+ 庫存足夠

不包含單接、雙接或跨目標下法。


最終需要實際修改的程式位置摘要

  1. generateLegalTurns() 後新增:
    • generateArbitrationDefenceTurns()
    • hasArbitrationDefence()
  2. evaluateNoMoveTerminal() 替換成:
    • evaluateArbitrationTerminal()
  3. 修改 tacticalAlphaBeta() 的終局判定順序。
  4. 修改 runStateMinimax()alphaBetaState() 的終局判定順序。
  5. 修改 runStateMCTS()ensureNodeMoves()
  6. 修改 startAI() 根節點的仲裁檢查。
  7. getAllValidMoves() 改為返回 generateArbitrationDefenceTurns(state)
  8. 其餘正常落子、計分、單接/雙接與跨目標例外流程不修改。