分页: 2 / 2

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

发表于 : 2026年 4月 10日 23:59
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` 之間。

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

发表于 : 2026年 4月 11日 00:16
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]]

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

发表于 : 2026年 4月 11日 01:06
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; // 達到限制深度,視為平局
}
```

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

发表于 : 2026年 4月 11日 01:18
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]]

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。

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

发表于 : 2026年 4月 11日 01:25
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]]

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

发表于 : 2026年 4月 11日 01:31
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)`);
        }
    });
```

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

发表于 : 2026年 4月 11日 02:01
ejsoon
konigsberg20260411toprandom7775

於是我做了個棋力超強的AI。
konigsberg20260411toprandom7775.7z
(11.69 KiB) 已下载 3 次