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

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

贏了7792:

代码: 全选

[[3,6],[-5,3,4,8,5],[5,6,4,3,8,4,5,8],[-5,6,2,5,1,4,7],[5,7,4,3,8,4,5,8,2,6,5,2,1,5,4,1,7,4],[-5,5,6,9,8,4,3],[3,7,3,4,4,8,5,8,2,6,5,2,1,5,4,1,7,4,6,5,9,6,8,9],[-5,7,3,0],[3,0,3,4,4,8,5,8,2,6,5,2,1,5,4,1,7,4,6,5,9,6,8,9,3,7,3,0],[-1,3,4,3,0,3,7,4,1,4,8,7,4,1,5,8,9,5,8,5,2,9,6,2,6,6,5]]
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改進:
一,把FORCE_CHECK_DEPTH的分段設置寫放進runMtcs()中,當搜尋時用橋的數量來決定FORCE_CHECK_DEPTH。

二,FORCE_CHECK_DEPTH的劃分方案改為:
0~8->2
10~12->6
其餘->10
並放到modal窗口上可以設置,2(8)6(12)10一共五個數字可填。

三,modal窗口的參數設置起初應摺疊,點擊打開。

只需給出所要修改的地方。

代码: 全选

以下是針對您的需求所需要修改的程式碼片段。請將對應部分替換至您的原始檔案中:

### 1. 修改 Modal 介面 (HTML 部分)
將 `#mcts_modal` 內的參數設定改為預設摺疊 (`<details>`),並加入必勝深度的 5 個自訂參數。

**找到這段代碼:**
```html
            <div id="mcts_modal">
                <div class="mcts_close" onclick="closeMctsModal()">×</div>
                <h3>🤖 AI 搜尋設定</h3>
                <div class="mcts_row"><label>模擬次數:</label> <input type="number" id="mcts_sims" value="50"></div>
                <div class="mcts_row"><label>搜尋深度:</label> <input type="number" id="mcts_depth" value="8"></div>
                <div class="mcts_row"><label>最大步數:</label> <input type="number" id="mcts_max_path" value="9" max="9" min="1"></div>
                <div class="mcts_row"><label>首回限步:</label> <input type="number" id="mcts_first_path" value="5" max="9" min="1"></div>
                <hr>
```

**替換為:**
```html
            <div id="mcts_modal">
                <div class="mcts_close" onclick="closeMctsModal()">×</div>
                <h3>🤖 AI 搜尋設定</h3>
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 8px; outline: none;">展開參數設定</summary>
                    <div class="mcts_row"><label>模擬次數:</label> <input type="number" id="mcts_sims" value="50"></div>
                    <div class="mcts_row"><label>搜尋深度:</label> <input type="number" id="mcts_depth" value="8"></div>
                    <div class="mcts_row"><label>最大步數:</label> <input type="number" id="mcts_max_path" value="9" max="9" min="1"></div>
                    <div class="mcts_row"><label>首回限步:</label> <input type="number" id="mcts_first_path" value="5" max="9" min="1"></div>
                    <div style="margin-top: 5px; margin-bottom: 5px;">
                        <div style="margin-bottom: 5px; font-size: 14px;">必勝搜尋深度 (橋數 ➔ 深度):</div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>0 ~ <input type="number" id="fcd_thresh1" value="8" style="width: 40px;"> 橋</label> ➔ <input type="number" id="fcd_depth1" value="2" style="width: 40px;">
                        </div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>10 ~ <input type="number" id="fcd_thresh2" value="12" style="width: 40px;"> 橋</label> ➔ <input type="number" id="fcd_depth2" value="6" style="width: 40px;">
                        </div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>其餘</label> ➔ <input type="number" id="fcd_depth3" value="10" style="width: 40px;">
                        </div>
                    </div>
                </details>
                <hr>
```

---

### 2. 修改 updateMctsParams 函數
移除原本判定 FORCE_CHECK_DEPTH 的邏輯,改為只負責從 UI 抓取設定值。

**找到這段代碼:**
```javascript
    function updateMctsParams() {
        PARAMS.SIMULATIONS_PER_MOVE = parseInt(document.getElementById('mcts_sims').value) || 50;
        PARAMS.MAX_DEPTH = parseInt(document.getElementById('mcts_depth').value) || 8;
        PARAMS.MAX_PATH_LENGTH = parseInt(document.getElementById('mcts_max_path').value) || 9;
        PARAMS.FIRST_MAX_PATH = parseInt(document.getElementById('mcts_first_path').value) || 5;
        let bridgesCount = arrows.length;
        if (bridgesCount <= 6) PARAMS.FORCE_CHECK_DEPTH = 2;
        else if (bridgesCount <= 9) PARAMS.FORCE_CHECK_DEPTH = 4;
        else if (bridgesCount <= 12) PARAMS.FORCE_CHECK_DEPTH = 6;
        else PARAMS.FORCE_CHECK_DEPTH = 8;
    }
```

**替換為:**
```javascript
    function updateMctsParams() {
        PARAMS.SIMULATIONS_PER_MOVE = parseInt(document.getElementById('mcts_sims').value) || 50;
        PARAMS.MAX_DEPTH = parseInt(document.getElementById('mcts_depth').value) || 8;
        PARAMS.MAX_PATH_LENGTH = parseInt(document.getElementById('mcts_max_path').value) || 9;
        PARAMS.FIRST_MAX_PATH = parseInt(document.getElementById('mcts_first_path').value) || 5;
        
        // 讀取新的必勝深度判定參數
        PARAMS.FCD_THRESH1 = parseInt(document.getElementById('fcd_thresh1').value) || 8;
        PARAMS.FCD_DEPTH1 = parseInt(document.getElementById('fcd_depth1').value) || 2;
        PARAMS.FCD_THRESH2 = parseInt(document.getElementById('fcd_thresh2').value) || 12;
        PARAMS.FCD_DEPTH2 = parseInt(document.getElementById('fcd_depth2').value) || 6;
        PARAMS.FCD_DEPTH3 = parseInt(document.getElementById('fcd_depth3').value) || 10;
    }
```

---

### 3. 修改 runMCTS 函數
將 `FORCE_CHECK_DEPTH` 的動態分段設置寫進 `runMCTS()` 開頭,依據當前橋樑數量與剛才讀取到的設定判定深度。

**找到這段代碼:**
```javascript
    function runMCTS(isAuto = false) {
        if (isMctsRunning) return; 
        isMctsRunning = true;

        let rootState = {
```

**替換為:**
```javascript
    function runMCTS(isAuto = false) {
        if (isMctsRunning) return; 
        isMctsRunning = true;

        // 根據橋數動態決定 FORCE_CHECK_DEPTH
        let bridgesCount = arrows.length;
        if (bridgesCount <= (PARAMS.FCD_THRESH1 || 8)) {
            PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH1 || 2;
        } else if (bridgesCount >= 10 && bridgesCount <= (PARAMS.FCD_THRESH2 || 12)) {
            PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH2 || 6;
        } else {
            // 其餘情形 (包含 9,或大於 FCD_THRESH2)
            PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH3 || 10;
        }

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

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

在這個局面下:
[[3,6],[-5,3,4,8,5],[5,6,4,3,8,4,5,8],[-5,6,2,5,1,4,7],[5,7,4,3,8,4,5,8,2,6,5,2,1,5,4,1,7,4]]
藍方只要走「5,6,9,8,4,3」就可獲勝,但是在搜尋中這步棋的勝率並不高,查找原因。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

優化這一段html的樣式,使之能夠在同一行顯示,且等高。

代码: 全选

                        <div class="mcts_row">
                            0 ~ <input type="number" id="fcd_thresh1" value="8" style="width: 40px;">  ➔ <input type="number" id="fcd_depth1" value="2" style="width: 40px;">
                             ~ <input type="number" id="fcd_thresh2" value="12" style="width: 40px;">  ➔ <input type="number" id="fcd_depth2" value="6" style="width: 40px;">
                             ~  ➔ <input type="number" id="fcd_depth3" value="10" style="width: 40px;">
                        </div>
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

grok:

代码: 全选

```html
<div class="mcts_row" style="
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: nowrap;
    white-space: nowrap;
    font-size: 16px;
    line-height: 1;
">
    0 ~ 
    <input type="number" id="fcd_thresh1" value="8" style="width: 40px; height: 34px; text-align: center; box-sizing: border-box;">
     ➔ 
    <input type="number" id="fcd_depth1" value="2" style="width: 40px; height: 34px; text-align: center; box-sizing: border-box;">
     ~ 
    <input type="number" id="fcd_thresh2" value="12" style="width: 40px; height: 34px; text-align: center; box-sizing: border-box;">
     ➔ 
    <input type="number" id="fcd_depth2" value="6" style="width: 40px; height: 34px; text-align: center; box-sizing: border-box;">
     ~ ➔ 
    <input type="number" id="fcd_depth3" value="10" style="width: 40px; height: 34px; text-align: center; box-sizing: border-box;">
</div>
```

### 優化說明:
- 使用 **`display: flex; align-items: center;`** 讓所有元素(文字 + input)**強制同一行**,且**垂直置中**。
- 所有 `<input>` 設定相同 `height: 34px` + `box-sizing: border-box`,確保**等高**且不會因邊框造成高度差異。
- `gap: 8px` 統一間距,符號前後加上 ` ` 避免排版跑掉。
- `flex-wrap: nowrap` + `white-space: nowrap` 防止換行。
- 保留原本的 `class="mcts_row"`,方便你之後在 CSS 檔繼續調整。

直接複製貼上即可使用,效果如下(已確保同一行、等高):

**0 ~** [8] **➔** [2] **~** [12] **➔** [6] **~ ➔** [10]

如果需要再微調高度或間距,告訴我即可!
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

konigsberg20260411strong7793

摺疊參數,提高了必殺搜尋深度,增加了必殺分段深度設置,必殺分段寫進搜尋中。

https://ejsoon.vip/wp-content/uploads/2 ... g7793.html
附件
konigsberg20260412strong7793.7z
(13.59 KiB) 已下载 51 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

贏7793!:

代码: 全选

[[3,6],[-5,3,4,0,1,5,8],[8,6,4,3,0,4,1,0,5,1,8,5],[-5,6,5,9,8,7],[8,7,4,3,0,4,1,0,5,1,8,5,5,6,9,5,8,9,7,8],[-5,8,4,1,0],[0,7,4,3,0,4,0,1,5,1,8,5,5,6,9,5,8,9,7,8,4,8,1,4],[-5,7,3],[0,3,4,3,0,4,0,1,5,1,8,5,5,6,9,5,8,9,7,8,4,8,1,4,3,7]]
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改進:
一,把FORCE_CHECK_DEPTH放進processNextChunk()中,當搜尋時用當前橋的數量來決定FORCE_CHECK_DEPTH。

二,把FCD_THRESH1和FCD_THRESH2寫到PARAMS中,去掉其在modal窗口中的設置。

三,當onload時,js也要把fcd_depth123的默認值填上。

只需給出所要修改的地方。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini pro:

代码: 全选

以下是針對您的需求,需要修改的程式碼片段:

### 1. 修改 HTML 中的 Modal 視窗 (去掉 thresh 的 input 設定)
找到 `<div id="mcts_modal">` 內的 `參數設定` 區塊,將 `必勝搜尋深度` 相關的 UI 改為:

```html
                    <div style="margin-top: 5px; margin-bottom: 5px;">
                        <div style="margin-bottom: 5px; font-size: 14px;">必勝搜尋深度 (橋數 ➔ 深度):</div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>0 ~ 8 橋 </label><input type="number" id="fcd_depth1" style="width: 40px;">
                        </div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>9 ~ 12 橋 </label><input type="number" id="fcd_depth2" style="width: 40px;">
                        </div>
                        <div class="mcts_row" style="padding-left: 10px;">
                            <label>13 橋以上 </label><input type="number" id="fcd_depth3" style="width: 40px;">
                        </div>
                    </div>
```

### 2. 在 `window.onload` 加入預設值賦值
找到 `window.onload = function() { ... }`,在設定預設值的區塊加上 `fcd_depth` 的賦值:

```javascript
    window.onload = function() {
        // ... 前面的 wakeLock 程式碼保持不變 ...
        displayGame();
        document.getElementById('mcts_sims').value = 50;
        document.getElementById('mcts_depth').value = 8;
        document.getElementById('mcts_max_path').value = 9;
        document.getElementById('mcts_first_path').value = 5;
        
        // 新增 fcd_depth1, 2, 3 的預設值
        document.getElementById('fcd_depth1').value = 2;
        document.getElementById('fcd_depth2').value = 6;
        document.getElementById('fcd_depth3').value = 10;
        
        document.getElementById('mcts_auto_blue').checked = false;
        document.getElementById('mcts_auto_green').checked = true;
        openMctsModal();
    }
```

### 3. 將 `FCD_THRESH1` 和 `FCD_THRESH2` 寫入 `PARAMS`
找到 `const PARAMS = { ... }` 的定義處,加入這兩個常數:

```javascript
    const PARAMS = {
        SIMULATIONS_PER_MOVE: 50,
        MAX_DEPTH: 8,
        MAX_PATH_LENGTH: 9,
        FIRST_MAX_PATH: 5,
        FCD_THRESH1: 8,      // 新增
        FCD_THRESH2: 12      // 新增
    };
```

### 4. 修改 `updateMctsParams()`,移除讀取 thresh 的邏輯
找到 `updateMctsParams()`,改為只讀取 depth:

```javascript
    function updateMctsParams() {
        PARAMS.SIMULATIONS_PER_MOVE = parseInt(document.getElementById('mcts_sims').value) || 50;
        PARAMS.MAX_DEPTH = parseInt(document.getElementById('mcts_depth').value) || 8;
        PARAMS.MAX_PATH_LENGTH = parseInt(document.getElementById('mcts_max_path').value) || 9;
        PARAMS.FIRST_MAX_PATH = parseInt(document.getElementById('mcts_first_path').value) || 5;
        
        // 讀取新的必勝深度判定參數 (移除 THRESH1, THRESH2 的 DOM 讀取)
        PARAMS.FCD_DEPTH1 = parseInt(document.getElementById('fcd_depth1').value) || 2;
        PARAMS.FCD_DEPTH2 = parseInt(document.getElementById('fcd_depth2').value) || 6;
        PARAMS.FCD_DEPTH3 = parseInt(document.getElementById('fcd_depth3').value) || 10;
    }
```

### 5. 調整 `runMCTS()` 與 `processNextChunk()` 邏輯
找到 `runMCTS()` 函數,**刪除**一開始判斷 `FORCE_CHECK_DEPTH` 的那段程式碼:
*(即刪除以下這段)*
```javascript
// 刪除這段
let bridgesCount = arrows.length;
if (bridgesCount <= (PARAMS.FCD_THRESH1 || 8)) { ... } else ...
```

然後找到裡面的 `processNextChunk()` 函數,將 `FORCE_CHECK_DEPTH` 的判斷**加入**到該函數的開頭:

```javascript
        // 非同步分塊執行函數
        function processNextChunk() {
            if (moveIndex < totalMoves) {
                let move = rootMoves[moveIndex];

                // 根據當前橋數動態決定 FORCE_CHECK_DEPTH
                let bridgesCount = arrows.length;
                if (bridgesCount <= PARAMS.FCD_THRESH1) {
                    PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH1;
                } else if (bridgesCount <= PARAMS.FCD_THRESH2) {
                    PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH2;
                } else {
                    PARAMS.FORCE_CHECK_DEPTH = PARAMS.FCD_DEPTH3;
                }

                // 實時更新 UI 狀態
                let statusText = `層數: ${PARAMS.MAX_DEPTH}<br>分支: ${moveIndex + 1} / ${totalMoves}`;
                // ... 下方的程式碼保持不變 ...
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

let bridgesCount = arrows.length;這句有實時獲得當前推演的橋的數量嗎?如果沒有,如何才能獲得?

代码: 全选

這句 `let bridgesCount = arrows.length;` **沒有**獲得「深層推演中」實時增加的橋的數量。它取得的只是**目前畫面上(也就是搜尋剛開始時的真實盤面)**已經蓋好的橋的數量。

**為什麼?**
因為 MCTS 在背景進行沙盤推演時,為了不干擾遊戲畫面,是使用一個虛擬的 `state` 物件(包含 `empty_map`, `arrow_map`, `stack_arrow` 等)來模擬未來狀態,它**絕對不會**去更動 `arrows` 這個用來控制 SVG 畫面的全局陣列。

### 如何才能獲得推演中的實時橋數?

在你的虛擬 `state` 結構中,已經建好的橋是記錄放在 `state.stack_arrow` 裡面。因為每座橋會佔用陣列中的兩個位置(起點和終點),所以實時的橋數算法是:

```javascript
let simulatedBridgesCount = state.stack_arrow.length / 2;
```

---

### 你需要修改嗎?這取決於你的設計目的:

**情況一:用「當下真實盤面」決定這回合的搜尋深度(原本代碼的邏輯)**
如果你只是想在 MCTS 啟動前,看看現在棋盤上有幾座橋,來決定「這整回合的 `FORCE_CHECK_DEPTH` 要設多深」,那麼你在 `processNextChunk` 裡使用 `arrows.length` **完全沒問題,而且效能最好**。

**情況二:希望深度能隨著「深層推演的未來盤面」動態改變**
如果你希望 AI 在腦海中多走兩步、橋變多之後,即時改變判斷的深度,那你就不能依賴全局變數,必須要把虛擬狀態的橋數傳進去。

如果你想改成情況二(動態依賴推演當下的狀態),你需要這樣改:

在用到 `PARAMS.FORCE_CHECK_DEPTH` 的地方(比如 `processNextChunk` 內部準備呼叫 `canForceWin` 時),將判斷改寫為讀取 `res.state`:

```javascript
// 取得推演到這一步時的虛擬橋數
let simulatedBridgesCount = res.state.stack_arrow.length / 2;
let dynamicDepth;

if (simulatedBridgesCount <= PARAMS.FCD_THRESH1) {
    dynamicDepth = PARAMS.FCD_DEPTH1;
} else if (simulatedBridgesCount <= PARAMS.FCD_THRESH2) {
    dynamicDepth = PARAMS.FCD_DEPTH2;
} else {
    dynamicDepth = PARAMS.FCD_DEPTH3;
}

// 然後把 dynamicDepth 傳給 canForceWin
if (canForceWin(res.state, dynamicDepth, rootState.turn)) {
    // ...
```

**總結:** `arrows.length` 是現在眼睛看到的橋數;`state.stack_arrow.length / 2` 才是 AI 腦海中推演到未來的橋數。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

改進:
如果當前有「自動走棋」的搜尋進程,則打開modal窗口時,「開始搜尋」按鈕將消失,取而代之的是一個「取消搜尋」功能。當按下「取消搜尋」按鈕,則取消本次自動走棋。如果「自動走棋」已經搜尋結束並開始走棋,則「取消搜尋」按鈕消失,變為「正在走棋」按鈕(不可點擊)。

當走棋完畢,如果下一個棋手不是自動走棋,遊戲也沒有結束,則出現「開始搜尋」按鈕;如果下一個棋手是自動走棋,遊戲也沒有結束,則出現「取消搜尋」按鈕;如果遊戲結束,它將變為「開始遊戲」。

當人工走棋時(棋子已被激活並shooting),打開modal窗口,按鈕也會是「正在走棋」。

如果當前處於「自動走棋」的搜尋進程時,場上的class=man,以及btn_history將不能被點擊。

如果當前沒有「自動走棋」的搜尋進程,打開modal窗口時,按下「開始搜尋」按鈕,它將被「取消搜尋」替代。當按下「取消搜尋」按鈕,則取消本次搜尋,並恢復「開始搜尋」按鈕。

只需給出所要修改的地方。
上次由 ejsoon 在 2026年 4月 12日 17:47,总共编辑 3 次。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

konigsberg20260412param7794

在搜尋中,用搜尋時橋的數量來指定必殺搜尋深度。
附件
konigsberg20260412param7794.7z
(13.58 KiB) 已下载 49 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

在processNextChunk()中,MTCS_FIRST_PATH應該只在搜尋中的首回合生效。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

gemini pro:

代码: 全选

為了實現這個需求,需要新增狀態控制變數(以判斷 MCTS 的搜尋與走棋狀態)、UI 的按鈕狀態更新邏輯,以及針對各個點擊事件增加鎖定(禁止中斷 AI 運算)。

請依序將對應的程式碼替換或新增至你的檔案中:

### 1. 修改 HTML (增加取消與正在走棋按鈕)
找到 `#mcts_modal` 裡面的按鈕區域,替換為以下內容:

```html
<button id="mcts_play_btn" style="width: 100%; padding: 8px; margin-top: 5px; cursor: pointer; display: none; background: #8278f5; color: white; border: none; border-radius: 4px; font-size: 16px;">開始遊戲</button>
<button id="mcts_start_btn" style="width: 100%; padding: 8px; margin-top: 5px; cursor: pointer;">開始搜尋</button>
<button id="mcts_cancel_btn" style="width: 100%; padding: 8px; margin-top: 5px; cursor: pointer; display: none; background: #e77; color: white; border: none; border-radius: 4px; font-size: 16px;">取消搜尋</button>
<button id="mcts_moving_btn" style="width: 100%; padding: 8px; margin-top: 5px; display: none; background: #ccc; border: none; border-radius: 4px; font-size: 16px; color: #666;" disabled>正在走棋...</button>
```

### 2. 新增全域變數
在 JS 的頂部(大約在 `let autoPlayTimer = null;` 的周圍)新增以下變數:

```javascript
let autoPlayTimer = null; // 用於自動下棋的計時器
let isMctsRunning = false; // 用來追蹤 MCTS 是否正在執行
let currentMctsResults = [];
let currentMctsPage = 0;
// ===== 新增的狀態變數 =====
let cancelMcts = false; 
let autoMoveTimer = null; 
```

### 3. 新增 UI 按鈕狀態更新函數 & 修改 Modal 開關邏輯
將原本的 `openMctsModal()` 和 `closeMctsModal()` 替換,並加入新的 `updateModalButtons()` 函數:

```javascript
function updateModalButtons() {
    let btnPlay = document.getElementById('mcts_play_btn');
    let btnStart = document.getElementById('mcts_start_btn');
    let btnCancel = document.getElementById('mcts_cancel_btn');
    let btnMoving = document.getElementById('mcts_moving_btn');
    let resultsDiv = document.getElementById('mcts_results');

    if (!btnPlay || !btnStart || !btnCancel || !btnMoving) return;

    btnPlay.style.display = 'none';
    btnStart.style.display = 'none';
    btnCancel.style.display = 'none';
    btnMoving.style.display = 'none';

    let autoBlue = document.getElementById('mcts_auto_blue') && document.getElementById('mcts_auto_blue').checked;
    let autoGreen = document.getElementById('mcts_auto_green') && document.getElementById('mcts_auto_green').checked;
    let isCurrentAuto = (game_status === 1 || game_status === 3) && ((current_man === 0 && autoBlue) || (current_man === 1 && autoGreen));

    if (game_status === 0 || game_status === 7 || game_status === 8) {
        btnPlay.style.display = 'block';
    } else if (game_status === 2 || game_status === 4) {
        btnMoving.style.display = 'block'; // 正在走棋 (人工或AI走棋時)
    } else if (isMctsRunning || (isCurrentAuto && !cancelMcts)) {
        btnCancel.style.display = 'block'; // 正在搜尋或即將自動搜尋
    } else {
        btnStart.style.display = 'block'; // 等待手動搜尋
    }
}

function openMctsModal() {
    if (!isMctsRunning && game_status !== 2 && game_status !== 4) {
        document.getElementById('mcts_results').innerHTML = '';
        currentMctsResults = [];
        currentMctsPage = 0;
    }
    updateModalButtons();
    document.getElementById('mcts_modal').style.display = 'block';
}

function closeMctsModal() {
    document.getElementById('mcts_modal').style.display = 'none';
    if (game_status == 1 || game_status == 3) {
        let autoBlue = document.getElementById('mcts_auto_blue') && document.getElementById('mcts_auto_blue').checked;
        let autoGreen = document.getElementById('mcts_auto_green') && document.getElementById('mcts_auto_green').checked;
        // 只有在非搜尋中且未被手動取消的情況下,才恢復自動走棋計時
        if (((current_man === 0 && autoBlue) || (current_man === 1 && autoGreen)) && !isMctsRunning && !cancelMcts) {
            clearTimeout(autoPlayTimer);
            autoPlayTimer = setTimeout(() => {
                updateMctsParams();
                runMCTS(true);
            }, 500);
        }
    }
}
```

### 4. 綁定新按鈕事件與修改舊有按鈕事件
找到 `document.getElementById("mcts_start_btn").addEventListener...`,將它替換成以下邏輯:

```javascript
// 新增:取消搜尋按鈕事件
document.getElementById("mcts_cancel_btn").addEventListener('click', () => {
    cancelMcts = true;
    isMctsRunning = false;
    clearTimeout(autoPlayTimer);
    clearTimeout(autoMoveTimer);
    document.getElementById('mcts_results').innerHTML = "🛑 搜尋已取消。";
    document.getElementById('thinking_ui').style.display = 'none';
    updateModalButtons();
});

// 覆寫開始搜尋按鈕事件(使用 cloneNode 清除舊綁定以防重複)
let mctsStartBtn = document.getElementById("mcts_start_btn");
let newMctsStartBtn = mctsStartBtn.cloneNode(true);
mctsStartBtn.parentNode.replaceChild(newMctsStartBtn, mctsStartBtn);
newMctsStartBtn.addEventListener('click', () => {
    if (![1, 2, 3, 4].includes(game_status)) {
        document.getElementById('mcts_results').innerHTML = "請在輪到玩家移動時搜尋。";
        return;
    }
    cancelMcts = false;
    isMctsRunning = true; // 點擊時立即鎖定狀態
    updateModalButtons();
    updateMctsParams();
    document.getElementById('mcts_results').innerHTML = "搜尋中,請稍候...";
    setTimeout(() => runMCTS(false), 50);
});
```

### 5. 禁止 MCTS 搜尋時點擊場上元素
在以下四個函數的第一行加入 `if (isMctsRunning) return;` 的防呆判斷:

```javascript
// 1. dotOnClick
function dotOnClick() {
    if (isMctsRunning) return; // 新增鎖定
    if (game_status == 2 || game_status == 4) {
// ...

// 2. manOnClick
function manOnClick() {
    if (isMctsRunning) return; // 新增鎖定
    if (game_status == 1 || game_status == 3) {
// ...

// 3. historyOnclick
function historyOnclick() {
    if (isMctsRunning) return; // 新增鎖定
    if ("g11048" == this.id) {
// ...

// 4. setting_close.onclick = btn_history_set.onclick
setting_close.onclick = btn_history_set.onclick = function() {
    if (isMctsRunning) return; // 新增鎖定
    let game_history_parse = [];
// ...
```

### 6. 在 `displayGame` 重置取消狀態與刷新UI
找到 `displayGame()` 函數尾端自動下棋的段落,修改如下:

```javascript
        // 自動下棋
        clearTimeout(autoPlayTimer);
        cancelMcts = false; // 新回合自動重置取消狀態
        if (game_status == 1 || game_status == 3) {
            let autoBlue = document.getElementById('mcts_auto_blue') && document.getElementById('mcts_auto_blue').checked;
            let autoGreen = document.getElementById('mcts_auto_green') && document.getElementById('mcts_auto_green').checked;
            if ((current_man === 0 && autoBlue) || (current_man === 1 && autoGreen)) {
                autoPlayTimer = setTimeout(() => {
                    updateMctsParams();
                    runMCTS(true);
                }, 500);
            }
        }
        if (typeof updateModalButtons === 'function') updateModalButtons(); // 更新 UI 狀態
    } // displayGame 的結尾大括號
```

### 7. 修改 `simulateMove` 與 `runMCTS`
找到 `simulateMove` 函數,在點擊棋子後呼叫 UI 更新:

```javascript
function simulateMove(path) {
    document.getElementById('mcts_modal').style.display = 'none';
    if (game_status == 1 || game_status == 3) {
        let manDotOnClick = manOnClick.bind(men_bottom[current_man]);
        manDotOnClick(); // 點擊棋子進入路線規劃階段
    }
    
    isMctsRunning = false; // 真正開始走棋時才解除搜尋鎖定
    updateModalButtons(); // 狀態改變為 2 或 4,更新按鈕為「正在走棋」

    let step = 1;
// ... 後面邏輯維持不變
```

找到 `runMCTS` 內部對應的三個結束節點(`canShootNow` 節點、`rootMoves.length === 0` 節點、與 `processNextChunk` 結束點),處理中斷與 `autoMoveTimer`:

```javascript
        if (canShootNow) {
            let msg = "🎯 <b>射殺對手!</b>";
            let thinkingUI = document.getElementById('thinking_ui');
            let resultsUI = document.getElementById('mcts_results');

            if (isAuto) {
                thinkingUI.innerHTML = msg;
                thinkingUI.style.display = 'block';
                autoMoveTimer = setTimeout(() => { // 加入 timer
                    thinkingUI.style.display = 'none';
                    isMctsRunning = false;
                    let manDotOnClick = manOnClick.bind(men_bottom[current_man]);
                    manDotOnClick();
                }, 1500);
            } else {
                resultsUI.innerHTML = msg;
                isMctsRunning = false;
                updateModalButtons();
            }
            return; // 終止後續的 MCTS 展開
        }

        let isFirstGameTurn = game_hp <= 1; 
        let rootMoves = getLegalMoves(rootState, isFirstGameTurn);
        if (rootMoves.length === 0) {
            if (!isAuto) {
                document.getElementById('mcts_results').innerHTML = "❌ 勝率0%,無路可走!";
                isMctsRunning = false;
                updateModalButtons();
            } else {
                let thinkingUI = document.getElementById('thinking_ui');
                thinkingUI.innerHTML = "❌ <b>勝率0%</b><br>無路可走!";
                thinkingUI.style.display = 'block';
                autoMoveTimer = setTimeout(() => { // 加入 timer
                    thinkingUI.style.display = 'none';
                    isMctsRunning = false;
                    let manDotOnClick = manOnClick.bind(men_bottom[current_man]);
                    manDotOnClick(); // 按下本方棋子
                    setTimeout(() => {
                        btn_finish.onclick(); // 再按下完成以直接輸掉回合
                    }, 500);
                }, 1500);
            }
            return;
        }

// ...往下找到 processNextChunk 的段落

        // 非同步分塊執行函數
        function processNextChunk() {
            if (cancelMcts) {
                isMctsRunning = false;
                updateModalButtons();
                return; // 接收到取消訊號,終止搜尋循環
            }

            if (moveIndex < totalMoves) {
// ... 中間維持不變 ...
                moveIndex++;
                setTimeout(processNextChunk, 0);
            } else {
                // 搜尋全部結束,處理結果
                moveStats.sort((a, b) => { ... });

                if (isAuto) {
                    let bestStat = moveStats[0];
                    if (bestStat.guaranteedWin) {
                        thinkingUI.innerHTML = '🎯 必殺!';
                    } else if (bestStat.immediateLoss) {
                        thinkingUI.innerHTML = '💀 告負';
                    } else if (bestStat.guaranteedLoss) {
                        thinkingUI.innerHTML = '⚠️ 必敗';
                    } else {
                        thinkingUI.innerHTML = '✅ <b>決定!</b><br>預估勝率: ' + ((bestStat.wins / bestStat.plays) * 100).toFixed(1) + '%';
                    }

                    // 短暫停留展示勝率後,自動執行走棋
                    autoMoveTimer = setTimeout(() => simulateMove(bestStat.move), 800); // 賦值給 autoMoveTimer 方便取消
                } else {
                    isMctsRunning = false;
                    updateModalButtons();
                    currentMctsResults = moveStats;
                    currentMctsPage = 0;
                    renderMctsResults();
                }
            }
        }
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5786
注册时间: 2022年 11月 18日 17:36
联系:

Re: 改下哥尼斯堡網頁程式

帖子 ejsoon »

按你說的修改之後,點擊「開始搜尋」就再也沒有開始搜尋。並且,當關閉modal窗口時,如果當前方的AI有打勾,它是應該開始自動搜尋和走棋的,但是現在並沒有。

代码: 全选

你說得非常準確,這確實是上一版邏輯中的一個衝突死結(Deadlock)。

**問題發生的原因:**
在上一版的「開始搜尋」按鈕點擊事件中,我加入了 `isMctsRunning = true;` 來提早鎖定狀態以更新 UI。然而,`runMCTS` 函數的第一行就是防呆機制 `if (isMctsRunning) return;`。
這導致點擊按鈕後,`runMCTS` 以為「已經有搜尋在跑了」而直接強制中斷。結果就是:搜尋根本沒執行,且 `isMctsRunning` 永遠卡在 `true`,導致後續關閉視窗時的自動走棋也因為這個卡死的狀態而無法觸發。

要解決這個問題,我們只需要把鎖定狀態的責任交還給 `runMCTS` 本身,並在手動勾選 AI 時重置取消狀態。請替換/新增以下兩段程式碼:

### 1. 修改「開始搜尋」的點擊事件
找到你原始碼中覆寫 `mcts_start_btn` 的這段程式碼,替換為以下內容(移除了提早設定的 `isMctsRunning = true`,改用直接操作 DOM 來切換按鈕):

```javascript
// 覆寫開始搜尋按鈕事件(使用 cloneNode 清除舊綁定以防重複)
let mctsStartBtn = document.getElementById("mcts_start_btn");
let newMctsStartBtn = mctsStartBtn.cloneNode(true);
mctsStartBtn.parentNode.replaceChild(newMctsStartBtn, mctsStartBtn);
newMctsStartBtn.addEventListener('click', () => {
    if (![1, 2, 3, 4].includes(game_status)) {
        document.getElementById('mcts_results').innerHTML = "請在輪到玩家移動時搜尋。";
        return;
    }
    if (isMctsRunning) return; // 按鈕端防呆
    
    cancelMcts = false;
    
    // 【關鍵修正】不要在這裡設定 isMctsRunning = true,以免觸發 runMCTS 的阻擋機制。
    // 直接手動切換按鈕顯示,讓 UI 立即反應即可
    document.getElementById('mcts_start_btn').style.display = 'none';
    document.getElementById('mcts_cancel_btn').style.display = 'block';
    
    updateMctsParams();
    document.getElementById('mcts_results').innerHTML = "搜尋中,請稍候...";
    setTimeout(() => runMCTS(false), 50); // runMCTS 內部會自己將 isMctsRunning 設為 true
});
```

### 2. 新增 AI 勾選框的事件監聽(加在剛才那段程式碼的下方)
這可以解決另一個潛在體驗問題:如果你按了「取消搜尋」(此時 `cancelMcts` 為 `true`),但你又重新去點擊打勾 AI 選項,程式應該要理解你「改變心意了」。加上這段後,只要切換 AI 設定,就會重置取消狀態,確保關閉 modal 時能順利啟動自動走棋:

```javascript
// 新增:當使用者手動改變 AI 勾選狀態時,解除「取消」狀態
// 確保在取消搜尋後,若重新設定 AI,關閉視窗時能恢復自動走棋
let mctsAutoBlueCb = document.getElementById('mcts_auto_blue');
let mctsAutoGreenCb = document.getElementById('mcts_auto_green');
if(mctsAutoBlueCb) mctsAutoBlueCb.addEventListener('change', () => { cancelMcts = false; });
if(mctsAutoGreenCb) mctsAutoGreenCb.addEventListener('change', () => { cancelMcts = false; });
```

換上這兩段後,按鈕卡死的問題就會解除,自動走棋也能在關閉視窗後順利接手了!
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

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