修復和改進
一,語言顯示問題
在AI思考時的消息框中的文字,應該都要改成當前的語言,尤其是做MCTS搜尋時。比如MCTS應該翻譯成「蒙地卡羅樹搜尋」,time是「用時」,等等。
二,棋譜中的棋子序號仍然不對
由於一局總共只有54個棋子,所以序號不應超出54。而棋譜中仍然會出現超過54的棋子,再次導入則會發生錯誤。
原因可能是當把落下的棋子拾回,再放下,它同棋子id就會增長。應把本回合新增的兩個棋子id等於「場上棋子總數」和「場上棋子總數+1」。同時檢查還有沒有其它可能原因。
三,AI會主動送分
AI會有可能落下一子,形成的頂鑫結構是在給對方加分,這是不對的。只有在「給對方加分的同時自己能加更多的分」的情況下,才能給對方主動加分。
檢查代碼,分析原因並修復。
回答要求:
明確給出所要修改的地方和所要替換的代碼。
代码: 全选
修復和改進
一,語言顯示問題
在AI思考時的消息框中的文字,應該都要改成當前的語言,尤其是做MCTS搜尋時。比如MCTS應該翻譯成「蒙地卡羅樹搜尋」,time是「用時」,等等。
二,棋譜中的棋子序號仍然不對
由於一局總共只有54個棋子,所以序號不應超出54。而棋譜中仍然會出現超過54的棋子,再次導入則會發生錯誤。
原因可能是當把落下的棋子拾回,再放下,它同棋子id就會增長。應把本回合新增的兩個棋子id等於「場上棋子總數」和「場上棋子總數+1」。同時檢查還有沒有其它可能原因。
三,AI會主動送分
AI會有可能落下一子,形成的頂鑫結構是在給對方加分,這是不對的。只有在「給對方加分的同時自己能加更多的分」的情況下,才能給對方主動加分。
檢查代碼,分析原因並修復。
回答要求:
明確給出所要修改的地方和所要替換的代碼。
gemini 3.1 pro + thinking
代码: 全选
這三個問題分別涉及到 UI 多語言適配、遊戲資料結構的一致性,以及 AI 評估函數的邏輯缺陷。
以下是針對這三個問題的修復方案與替換代碼。
### 一、語言顯示問題
需要將 AI 思考中(尤其是 Minimax 與 MCTS 搜尋時)輸出的進度訊息支援雙語切換。
**請找到 `startAI()` 函數中的 Minimax 區塊:**
```javascript
if (performance.now() - startTime > 100) {
showMessage(
`Minimax Depth: ${maxDepth} | Time: ${((performance.now() - startTime) / 1000).toFixed(1)}s`,
0,
true
);
await new Promise(r => setTimeout(r, 0));
}
```
**替換為:**
```javascript
if (performance.now() - startTime > 100) {
let timeStr = ((performance.now() - startTime) / 1000).toFixed(1);
let msg = currentLang === 'zh'
? `極小化極大演算法 | 深度: ${maxDepth} | 用時: ${timeStr}秒`
: `Minimax | Depth: ${maxDepth} | Time: ${timeStr}s`;
showMessage(msg, 0, true);
await new Promise(r => setTimeout(r, 0));
}
```
**接著,在同一個函數內找到 MCTS 區塊:**
```javascript
// UI 更新頻率控制
if (searchCount % 10 === 0) {
showMessage(
`MCTS | Time: ${((performance.now() - startTime) / 1000).toFixed(1)}s | Searches: ${searchCount}`,
0,
true
);
await new Promise(r => setTimeout(r, 0)); // 讓出主執行緒避免卡頓
}
```
**替換為:**
```javascript
// UI 更新頻率控制
if (searchCount % 10 === 0) {
let timeStr = ((performance.now() - startTime) / 1000).toFixed(1);
let msg = currentLang === 'zh'
? `蒙地卡羅樹搜尋 | 用時: ${timeStr}秒 | 搜尋次數: ${searchCount}`
: `MCTS | Time: ${timeStr}s | Searches: ${searchCount}`;
showMessage(msg, 0, true);
await new Promise(r => setTimeout(r, 0)); // 讓出主執行緒避免卡頓
}
```
**最後找到搜尋結束的提示:**
```javascript
let totalTime = ((performance.now() - startTime) / 1000).toFixed(1);
let valStr = useMinimax ? `Score: ${expectedValue}` : `WinRate: ${(expectedValue * 100).toFixed(1)}%`;
showMessage(`Done | Time: ${totalTime}s | Searches: ${searchCount} | ${valStr}`, 3000, true);
```
**替換為:**
```javascript
let totalTime = ((performance.now() - startTime) / 1000).toFixed(1);
let valStr = useMinimax
? (currentLang === 'zh' ? `分數: ${expectedValue}` : `Score: ${expectedValue}`)
: (currentLang === 'zh' ? `勝率: ${(expectedValue * 100).toFixed(1)}%` : `WinRate: ${(expectedValue * 100).toFixed(1)}%`);
let msg = currentLang === 'zh'
? `完成 | 用時: ${totalTime}秒 | 搜尋次數: ${searchCount} | ${valStr}`
: `Done | Time: ${totalTime}s | Searches: ${searchCount} | ${valStr}`;
showMessage(msg, 3000, true);
```
---
### 二、棋子序號 (ID) 超出 54 的問題
由於頻繁拾起、放下、以及生成預覽殘影都會消耗 `nextPieceId` 計數器,導致最終產生的序號超出理論上限。我們應該在按下打勾(`actionCheck`)真正確認落子時,**嚴格根據當前場上棋子總數 (`pieces.length`) 強制重新編號**,並徹底廢除全域變數 `nextPieceId`。
**1. 刪除全域變數 `nextPieceId` 及其相關重置邏輯**
請在代碼中**刪除**以下包含 `nextPieceId` 的行(共 4 處):
* 全域宣告:`let nextPieceId = 1;`
* `resetGame()` 內:`nextPieceId = 1;`
* `undo()` 內:`nextPieceId = pieces.length + 1;`
* `redo()` 內:`nextPieceId = pieces.length + 1;`
**2. 替換 `commitGhost` 中的 ID 賦值**
```javascript
gp.id = nextPieceId++; // 二、改用計數器確保唯一,避免拾回重下後與另一暫存棋子撞號
```
**替換為:**
```javascript
let maxId = pieces.length;
for (let tp of tempPieces) { if (tp.id > maxId) maxId = tp.id; }
gp.id = maxId + 1;
```
**3. 替換所有其它 `id: nextPieceId++` 或 `g.id = nextPieceId++**`
請在以下五個函數中,將對應的 `nextPieceId++` 替換為 `pieces.length + 1` 或 `pieces.length + 2`:
* `placeAIFirstMove` 內:`id: pieces.length + 1,`
* `placeAISingleMove` 內:`g.id = pieces.length + 1;`
* `startAI` 內:將 `g1.id = nextPieceId++; g2.id = nextPieceId++;` 替換為 `g1.id = pieces.length + 1; g2.id = pieces.length + 2;`
* `selectTile` (首回合邏輯) 內:`id: pieces.length + 1,`
* `confirmImport` (首回合) 內:`id: pieces.length + 1,`
* `confirmImport` (後續回合) 內:
```javascript
let maxId = pieces.length;
for (let tp of tempPieces) { if (tp.id > maxId) maxId = tp.id; }
let p = {
id: maxId + 1,
```
**4. 在 `actionCheck` 中強制重編號(最關鍵的一步)**
**請找到 `actionCheck(that = false)` 函數:**
```javascript
function actionCheck(that = false) {
if (that && that.classList.contains('disabled')) return;
allRings = [];
```
**替換為:**
```javascript
function actionCheck(that = false) {
if (that && that.classList.contains('disabled')) return;
// 嚴格依照場上已有棋子總數重新編號,確保送入 pieces 與棋譜的 ID 連續且不超過上限
for (let i = 0; i < tempPieces.length; i++) {
tempPieces[i].id = pieces.length + i + 1;
}
allRings = [];
```
---
### 三、AI 主動送分的問題
原本在步驟四 (保底落子) 時,AI 只是隨機挑選了任意不卡死對方的下法,並未把這些保底下法送進 Minimax/MCTS 去驗算,導致有極大機率亂下並送分。同時,在單子判斷中,過濾條件也不夠嚴謹。
**1. 修改步驟二的單子過濾條件**
**請找到 `getScoringSingleCandidates` 函數內的判斷:**
```javascript
if (gain[player] > 0 && gain[player] >= gain[opp]) {
result.push(g);
}
```
**替換為(加上給對方加分時,自己必須加更多分的限制):**
```javascript
if (gain[player] > 0 && (gain[opp] === 0 || gain[player] > gain[opp])) {
result.push(g);
}
```
**2. 將隨機保底落子改為過濾送分下法,並全數交給搜尋引擎**
**請找到整個 `pickRandomSameTargetMove` 函數,並將它替換為新的過濾函數:**
```javascript
async function getFilteredSameTargetMoves(sameTargetMoves, player, opp) {
let safeMoves = [];
for (let idx = 0; idx < sameTargetMoves.length; idx++) {
if (cancelAi) break;
let m = sameTargetMoves[idx];
let gc1 = Object.assign({}, m[0], { id: -70001 });
let gc2 = Object.assign({}, m[1], { id: -70002 });
let gain = simulateScoreGain([gc1, gc2]);
// 不主動送分:若給對方加分的同時,己方加分沒有大於對方,則拋棄此下法
if (gain[opp] > 0 && gain[player] <= gain[opp]) {
continue;
}
if (hasLegalContinuation([m[0], m[1]], opp)) {
safeMoves.push(m);
}
await aiMaybeYield(
currentLang === 'zh'
? `AI 思考中:步驟四/從同一棋子下法中篩選保底選項 (${idx + 1}/${sameTargetMoves.length})`
: `AI thinking: Step4/filtering fallback same-target moves (${idx + 1}/${sameTargetMoves.length})`
);
}
// 如果安全且不送分的下法全部被濾光了,退而求其次只保證不會死局(避免AI無法下子卡死)
if (safeMoves.length === 0) {
for (let idx = 0; idx < sameTargetMoves.length; idx++) {
if (cancelAi) break;
let m = sameTargetMoves[idx];
if (hasLegalContinuation([m[0], m[1]], opp)) safeMoves.push(m);
}
}
return safeMoves.length > 0 ? safeMoves : sameTargetMoves;
}
```
**3. 更新 `startAI()` 使用上述新函數**
**請找到 `startAI()` 內原本的步驟四區塊:**
```javascript
let validMoves;
if (exceptionMoves.length > 0) {
// 有一種以上能形成頂鑫結構的下法,全部往下走,交給 Minimax / MCTS 搜尋比較最終分數最高者
validMoves = exceptionMoves;
} else {
// ===== 步驟四:都無法形成頂鑫結構 → 隨機挑選一種「落在同一對方棋子」的下法 =====
let oppPlayerNum = currentPlayer === 1 ? 2 : 1;
validMoves = [await pickRandomSameTargetMove(sameTargetMoves, oppPlayerNum)];
}
```
**替換為(這樣一來,大量保底下法也會送進 MCTS/Minimax 進行智力評估):**
```javascript
let validMoves;
let oppPlayerNum = currentPlayer === 1 ? 2 : 1;
if (exceptionMoves.length > 0) {
// 有一種以上能形成頂鑫結構的下法,全部往下走,交給 Minimax / MCTS 搜尋比較最終分數最高者
validMoves = exceptionMoves;
} else {
// ===== 步驟四:都無法形成頂鑫結構 → 從「落在同一對方棋子」的下法中篩選 =====
let candidateMoves = await getFilteredSameTargetMoves(sameTargetMoves, currentPlayer, oppPlayerNum);
// 為了防止 Minimax 在選項過多時跑太久卡死,打亂並取前 20 種;MCTS 無此限制則全拿
if (useMinimax && candidateMoves.length > 20) {
candidateMoves = candidateMoves.sort(() => Math.random() - 0.5).slice(0, 20);
}
validMoves = candidateMoves;
}
```