代码: 全选
針對您提出的兩項需求,以下是精確的修改位置與替換代碼說明:
---
### 一、修復與改進「回退功能(Undo)」
**問題分析:**
原本的回退機制會在棋盤無棋子時直接禁止(`history.length <= 1`),使得第二局開始且未下子時無法退回第一局的結算畫面。
**修改邏輯:**
1. 允許在「當前對局歷史為 0(或只剩下初始狀態)」時,若存在上一局的紀錄(`gameHistory.length > 0`),則觸發跨局回退。
2. 退回到上一局的終局狀態,並重新跳出上一局的結算全屏視窗。
#### 1. 修改 `undoMove` 函式
請在 HTML 文件中搜尋 `function undoMove()`,將整段函式替換為:
```javascript
function undoMove() {
if (isAiThinking) return;
// 狀況 1:當前對局內有步驟可以回退
if (history.length > 1) {
history.pop();
const prevState = history[history.length - 1];
board = JSON.parse(JSON.stringify(prevState.board));
currentPlayer = prevState.player;
selectedCell = null;
lastMove = prevState.lastMove;
moveHistory = [...prevState.moveHistory];
renderBoard();
updateStatus();
updateMoveHistoryUI();
return;
}
// 狀況 2:當前對局已無步驟可退(場上0子),但有上一局的歷史紀錄
if (gameHistory.length > 0) {
// 移除上一局紀錄並恢復
const lastGameRecord = gameHistory.pop();
board = JSON.parse(JSON.stringify(lastGameRecord.finalBoard));
currentPlayer = lastGameRecord.finalPlayer;
selectedCell = null;
lastMove = lastGameRecord.lastMove;
moveHistory = [...lastGameRecord.moveHistory];
currentGameIndex = lastGameRecord.gameIndex;
scores = [...lastGameRecord.scores];
renderBoard();
updateStatus();
updateMoveHistoryUI();
// 重新彈出全屏勝負結果視窗
showWinnerModal(lastGameRecord.modalTitle, lastGameRecord.modalDetail);
}
}
```
#### 2. 在對局結束時保存回復所需的完整資訊
為了能準確退回到上一局的終局狀態(包含狀態與彈窗內容),請搜尋 `checkGameOver` 函式,找到呼叫 `showWinnerModal` 的地方,在儲存 `gameHistory` 時擴充保存的資料種類:
請搜尋以下類似代碼(在 `checkGameOver` 裡面):
```javascript
gameHistory.push({
board: JSON.parse(JSON.stringify(board)),
moveHistory: [...moveHistory]
});
```
將其替換為:
```javascript
let modalTitle = "";
let modalDetail = "";
if (scores[0] > scores[1]) {
modalTitle = "藍方 獲得最終勝利!";
} else if (scores[1] > scores[0]) {
modalTitle = "紅方 獲得最終勝利!";
} else {
modalTitle = "雙方平手!";
}
modalDetail = `比分:藍方 ${scores[0]} - 紅方 ${scores[1]}`;
// 保存上一局終局的完整狀態以供回退
gameHistory.push({
gameIndex: currentGameIndex,
scores: [...scores],
finalBoard: JSON.parse(JSON.stringify(board)),
finalPlayer: currentPlayer,
lastMove: lastMove,
moveHistory: [...moveHistory],
modalTitle: modalTitle,
modalDetail: modalDetail
});
showWinnerModal(modalTitle, modalDetail);
```
---
### 二、記譜格式改進(藍色棋子簡寫規則)
**問題分析:**
針對縱坐標為 `0` 的藍色棋子(通常記譜為三個字符,如 `C0B` 或 `e0d`),當縱坐標為 0 且橫坐標字母不全為 `a` / `A` 時,簡化為兩個橫坐標字母中字母序較小的一個(例如 `C` 與 `B` 取 `B`,`e` 與 `d` 取 `d`)。
#### 修改 `getNotation` 相關記譜轉換函式
請在 HTML 文件中搜尋負責轉換記譜的函式(例如 `getNotation` 或處理棋格座標轉記譜文字的邏輯區塊)。
尋找類似下面的記譜生成代碼並加以替換/修改:
```javascript
function formatCellNotation(cell) {
// 假設原本邏輯取得 col1, row, col2 (例如 'C', 0, 'B')
let c1 = cell.col1;
let r = cell.row;
let c2 = cell.col2;
// 新增判斷邏輯:藍色棋子 (縱坐標 row === 0)
if (r === 0) {
// 檢查是否橫坐標都不為 'a' 或 'A'
const isAllA = (c1.toLowerCase() === 'a' && c2.toLowerCase() === 'a');
if (!isAllA) {
// 取字母序較小者(例如 'C' 與 'B' 比較,取 'B')
const minCol = c1.localeCompare(c2) <= 0 ? c1 : c2;
return minCol;
}
}
// 其他情況維持原有的記譜格式輸出
return `${c1}${r}${c2}`;
}
```
*註:如果您的程式碼中是在落子紀錄 `moveHistory` 生成時直接拼湊字串,請將拼接邏輯改為呼叫上述邏輯,或將對應的字串處理改為:*
```javascript
// 在生成藍棋記譜字串的位置:
if (row === 0 && col1.toLowerCase() !== 'a' && col2.toLowerCase() !== 'a') {
notation = col1.localeCompare(col2) <= 0 ? col1 : col2;
} else {
notation = `${col1}${row}${col2}`;
}
```代码: 全选
這裡為你提供修復這兩個問題的具體代碼修改。
### 一、 修復「第二局回退」邏輯錯誤
原代碼在第二局開始階段處理 `historyIndex` 的邏輯有缺陷,導致回退時未能正確先回到第二局的空棋盤狀態,而是直接跳回了第一局。我們需要重構 `undoMove` 函數。
請找到代碼中的 `function undoMove()`,將整個函數替換為以下代碼:
```javascript
function undoMove() {
if (historyIndex >= 0) {
forceHumanPlayer(); // 中斷 AI 並切換為人類
document.getElementById('game-over-screen').style.display = 'none'; // 必定收起結算視窗
let roundStart = currentRound === 2 ? 36 : 0;
// 處理已退回到該局最開頭的情況
if (historyIndex < roundStart) {
// 如果是第二局的空棋盤,再次點擊回退則回到第一局終局狀態
if (currentRound === 2 && historyIndex === 35) {
currentRound = 1;
redrawFromHistory();
gameState = 'ended';
showRoundEnd();
}
return;
}
let move = moveHistory[historyIndex];
let scores = gameLogic.undo();
p1CurrentScore = scores.p1Score;
p2CurrentScore = scores.p2Score;
piecesLeft[move.player][move.pid]++;
historyIndex--;
let dropG = document.getElementById('etanidrop');
let tiles = dropG.querySelectorAll('.tiledropped');
if (tiles.length > 0) {
dropG.removeChild(tiles[tiles.length - 1]);
}
isFirstMove = historyIndex < roundStart;
currentPlayer = move.player;
gameState = 'playing'; // 強制恢復為遊玩狀態
updateUI();
}
}
```
---
### 二、 修復並改進記譜格式(藍色棋子 `y=0` 簡寫)
這部分需要修改兩處:一是導出時生成正確的簡寫字串,二是導入時能夠正確將單字母反向解析成完整的座標。
#### 1. 修改導出格式 (`formatExportNotation`)
請找到 `formatExportNotation` 函數中處理**藍色棋子**的分支 `if (y1 === y2) { ... }`,將這部分的 `else` 替換為支援 `y1 === 0` 簡寫的邏輯:
```javascript
if (y1 === y2) {
// 藍色棋子
let c1 = xToCharFormat(x1),
c2 = xToCharFormat(x2);
if (x1 === 0 && x2 === 1) {
// 坐標為 a 和 A
if (y1 > 0) return 'A' + y1;
else return 'a' + Math.abs(y1);
} else {
let chars = [c1, c2].sort((a, b) => {
let A = a.toLowerCase(),
B = b.toLowerCase();
if (A === B) return a < b ? -1 : 1;
return A < B ? -1 : 1;
});
if (y1 > 0) {
return chars[0] + y1 + chars[1]; // 字母順序
} else if (y1 < 0) {
return chars[1] + Math.abs(y1) + chars[0]; // 倒序
} else {
return chars[0]; // y1 === 0 時簡寫為字母序最小的一個
}
}
}
```
#### 2. 修改導入解析 (`parseImportNotation`)
為了讓導出的單一字母(例如 `B`, `d`)能夠被順利導入,請找到 `parseImportNotation` 函數,在拋出錯誤(`throw new Error('格式錯誤')`)的前面加上對應的正則表達式解析條件:
```javascript
function parseImportNotation(token) {
if (token === '0') return { t1_idx: 0, t1_N: 0, t2_idx: 1, t2_N: 0 };
let match;
if ((match = token.match(/^([Aa])(\d+)$/))) {
let y = parseInt(match[2]);
if (match[1] === 'A') return { t1_idx: 0, t1_N: y, t2_idx: 1, t2_N: y };
else return { t1_idx: 0, t1_N: -y, t2_idx: 1, t2_N: -y };
} else if ((match = token.match(/^([A-Za-z])(\d+)([A-Za-z])$/))) {
let c1 = match[1],
c2 = match[3];
let yAbs = parseInt(match[2]);
let chars = [c1, c2].sort((a, b) => {
let A = a.toLowerCase(),
B = b.toLowerCase();
if (A === B) return a < b ? -1 : 1;
return A < B ? -1 : 1;
});
let y = c1 === chars[0] ? yAbs : -yAbs;
return { t1_idx: charToXFormat(c1), t1_N: y, t2_idx: charToXFormat(c2), t2_N: y };
} else if ((match = token.match(/^(\d+)([A-Za-z])(\d+)$/))) {
let num1 = parseInt(match[1]),
num2 = parseInt(match[3]);
let x = charToXFormat(match[2]);
let y1, y2;
if (num1 < num2) {
y1 = num1;
y2 = num2;
} else {
y1 = -num1;
y2 = -num2;
}
return { t1_idx: x, t1_N: y1, t2_idx: x, t2_N: y2 };
} else if ((match = token.match(/^([A-Za-z])$/))) {
// 解析單字母簡寫(y=0 的藍色棋子)
let x1 = charToXFormat(match[1]);
// 透過遊戲網格奇偶性推導相鄰的另一半座標
let isRight = (0 === ((x1 - 1) & 1));
let x2 = isRight ? x1 - 1 : x1 + 1;
return { t1_idx: x1, t1_N: 0, t2_idx: x2, t2_N: 0 };
}
throw new Error('格式錯誤');
}
```