改下哥尼斯堡網頁程式

分享和讨论Javascript相关的话题
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

去掉MAX_PATH_LENGTH,改為:首先算出當前一次移動最多能走幾個點,設為max_path_length,之後在1到max_path_length之間隨機選一個數字,作為當前運算的深度。只需告知所要更改的地方。

代码: 全选

根據您的要求,我將修改蒙地卡洛搜尋的邏輯。主要更動為:移除固定的 `MAX_PATH_LENGTH`,新增一個計算最長路徑的函式,並在每次模擬時隨機決定步數長度。

以下是需要更動的四個地方:

### 1. 修改 `PARAMS` 參數
移除 `MAX_PATH_LENGTH`,保留其他模擬參數。
```javascript
const PARAMS = {
    SIMULATIONS_PER_MOVE: 50, 
    MAX_DEPTH: 8            
};
```

### 2. 新增 `getMaxPathLength` 函式
在 `runMCTS` 函式外部新增此工具函式,用來動態計算當前位置出發,不重複經過節點的最長路徑長度(點的數量)。
```javascript
function getMaxPathLength(state) {
    let start = state.turn === 0 ? state.p0 : state.p1;
    let maxL = 1;
    function dfs(curr, visitedSet) {
        maxL = Math.max(maxL, visitedSet.size);
        // 取得所有可通行的鄰居 (空地或已有橋樑)
        let neighbors = (state.empty_map[curr] || []).concat(state.arrow_map[curr] || []);
        for (let nxt of neighbors) {
            if (!visitedSet.has(nxt)) {
                visitedSet.add(nxt);
                dfs(nxt, visitedSet);
                visitedSet.delete(nxt);
            }
        }
    }
    dfs(start, new Set([start]));
    return maxL;
}
```

### 3. 修改 `runMCTS` 的第一步搜尋邏輯
將原本固定的搜尋深度改為隨機選取的深度。
```javascript
// 在 runMCTS 內部:
let maxPossibleLen = getMaxPathLength(rootState);
// 在 2 (最少移動一格) 到最長長度之間隨機選一個數字作為當前深度
let randomTargetLen = Math.floor(Math.random() * (maxPossibleLen - 1)) + 2; 

// 取得所有長度剛好等於 randomTargetLen 的合法路徑
let rootMoves = getLegalMoves(rootState, randomTargetLen).filter(m => m.length === randomTargetLen);
```

### 4. 修改 `playout` 隨機模擬函式
將隨機推演的過程也改為動態長度。
```javascript
function playout(state, maxDepth) {
    let currState = state;
    for (let d = 0; d < maxDepth; d++) {
        let maxL = getMaxPathLength(currState);
        if (maxL < 2) return 1 - currState.turn; // 無路可走,判負

        // 隨機決定這次移動要走幾個點 (2 ~ maxL)
        let k = Math.floor(Math.random() * (maxL - 1)) + 2;
        let moves = getLegalMoves(currState, k).filter(m => m.length === k);
        
        if (moves.length === 0) return 1 - currState.turn;
        
        let randMove = moves[Math.floor(Math.random() * moves.length)];
        let result = applyMove(currState, randMove);
        if (result.terminal) return result.winner;
        currState = result.state;
    }
    return -1; 
}
```

### 調整建議與注意事項
1.  **隨機性的影響**:由於現在每一步的「深度(路徑長度)」是隨機選取的,這會增加搜尋的廣度,但可能會降低對「極端長路徑」的覆蓋率。如果您發現推薦的路徑都太短,可以將隨機權重往高處調整。
2.  **效能警告**:`getMaxPathLength` 使用了 DFS 窮舉。在節點非常多且全連通的複雜盤面上,計算最長路徑可能會稍微增加運算負擔。如果點擊後出現卡頓,建議將 `SIMULATIONS_PER_MOVE` 調低至 `30`。
3.  **合法性**:遊戲規則通常要求必須建立新橋或移動。在隨機選取長度為 `1` 時(即不移動),邏輯上是不合法的,因此程式碼中我將隨機範圍限定在 `2` 到 `max_path_length` 之間。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

在以下的盤面,應該要推出必殺:

代码: 全选

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

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

帖子 ejsoon »

去掉MAX_PATH_LENGTH,改為:首先算出當前一次移動最多能走幾個點,設為max_path_length,之後在1到max_path_length之間隨機選一個數字,作為這一次所要走的點數。則在排名前五的走法中,應該會有五種不同長度的走法。

與此同時,在一些終局局面時,應該要能算出必殺走法。

只需告知所要更改的地方。

gemini pro:

代码: 全选

為了達成你的需求(動態隨機選擇路徑長度、解除最大路徑限制,並標註必殺走法),你只需要替換掉原本程式碼中的幾個特定區塊。

以下是需要更改的地方:

### 1. 修改 `PARAMS` 參數區塊
移除 `MAX_PATH_LENGTH` 參數。
```javascript
    const PARAMS = {
        // 每個「安全候選路徑」要進行幾場隨機模擬盤? (數字越大越準,但計算越久)
        SIMULATIONS_PER_MOVE: 50, 
        // 每次隨機模擬最多往後推演幾回合? (超過就視為平手)
        MAX_DEPTH: 8            
    };
```

### 2. 修改 `runMCTS` 內的呼叫方式
移除傳入 `MAX_PATH_LENGTH` 的引數。
```javascript
    // 將取得 rootMoves 的這行改為:
    let rootMoves = getLegalMoves(rootState);

    // ...略...

    // 將迴圈內 playout 的呼叫改為:
    let winner = playout(res.state, PARAMS.MAX_DEPTH);
```

### 3. 更新 `runMCTS` 的輸出報告 (加入必殺走法判定)
利用模擬勝率來判斷並標示出「必殺走法」。
```javascript
    // 將 4. 輸出報告 的 forEach 區塊改為:
    moveStats.slice(0, 5).forEach((stat, idx) => {
        let pathStr = stat.move.join(" ➔ ");
        if (stat.immediateLoss) {
            console.log(`   ${idx + 1}. 路徑: [${pathStr}] | 💀 致命步 (走完立刻遭對手連通射擊)`);
        } else {
            let score = (stat.wins / stat.plays) * 100;
            // 如果在蒙地卡洛推演中達到近乎 100% 勝率,標記為必殺
            if (score >= 99) {
                console.log(`   ${idx + 1}. 路徑: [${pathStr}] (長度: ${stat.move.length - 1}) | 🎯 必殺走法! (預期勝率: 100%)`);
            } else {
                console.log(`   ${idx + 1}. 路徑: [${pathStr}] (長度: ${stat.move.length - 1}) | 預期勝率: ${score.toFixed(1)}% (模擬 ${stat.plays} 場)`);
            }
        }
    });
```

### 4. 移除 `getLegalMoves` 的長度限制
讓 DFS 可以窮舉當前所有可能走到極限的路線。
```javascript
// 將原有的 getLegalMoves 整個替換為:
function getLegalMoves(state) {
    let start = state.turn === 0 ? state.p0 : state.p1;
    let moves = [];
    let bridges_count = state.stack_arrow.length / 2;

    function dfs(curr, path, has_new_bridge) {
        if (path.length > 1 && (has_new_bridge || bridges_count >= 12)) {
            moves.push(path.slice());
        }
        // 移除原有的 if (path.length >= maxLen) return; 限制

        for (let nxt of state.empty_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, true);
                path.pop();
            }
        }
        for (let nxt of state.arrow_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, has_new_bridge);
                path.pop();
            }
        }
    }
    dfs(start, [start], false);
    return moves;
}
```

### 5. 更新 `playout` 邏輯 (動態隨機點數)
在這裡實踐「算出最大能走點數,並在 1 到 max 之間隨機選定長度」的邏輯。
```javascript
// 將原有的 playout 整個替換為:
function playout(state, maxDepth) {
    let currState = state;
    for (let d = 0; d < maxDepth; d++) {
        let moves = getLegalMoves(currState);
        if (moves.length === 0) {
            return 1 - currState.turn; // 走投無路,換對方贏
        }
        
        // 1. 算出當前一次移動最多能走幾個點 (邊數 = 陣列長度 - 1)
        let max_path_length = Math.max(...moves.map(m => m.length - 1));
        
        // 2. 在 1 到 max_path_length 之間隨機選一個數字,作為這一次所要走的點數
        let target_length = Math.floor(Math.random() * max_path_length) + 1;
        
        // 3. 過濾出符合該長度的走法
        let filtered_moves = moves.filter(m => (m.length - 1) === target_length);
        
        // 防呆:如果剛好沒有該長度的路徑,退回從所有路徑中隨機選
        if (filtered_moves.length === 0) filtered_moves = moves; 
        
        let randMove = filtered_moves[Math.floor(Math.random() * filtered_moves.length)];
        let result = applyMove(currState, randMove);
        
        if (result.terminal) {
            return result.winner;
        }
        currState = result.state;
    }
    return -1; // 達到限制深度,視為平局
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

改進:

一,在行走規則中加上「不能跟對方停在同一格」。

二,在以下局面中,要能算出藍方有必殺走法:

[[9,4,3,0,4,0,5,4,5,9,9,8,2,6,2,1,0,1,3,4,4,7,8,7,5,8,5,1,2,5,5,6,6,9,1,4,3,7]]

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

竟然算出45962的勝率是92%

代码: 全选

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

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

帖子 ejsoon »

gemini pro:

代码: 全选

Here are the specific parts of the script you need to modify. All comments and logs have been translated to English to meet your requirements.

### 1. Update `getLegalMoves` (Add rule: Cannot stop on opponent's node)
Replace the entire `getLegalMoves` function with this updated version that identifies the opponent's position and prevents the path from ending there:

```javascript
// 1. Updated getLegalMoves
function getLegalMoves(state) {
    let start = state.turn === 0 ? state.p0 : state.p1;
    let opponent_pos = state.turn === 0 ? state.p1 : state.p0; // Identify opponent's position
    let moves = [];
    let bridges_count = state.stack_arrow.length / 2;

    function dfs(curr, path, has_new_bridge) {
        // Rule: Cannot stop on the same node as the opponent (curr !== opponent_pos)
        if (path.length > 1 && (has_new_bridge || bridges_count >= 12) && curr !== opponent_pos) {
            moves.push(path.slice());
        }

        for (let nxt of state.empty_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, true);
                path.pop();
            }
        }
        for (let nxt of state.arrow_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, has_new_bridge);
                path.pop();
            }
        }
    }
    dfs(start, [start], false);
    return moves;
}
```

### 2. Update `applyMove` (Fix bridge limit bug for late-game states)
In your `applyMove` function, replace the bridge-flipping logic at the bottom (which previously forced a `.slice(0, 24)` array truncation) to preserve late-game bridges for accurate calculation:

```javascript
    // Replace the bridge flipping loop and state update in applyMove with:
    let sim_stack_arrow = nextState.stack_arrow.concat(new_arrow);
    for (let x = 0; x < route.length - 1; x++) {
        let step_start = route[x], step_end = route[x + 1];
        // Game rule: Only the first 12 bridges (y < 12) can be flipped
        for (let y = 0; y * 2 < sim_stack_arrow.length && y < 12; y++) {
            if (sim_stack_arrow[y * 2] === step_start && sim_stack_arrow[y * 2 + 1] === step_end) {
                let turnindex = nextState.arrow_map[step_start].indexOf(step_end);
                if (turnindex > -1) {
                    nextState.arrow_map[step_start].splice(turnindex, 1);
                    nextState.arrow_map[step_end].push(step_start);
                }
                sim_stack_arrow[y * 2] = step_end;
                sim_stack_arrow[y * 2 + 1] = step_start;
                break;
            }
        }
    }
    // Assign the array directly. Do NOT use .slice(0, 24), as the state can have > 12 bridges
    nextState.stack_arrow = sim_stack_arrow; 
```

### 3. Add the `isForcedWin` Helper Function
Insert this new function anywhere outside of `runMCTS` (e.g., right below `applyMove`). This implements a shallow Minimax search to definitively calculate "必殺" (Forced Mate) scenarios:

```javascript
// Check for Guaranteed Wins (Forced Mate) recursively
function isForcedWin(state, depth) {
    let oppMoves = getLegalMoves(state);
    
    // If opponent has no valid moves left, they lose (Current player wins)
    if (oppMoves.length === 0) return true;
    
    for (let oppMove of oppMoves) {
        let oppRes = applyMove(state, oppMove);
        if (oppRes.terminal) continue; // This specific response kills the opponent natively
        
        // If opponent survives, check if we can still force a win in the next turn
        if (depth > 1) {
            let myMoves = getLegalMoves(oppRes.state);
            let canForce = false;
            for (let myMove of myMoves) {
                let myRes = applyMove(oppRes.state, myMove);
                if (!myRes.terminal && isForcedWin(myRes.state, depth - 1)) {
                    canForce = true;
                    break;
                }
            }
            if (!canForce) return false; // Opponent has a safe line we cannot defeat
        } else {
            return false; // Depth limit reached, opponent survives for now
        }
    }
    return true; // All of the opponent's possible responses result in their inevitable defeat
}
```

### 4. Update Move Evaluation in `runMCTS`
In the `runMCTS` function, replace the `for (let move of rootMoves)` loop block with this one, which integrates the new `isForcedWin` calculation:

```javascript
    // 2. Evaluate first step and run MCTS
    for (let move of rootMoves) {
        let res = applyMove(rootState, move);
        
        if (res.terminal) {
            moveStats.push({ move, wins: -999, plays: 1, immediateLoss: true });
        } else if (isForcedWin(res.state, 2)) { 
            // Check for Forced Mate within 2 turns (Depth 2)
            moveStats.push({ move, wins: Infinity, plays: 1, guaranteedWin: true });
        } else {
            // Safe move, run random simulations (Playouts)
            let wins = 0;
            for (let i = 0; i < PARAMS.SIMULATIONS_PER_MOVE; i++) {
                let winner = playout(res.state, PARAMS.MAX_DEPTH);
                if (winner === rootState.turn) wins += 1;
                else if (winner === -1) wins += 0.5;
            }
            moveStats.push({ move, wins, plays: PARAMS.SIMULATIONS_PER_MOVE });
        }
    }
```

### 5. Update Output Formatting in `runMCTS`
At the very bottom of the `runMCTS` function, replace the sorting and `forEach` printing logic with this:

```javascript
    // Sort results: Guaranteed wins first, then sort by win rate
    moveStats.sort((a, b) => {
        if (a.guaranteedWin) return -1;
        if (b.guaranteedWin) return 1;
        return (b.wins / b.plays) - (a.wins / a.plays);
    });

    console.log(`✅ Search completed! (Evaluated ${rootMoves.length} possible paths)`);
    console.log("🏆 Recommended Paths (Top 5):");
    
    moveStats.slice(0, 5).forEach((stat, idx) => {
        let pathStr = stat.move.join(" ➔ ");
        if (stat.immediateLoss) {
            console.log(`   ${idx + 1}. Path: [${pathStr}] | 💀 Fatal (Immediate loss by opponent shooting)`);
        } else if (stat.guaranteedWin) {
            console.log(`   ${idx + 1}. Path: [${pathStr}] (Length: ${stat.move.length - 1}) | 🎯 Guaranteed Win (Forced Mate)!`);
        } else {
            let score = ((stat.wins / stat.plays) * 100).toFixed(1);
            console.log(`   ${idx + 1}. Path: [${pathStr}] (Length: ${stat.move.length - 1}) | Expected Win Rate: ${score}% (Simulated ${stat.plays} games)`);
        }
    });
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

改進:

一,增加MAX_PATH_LENGTH,作為限定一次移動最多走幾步,不改變其餘邏輯。

二,參考「必勝走法」,也要得出「必敗走法」,即雖然下一步不會馬上被殺,但是對方已經可以有殺棋手段。

三,當點id="g12155"時彈出窗口,有SIMULATIONS_PER_MOVE,MAX_DEPTH,MAX_PATH_LENGTH三個參數的設置,同時有一個「開始搜尋」的按鈕。當搜尋完成時,五個搜尋結果將展示出來並可點擊,當點擊其中一個結果時,程式將模擬走棋。

四,所彈窗口中還有「自動藍方」和「自動綠方」的打勾選項,當關閉窗口時,若其中一個或兩個有勾選,當輪到自動方時則開始模擬走棋。

五,模擬走棋是當搜尋完之後,點擊本方棋子,再每隔240毫秒點擊走到的點,之後按btn_finish。當勝負已分則停止。

只需告知所要更改的地方。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

gemini pro:

代码: 全选

以下是達成這 5 項新功能的程式碼修改指南。請依照以下五個步驟,在您的 HTML 與 JavaScript 中替換或新增對應的區塊。

### 1. 新增 UI 介面與樣式 (對應功能三、四)
在原本的 `<div id="detail_div"></div>` 下方,插入 AI 設定面板的 HTML 與 CSS:

```html
			<div id="detail_div"></div>
            <style>
                #mcts_modal { position: absolute; top: 10%; left: 50%; transform: translateX(-50%); background: white; border: 2px solid #333; padding: 15px; z-index: 20; display: none; width: 300px; max-height: 80%; overflow-y: auto; font-size: 16px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.3);}
                #mcts_modal h3 { margin-top: 0; margin-bottom: 10px; }
                .mcts_result_item { padding: 8px; margin: 5px 0; background: #eee; cursor: pointer; border-radius: 4px; border: 1px solid #ccc; }
                .mcts_result_item:hover { background: #ddd; }
                .mcts_close { position: absolute; top: 5px; right: 10px; cursor: pointer; font-weight: bold; font-size: 20px; }
                .mcts_row { margin-bottom: 5px; display: flex; justify-content: space-between;}
                .mcts_row input { width: 60px; }
            </style>
            <div id="mcts_modal">
                <div class="mcts_close" onclick="document.getElementById('mcts_modal').style.display='none'">×</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="99"></div>
                <hr>
                <div class="mcts_row">
                    <label><input type="checkbox" id="mcts_auto_blue"> 自動藍方</label>
                    <label><input type="checkbox" id="mcts_auto_green"> 自動綠方</label>
                </div>
                <button id="mcts_start_btn" style="width: 100%; padding: 8px; margin-top: 5px; cursor: pointer;">開始搜尋</button>
                <div id="mcts_results" style="margin-top: 10px; font-size: 14px;"></div>
            </div>
            ```

### 2. 替換參數與綁定事件 (對應功能三、五)
在 `<script>` 中找到 `const PARAMS = {...};` 和 `document.getElementById("g12155").addEventListener...` 這兩段,將它們**刪除並替換為**:

```javascript
// ==========================================
// 🛠️ MCTS 參數與 UI 綁定
// ==========================================
const PARAMS = {
    SIMULATIONS_PER_MOVE: 50, 
    MAX_DEPTH: 8,
    MAX_PATH_LENGTH: 99            
};
let autoPlayTimer = null; // 用於自動下棋的計時器

// 點擊圖標顯示設定視窗
document.getElementById("g12155").addEventListener('click', () => {
    document.getElementById('mcts_modal').style.display = 'block';
});

// 點擊開始搜尋
document.getElementById("mcts_start_btn").addEventListener('click', () => {
    if (![1, 2, 3, 4].includes(game_status)) {
        document.getElementById('mcts_results').innerHTML = "請在輪到玩家移動時搜尋。";
        return;
    }
    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) || 99;
    document.getElementById('mcts_results').innerHTML = "搜尋中,請稍候...";
    setTimeout(() => runMCTS(false), 50); // 讓 UI 有時間渲染
});

// 自動模擬走棋函數
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(); // 點擊棋子進入路線規劃階段
    }
    let step = 1;
    function clickNext() {
        if (step < path.length && (game_status == 2 || game_status == 4)) {
            let dotClick = dotOnClick.bind(dots[path[step]]);
            dotClick(); // 每 240 毫秒點擊下一個點
            step++;
            setTimeout(clickNext, 240);
        } else if (game_status == 2 || game_status == 4) {
            btn_finish.onclick(); // 走完點擊完成
        }
    }
    setTimeout(clickNext, 240);
}
```

### 3. 攔截 displayGame 處理自動下棋 (對應功能四)
找到現有的 `function displayGame () { ... }`,在其函數**最後面**(結尾大括號 `}` 前)加入以下判斷:

```javascript
	// --- 新增自動下棋判斷區塊 ---
	clearTimeout(autoPlayTimer);
	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(() => {
				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) || 99;
				let bestMove = runMCTS(true); // true 表示不顯示 UI 而是直接返回最佳步
				if (bestMove) simulateMove(bestMove);
			}, 500);
		}
	}
	// --- 新增結束 ---
```

### 4. 強化深度搜尋邏輯 (對應功能一、功能二)
在 `<script>` 中**取代**原本的 `getLegalMoves` 和 `isForcedWin` 函數,改為這兩個強化版:

```javascript
// 尋找某個狀態下的所有合法路徑 (加入 MAX_PATH_LENGTH 限制)
function getLegalMoves(state) {
    let start = state.turn === 0 ? state.p0 : state.p1;
    let opponent_pos = state.turn === 0 ? state.p1 : state.p0; 
    let moves = [];
    let bridges_count = state.stack_arrow.length / 2;

    function dfs(curr, path, has_new_bridge) {
        if (path.length > 1 && (has_new_bridge || bridges_count >= 12) && curr !== opponent_pos) {
            moves.push(path.slice());
        }
        if (path.length - 1 >= PARAMS.MAX_PATH_LENGTH) return; // 功能一:達到最大步數不再繼續展開

        for (let nxt of state.empty_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, true);
                path.pop();
            }
        }
        for (let nxt of state.arrow_map[curr] || []) {
            if (path.indexOf(nxt) === -1) {
                path.push(nxt);
                dfs(nxt, path, has_new_bridge);
                path.pop();
            }
        }
    }
    dfs(start, [start], false);
    return moves;
}

// 判斷給定玩家是否保證獲勝 (取代原本有缺陷的 isForcedWin,加入必敗走法支援)
function canForceWin(state, depth, player) {
    let moves = getLegalMoves(state);
    if (moves.length === 0) return (1 - state.turn) === player; // 無路可走代表對手贏
    if (depth <= 0) return false;

    if (state.turn === player) {
        for (let move of moves) {
            let res = applyMove(state, move);
            if (res.terminal) {
                if (res.winner === player) return true;
            } else if (canForceWin(res.state, depth - 1, player)) {
                return true;
            }
        }
        return false;
    } else {
        for (let move of moves) {
            let res = applyMove(state, move);
            if (res.terminal) {
                if (res.winner !== player) return false;
            } else if (!canForceWin(res.state, depth - 1, player)) {
                return false;
            }
        }
        return true;
    }
}
```

### 5. 替換 runMCTS 核心以支援視窗輸出 (對應功能二、三)
**刪除**原本的 `function runMCTS() { ... }` 整個函式,並**替換為**以下版本:

```javascript
function runMCTS(isAuto = false) {
    let rootState = {
        p0: parseInt(men_bottom[0].getAttribute("i") || men_top[0].getAttribute("i")),
        p1: parseInt(men_bottom[1].getAttribute("i") || men_top[1].getAttribute("i")),
        turn: current_man,
        empty_map: JSON.parse(JSON.stringify(empty_map)),
        arrow_map: JSON.parse(JSON.stringify(arrow_map)),
        stack_arrow: stack_arrow.slice()
    };

    let rootMoves = getLegalMoves(rootState);
    if (rootMoves.length === 0) {
        if (!isAuto) document.getElementById('mcts_results').innerHTML = "❌ 無路可走。";
        return null;
    }

    let moveStats = [];

    for (let move of rootMoves) {
        let res = applyMove(rootState, move);
        
        if (res.terminal) {
            moveStats.push({ move, wins: -999, plays: 1, immediateLoss: true });
        } else if (canForceWin(res.state, 2, rootState.turn)) { 
            // 功能二:己方兩步內必勝
            moveStats.push({ move, wins: Infinity, plays: 1, guaranteedWin: true });
        } else if (canForceWin(res.state, 2, 1 - rootState.turn)) {
            // 功能二:對手兩步內必勝 (我方必敗步)
            moveStats.push({ move, wins: -500, plays: 1, guaranteedLoss: true });
        } else {
            let wins = 0;
            for (let i = 0; i < PARAMS.SIMULATIONS_PER_MOVE; i++) {
                let winner = playout(res.state, PARAMS.MAX_DEPTH);
                if (winner === rootState.turn) wins += 1;
                else if (winner === -1) wins += 0.5;
            }
            moveStats.push({ move, wins, plays: PARAMS.SIMULATIONS_PER_MOVE });
        }
    }

    // 依照勝率排序
    moveStats.sort((a, b) => {
        if (a.guaranteedWin && !b.guaranteedWin) return -1;
        if (!a.guaranteedWin && b.guaranteedWin) return 1;
        if (a.guaranteedLoss && !b.guaranteedLoss) return 1;
        if (!a.guaranteedLoss && b.guaranteedLoss) return -1;
        if (a.immediateLoss && !b.immediateLoss) return 1;
        if (!a.immediateLoss && b.immediateLoss) return -1;
        return (b.wins / b.plays) - (a.wins / a.plays);
    });

    if (isAuto) return moveStats[0].move; // 自動模式直接回傳最佳陣列

    // 功能三:渲染結果到 UI 上並綁定點擊事件
    let resultsHtml = "<b>推薦路徑 (前 5 名):</b><br>";
    moveStats.slice(0, 5).forEach((stat, idx) => {
        let pathStr = stat.move.join(" ➔ ");
        let scoreText = "";
        if (stat.immediateLoss) scoreText = "<span style='color:red'>💀 致命步 (立即被殺)</span>";
        else if (stat.guaranteedLoss) scoreText = "<span style='color:orange'>⚠️ 必敗步 (對方有殺招)</span>";
        else if (stat.guaranteedWin) scoreText = "<span style='color:green'>🎯 必殺走法!</span>";
        else scoreText = `勝率: ${((stat.wins / stat.plays) * 100).toFixed(1)}%`;

        resultsHtml += `<div class="mcts_result_item" onclick="simulateMove([${stat.move}])">${idx + 1}. [${pathStr}] | ${scoreText}</div>`;
    });
    document.getElementById('mcts_results').innerHTML = resultsHtml;
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

改進:

一,當「自動走棋」在搜尋時,應在左上角顯示一個小窗口,提示「思考中…」,並實時顯示當前搜尋層數及分支數。當搜尋結束則顯示當前走法的勝率,行動結束後,這個窗口消失。

二,在mcts_modal窗口中,點擊開始搜尋時,應顯示「搜尋中:層數…,分支數…」並實時更新。搜尋完畢,在下方的結果中,應有向下翻頁和向上翻頁,每頁顯示最多五個,同時給出總個數。當關閉窗口時,應清空搜尋結果。

只需告知所要更改的地方。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

gemini pro:

代码: 全选

這是一個很棒的優化方向。為了讓 UI 能夠實時更新而不被 JavaScript 單執行緒卡死,我們需要將核心的 `runMCTS` 從「同步迴圈」改寫為「非同步分塊處理(利用 `setTimeout`)」。

以下是達成您需求的 **5 個需要更改的地方**,請直接替換或新增對應的區塊:

### 第一處:新增「思考中」左上角視窗,並修改「關閉按鈕」
在原本 `<div id="mcts_modal">` 的上方加入 `#thinking_ui` 的樣式與元素,同時修改 `mcts_close` 的 `onclick` 事件(讓它在關閉時清空結果):

**請將原本 UI 區塊的開頭部分替換為:**
```html
            <style>
                /* 原本的 MCTS 樣式保留... */
                #mcts_modal { position: absolute; top: 10%; left: 50%; transform: translateX(-50%); background: white; border: 2px solid #333; padding: 15px; z-index: 20; display: none; width: 300px; max-height: 80%; overflow-y: auto; font-size: 16px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.3);}
                #mcts_modal h3 { margin-top: 0; margin-bottom: 10px; }
                .mcts_result_item { padding: 8px; margin: 5px 0; background: #eee; cursor: pointer; border-radius: 4px; border: 1px solid #ccc; }
                .mcts_result_item:hover { background: #ddd; }
                .mcts_close { position: absolute; top: 5px; right: 10px; cursor: pointer; font-weight: bold; font-size: 20px; }
                .mcts_row { margin-bottom: 5px; display: flex; justify-content: space-between;}
                .mcts_row input { width: 60px; }
                
                /* 新增:左上角自動走棋思考視窗樣式 */
                #thinking_ui { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 15px; border-radius: 8px; z-index: 25; font-size: 16px; display: none; pointer-events: none; line-height: 1.5; }
            </style>
            
            <div id="thinking_ui">🤔 思考中...</div>
            
            <div id="mcts_modal">
                <div class="mcts_close" onclick="document.getElementById('mcts_modal').style.display='none'; document.getElementById('mcts_results').innerHTML=''; currentMctsResults=[];">×</div>
                <h3>🤖 AI 搜尋設定</h3>
```

### 第二處:修改 `displayGame` 內的自動呼叫
因為我們將 AI 搜尋改為非同步,它不再同步回傳 `bestMove`,而是會在搜尋結束後自己觸發走棋。

**在 `displayGame` 最下方的自動判斷區塊,將原本這兩行:**
```javascript
				let bestMove = runMCTS(true); // true 表示不顯示 UI 而是直接返回最佳步
				if (bestMove) simulateMove(bestMove);
```
**替換為:**
```javascript
				runMCTS(true); // 非同步執行,搜尋完會自動呼叫 simulateMove
```

### 第三處:修改 `simulateMove`(收尾時隱藏視窗)
在走棋完成並點擊 `btn_finish.onclick()` 後,隱藏左上角的思考視窗。

**請將原本的 `simulateMove` 函數內部的 `clickNext` 修改為:**
```javascript
    function clickNext() {
        if (step < path.length && (game_status == 2 || game_status == 4)) {
            let dotClick = dotOnClick.bind(dots[path[step]]);
            dotClick(); 
            step++;
            setTimeout(clickNext, 240);
        } else if (game_status == 2 || game_status == 4) {
            btn_finish.onclick(); // 走完點擊完成
            // 新增:延遲隱藏左上角視窗,配合動畫時間
            setTimeout(() => {
                document.getElementById('thinking_ui').style.display = 'none';
            }, 1200);
        }
    }
```

### 第四處:新增分頁狀態變數與渲染邏輯
在 `<script>` 裡面(可以放在 `let autoPlayTimer = null;` 下方),加入分頁專用的全域變數與函數:

```javascript
let currentMctsResults = [];
let currentMctsPage = 0;

function renderMctsResults() {
    let maxPerPage = 5;
    let total = currentMctsResults.length;
    let totalPages = Math.ceil(total / maxPerPage) || 1;
    let start = currentMctsPage * maxPerPage;
    let end = Math.min(start + maxPerPage, total);

    let resultsHtml = `<b>搜尋完成!共 ${total} 條路徑:</b><br>`;
    for (let i = start; i < end; i++) {
        let stat = currentMctsResults[i];
        let pathStr = stat.move.join(" ➔ ");
        let scoreText = "";
        
        if (stat.immediateLoss) scoreText = "<span style='color:red'>💀 致命步</span>";
        else if (stat.guaranteedLoss) scoreText = "<span style='color:orange'>⚠️ 必敗步</span>";
        else if (stat.guaranteedWin) scoreText = "<span style='color:green'>🎯 必殺走法</span>";
        else scoreText = `勝率: ${((stat.wins / stat.plays) * 100).toFixed(1)}%`;

        resultsHtml += `<div class="mcts_result_item" onclick="simulateMove([${stat.move}])">${i + 1}. [${pathStr}] | ${scoreText}</div>`;
    }

    // 分頁按鈕 UI
    resultsHtml += `
        <div style="margin-top: 10px; display: flex; justify-content: space-between; align-items: center;">
            <button onclick="changeMctsPage(-1)" ${currentMctsPage === 0 ? 'disabled' : ''}>上一頁</button>
            <span>頁數: ${currentMctsPage + 1} / ${totalPages}</span>
            <button onclick="changeMctsPage(1)" ${currentMctsPage >= totalPages - 1 ? 'disabled' : ''}>下一頁</button>
        </div>`;
        
    document.getElementById('mcts_results').innerHTML = resultsHtml;
}

function changeMctsPage(delta) {
    currentMctsPage += delta;
    renderMctsResults();
}
```

### 第五處:全面替換 `runMCTS` 函數(核心非同步改寫)
將原本的 `runMCTS` **整個刪除**,替換為以下這個會主動讓出執行緒(Yield)的版本,以便實時更新 UI。

```javascript
function runMCTS(isAuto = false) {
    let rootState = {
        p0: parseInt(men_bottom[0].getAttribute("i") || men_top[0].getAttribute("i")),
        p1: parseInt(men_bottom[1].getAttribute("i") || men_top[1].getAttribute("i")),
        turn: current_man,
        empty_map: JSON.parse(JSON.stringify(empty_map)),
        arrow_map: JSON.parse(JSON.stringify(arrow_map)),
        stack_arrow: stack_arrow.slice()
    };

    let rootMoves = getLegalMoves(rootState);
    if (rootMoves.length === 0) {
        if (!isAuto) document.getElementById('mcts_results').innerHTML = "❌ 無路可走。";
        return;
    }

    let moveStats = [];
    let moveIndex = 0;
    let totalMoves = rootMoves.length;

    let thinkingUI = document.getElementById('thinking_ui');
    let resultsUI = document.getElementById('mcts_results');

    if (isAuto) {
        thinkingUI.style.display = 'block';
    }

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

            // 實時更新 UI 狀態
            let statusText = `層數: ${PARAMS.MAX_DEPTH}<br>分支: ${moveIndex + 1} / ${totalMoves}`;
            if (isAuto) {
                thinkingUI.innerHTML = `🤔 <b>思考中...</b><br>${statusText}`;
            } else {
                resultsUI.innerHTML = `<b>搜尋中...</b><br>${statusText}`;
            }

            // 核心推演邏輯
            let res = applyMove(rootState, move);
            if (res.terminal) {
                moveStats.push({ move, wins: -999, plays: 1, immediateLoss: true });
            } else if (canForceWin(res.state, 2, rootState.turn)) { 
                moveStats.push({ move, wins: Infinity, plays: 1, guaranteedWin: true });
            } else if (canForceWin(res.state, 2, 1 - rootState.turn)) {
                moveStats.push({ move, wins: -500, plays: 1, guaranteedLoss: true });
            } else {
                let wins = 0;
                for (let i = 0; i < PARAMS.SIMULATIONS_PER_MOVE; i++) {
                    let winner = playout(res.state, PARAMS.MAX_DEPTH);
                    if (winner === rootState.turn) wins += 1;
                    else if (winner === -1) wins += 0.5;
                }
                moveStats.push({ move, wins, plays: PARAMS.SIMULATIONS_PER_MOVE });
            }

            moveIndex++;
            // 使用 setTimeout 讓出執行緒,允許瀏覽器重新渲染畫面
            setTimeout(processNextChunk, 0); 
        } else {
            // 搜尋全部結束,處理結果
            moveStats.sort((a, b) => {
                if (a.guaranteedWin && !b.guaranteedWin) return -1;
                if (!a.guaranteedWin && b.guaranteedWin) return 1;
                if (a.guaranteedLoss && !b.guaranteedLoss) return 1;
                if (!a.guaranteedLoss && b.guaranteedLoss) return -1;
                if (a.immediateLoss && !b.immediateLoss) return 1;
                if (!a.immediateLoss && b.immediateLoss) return -1;
                return (b.wins / b.plays) - (a.wins / a.plays);
            });

            if (isAuto) {
                let bestStat = moveStats[0];
                let winRate = bestStat.guaranteedWin ? '100%' :
                              bestStat.guaranteedLoss ? '0%' :
                              bestStat.immediateLoss ? '0%' :
                              ((bestStat.wins / bestStat.plays) * 100).toFixed(1) + '%';
                thinkingUI.innerHTML = `✅ <b>決定!</b><br>預估勝率: ${winRate}`;
                
                // 短暫停留展示勝率後,自動執行走棋
                setTimeout(() => simulateMove(bestStat.move), 800);
            } else {
                currentMctsResults = moveStats;
                currentMctsPage = 0;
                renderMctsResults(); // 呼叫分頁渲染
            }
        }
    }

    processNextChunk(); // 啟動非同步迴圈
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

代码: 全选

#tiling {
  position: relative;
  width: 100%;
  height: 100%;
}
#tiling_inner {
  position: relative;
  padding-top: 66.7%;
}
svg#konigsberg {
  position: absolute;
  float: left;
  background: lightblue;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
html結構是#tiling>(#tiling_inner+svg#konigsberg)。

現在它雖然能維持2:3的高寬比,但是當屏寬達到一定寬度時,其高度會超出屏幕。如何使svg維持高寬比為2:3,並最大化顯示在父元素中間?
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5774
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

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

帖子 ejsoon »

然後我戰勝了AI:

代码: 全选

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

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

帖子 ejsoon »

當在mcts_modal窗口關閉時,搜尋結果並沒有被清空。 我希望窗口再次打開時,不要出現上次的搜尋結果,即不要出現#mcts_result。

gemini fast:

代码: 全选

這是一個細節處理上的問題。雖然在前一次建議中我們在 `onclick` 加入了清空邏輯,但為了確保萬無一無,最穩妥的方法是在**每次打開視窗的函式**中也執行一次清空,並確保 HTML 結構中的 `mcts_results` 內容被徹底移除。

請對程式碼進行以下兩處修改:

### 1. 修改「打開視窗」的事件監聽
在點擊 `g12155` 圖標打開視窗時,主動清空結果區域,這樣即使上次關閉失敗,這次打開也會是乾淨的。

**請將該段程式碼修改為:**
```javascript
// 點擊圖標顯示設定視窗
document.getElementById("g12155").addEventListener('click', () => {
    // 打開時確保清空舊結果
    document.getElementById('mcts_results').innerHTML = ''; 
    currentMctsResults = []; 
    currentMctsPage = 0;
    
    document.getElementById('mcts_modal').style.display = 'block';
});
```

### 2. 強化「關閉按鈕」的邏輯
確保點擊 `×` 號時,不只是隱藏視窗,連同內部的 HTML 內容也要清空。

**請修改 HTML 視窗中的關閉按鈕部分:**
```html
<div class="mcts_close" onclick="closeMctsModal()">×</div>

<script>
function closeMctsModal() {
    // 1. 隱藏視窗
    document.getElementById('mcts_modal').style.display = 'none';
    // 2. 徹底清空結果區域的 HTML
    document.getElementById('mcts_results').innerHTML = '';
    // 3. 重置相關變數
    currentMctsResults = [];
    currentMctsPage = 0;
}
</script>
```

### 為什麼之前的修改可能沒生效?
如果在執行 `runMCTS` 的過程中(非同步搜尋中)直接關閉視窗,舊的 `processNextChunk` 可能還在執行並在幾毫秒後又把結果填了進去。

為了徹底解決這個問題,建議在 `processNextChunk` 的開頭加入一個判斷:
**在 `function processNextChunk() { ... }` 內部第一行加入:**
```javascript
function processNextChunk() {
    // 如果視窗已經被關閉且不是自動模式,則停止搜尋,避免結果再次彈出
    if (!isAuto && document.getElementById('mcts_modal').style.display === 'none') {
        return; 
    }
    // ... 原本的邏輯 ...
}
```

這樣就能保證當用戶關閉視窗後,搜尋進程會立即終止,且下次打開時絕對不會看到舊的殘留結果。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

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