為何在跟「隨機局面」取局面數據時會出現最後一個數字(即最後行動棋子)為null?如果沒有最後行動棋子,或者剛開局,應統一為0,而不是null,請修正。
代码: 全选
這是一個很細微但關鍵的 Bug。在 JavaScript 中,當你將 `null` 置於字串範本(Template Literals)中時,它會被自動轉化為字串 `"null"`。由於 `ChaosClock` 初始狀態或隨機生成盤面後,若尚未有人移動,`lastMovedPiece` 的預設值是 `null`,這就導致了輸出的字串變成了 `...;1;null`。
以下是修正後的 `CrackChaosClock` 類別,我已經針對 `null` 值處理、`posValue` 整合以及自動掛載邏輯進行了優化。
### 修正後的 CrackChaosClock 實作
```javascript
class CrackChaosClock {
constructor(mainGameInstance) {
this.game = mainGameInstance;
// 確保遊戲 UI 已經存在才掛載
if (this.game.uiContainer) {
this.initUI();
}
}
/**
* 移植自 C++ 的局面評分
*/
posValue(bc) {
let sum = 0;
for (let i = 0; i < 12; i++) {
const p = Number((bc >> BigInt(i * 4)) & 0xFn);
if (p === i + 1) {
// C++ 邏輯: 2^(i/2)
sum += (1 << Math.floor(i / 2));
}
}
return sum;
}
/**
* 核心轉換:將局面字串轉為 BigInt (64-bit)
*/
ckvalueToBigInt(ckvalue) {
const segments = ckvalue.split(';');
const board = segments[0].split(',').map(Number);
// 處理回合:預設 1 (乙方)
const turn = (segments[1] === '0') ? 0 : 1;
// 修正點:處理 'null' 字串或 undefined,確保為數字 0
let lastMoved = 0;
if (segments[2] && segments[2] !== 'null') {
lastMoved = parseInt(segments[2]) || 0;
}
// 處理評分 (位元 60-63)
const score = parseInt(segments[3]) || 0;
let val = 0n;
// 位元 0-47: 盤面
for (let i = 0; i < 12; i++) {
val |= (BigInt(board[i] || 0) << BigInt(i * 4));
}
// 位元 48: 回合
val |= (BigInt(turn) << 48n);
// 位元 49-52: 最後動作棋子
val |= (BigInt(lastMoved & 0xF) << 49n);
// 位元 60-63: 評分
val |= (BigInt(score & 0xF) << 60n);
return val;
}
handleConvert() {
let inputStr = this.input.value.trim();
if (!inputStr) {
// 從當前 ChaosClock 實例抓取即時數據
const b = this.game.board.map(v => v || 0).join(',');
const t = this.game.turn;
// 修正點:這裡強制將 null 轉為 0
const l = this.game.lastMovedPiece === null ? 0 : this.game.lastMovedPiece;
inputStr = `${b};${t};${l}`;
this.input.value = inputStr;
}
const bigIntValue = this.ckvalueToBigInt(inputStr);
const scoreValue = this.posValue(bigIntValue);
this.renderDisplay(bigIntValue, scoreValue);
}
initUI() {
const container = document.createElement('div');
container.style.cssText = 'margin-top: 20px; padding: 15px; border: 2px solid #4CAF50; border-radius: 10px; background: #fff; box-shadow: 0 4px 6px rgba(0,0,0,0.1);';
container.innerHTML = `
<h4 style="margin-top:0; color:#2E7D32;">🧩 CrackChaosClock 局面解析</h4>
<div style="display:flex; gap:10px; margin-bottom:10px;">
<input type="text" id="ck-input-${this.game.initialPlayer}" placeholder="ckvalue: 12,5...;0;3" style="flex-grow:1; padding:8px; border:1px solid #ccc; border-radius:4px;">
<button id="ck-btn-${this.game.initialPlayer}" style="padding:8px 15px; cursor:pointer; background:#4CAF50; color:white; border:none; border-radius:4px;">解析並轉換</button>
</div>
<div id="ck-output-${this.game.initialPlayer}" style="font-family: 'Courier New', monospace; font-size:12px; background:#f4f4f4; padding:10px; border-radius:4px; min-height:60px; line-height:1.4;">
等待解析...
</div>
`;
this.game.uiContainer.appendChild(container);
this.input = container.querySelector('input');
this.bitOutput = container.querySelector('div[id^="ck-output"]');
container.querySelector('button').onclick = () => this.handleConvert();
}
renderDisplay(bigInt, posScore) {
let display = `<div style="margin-bottom:8px;"><strong style="color:#d32f2f;">PosValue (權重分): ${posScore}</strong></div>`;
display += `<strong>Binary Bits (00-63):</strong><br>`;
for (let i = 0; i < 64; i++) {
const bit = (bigInt >> BigInt(i)) & 1n;
let color = "#999";
if (i < 48) color = "#1976D2"; // 盤面
if (i === 48) color = "#E91E63"; // 回合
if (i >= 49 && i <= 52) color = "#2E7D32"; // 最後棋子
if (i >= 60) color = "#FF9800"; // 評分
display += `<span style="color:${color}">${bit}</span>`;
if ((i + 1) % 4 === 0) display += ' ';
if ((i + 1) % 16 === 0) display += '<br>';
}
display += `<div style="margin-top:8px; font-size:10px; color:#666;">BigInt: ${bigInt.toString()}</div>`;
this.bitOutput.innerHTML = display;
}
}
```
---
### 如何使每一個 `ChaosClock` 下方都有一個 `CrackChaosClock`?
要達成這個自動化效果,最簡單的方法是在 `ChaosClock` 的構造函數或初始化流程結束後,直接 new 一個助手。
請將以下程式碼加在你的 `chaosclockgame202604221531.js` 檔案最後面(或者是這兩個類別宣告完畢後的任何地方):
```javascript
// 使用 AOP (面向切面) 的方式,攔截 ChaosClock 的初始化
const originalInit = ChaosClock.prototype.init || ChaosClock.prototype.setupGame;
// 假設原本的檔案是用 setupGame 或類似名稱作為 UI 生成的結尾
// 我們這裡針對 ChaosClock 的原型進行擴展
ChaosClock.prototype.initApp = function() {
// 呼叫原本的初始化邏輯
// (這裡請根據你實際代碼中啟動 UI 的 function 名稱修改)
// 實例化助手,並將當前的 ChaosClock (this) 傳進去
this.cracker = new CrackChaosClock(this);
};
// 如果你是在頁面手動 new ChaosClock,只需在後面加一行:
// let game = new ChaosClock(el);
// game.cracker = new CrackChaosClock(game);
```
### 修正說明:
1. **Null 預防**:在 `handleConvert` 抓取數據時,加入了 `this.game.lastMovedPiece === null ? 0 : ...` 的判斷,確保輸出的字串中不會出現 `null` 字樣。
2. **健壯性**:在 `ckvalueToBigInt` 中加入了對 `segments[2] !== 'null'` 的字串檢查,防止手動輸入字串包含 `null` 時導致 `parseInt` 失敗。
3. **自動化 UI**:每個 `CrackChaosClock` 都會根據傳入的 `game` 實例,將自己的面板 `appendChild` 到該遊戲的 `uiContainer` 裡,實現「一對一」的綁定。