準備研究Wasm
发表于 : 2026年 7月 15日 08:19
代码: 全选
你所提到的 **"wrbassemble"** 應該是 **WebAssembly (Wasm)** 的拼寫誤植。
**皮卡魚 (Pikafish)** 是一款基於 Stockfish 架構開發的強力開源中國象棋引擎。因為它是由 C++ 編寫的,若想在瀏覽器(網頁端)運行,我們需要使用 **Emscripten** 工具鏈將其 C++ 源碼編譯為 WebAssembly(`.wasm` 和 `.js` 膠水代碼),然後透過 **Web Worker** 在前端非同步調用。
以下是完整的實作指南,從「編譯環境搭建」到「網頁前端調用」。
---
## 一、 將皮卡魚編譯為 WebAssembly (Wasm)
編譯 C++ 項目至 WebAssembly 最主流的工具是 **Emscripten (emcc)**。
### 1. 準備環境
首先,你需要安裝 Emscripten SDK (emsdk):
```bash
# 下載 emsdk
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# 安裝並激活最新版(建議配合皮卡魚源碼要求,通常最新穩定版即可)
./emsdk install latest
./emsdk activate latest
# 激活環境變量(每次新開終端都需要執行)
source ./emsdk_env.sh
```
### 2. 獲取皮卡魚源碼
```bash
git clone https://github.com/official-pikafish/Pikafish.git
cd Pikafish/src
```
### 3. 使用 Emscripten 進行編譯
因為皮卡魚繼承自 Stockfish,其 `Makefile` 通常已經支持交叉編譯,或者你可以直接使用 `emcc` 命令手動編譯核心代碼。
最簡單、最直接的手動編譯命令如下(在 `src` 目錄下執行):
```bash
emcc -O3 -std=c++17 \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_FUNCTIONS="['_main']" \
-s EXIT_RUNTIME=0 \
-s MODULARIZE=1 \
-s EXPORT_NAME="Pikafish" \
*.cpp -o pikafish.js
```
> **💡 說明:**
> * `-O3`:開啟最優化,提升 Wasm 運行效率。
> * `-s WASM=1`:輸出為 WebAssembly 格式。
> * `-s MODULARIZE=1` 與 `-s EXPORT_NAME="Pikafish"`:將輸出的 JS 包裝為一個模組,方便前端載入。
> * 執行後會生成兩個文件:`pikafish.js`(膠水代碼)與 `pikafish.wasm`(核心二進制檔)。
>
>
---
## 二、 網頁程式調用 (HTML + JavaScript)
象棋引擎計算非常消耗 CPU。如果直接在瀏覽器主線程運行,網頁會直接卡死。因此,**必須使用 Web Worker 在後台線程運行皮卡魚**。
### 1. 建立 Web Worker 腳本 (`pikafish-worker.js`)
這個文件負責在後台線程載入 Wasm,並透過 **UCI 協議**(象棋引擎通用通訊協議)與主線程通訊。
```javascript
// 引入編譯好的膠水 JS
importScripts('pikafish.js');
let engine;
// 初始化 Wasm 引擎
Pikafish().then(instance => {
engine = instance;
postMessage({ type: 'ready', data: 'Pikafish Wasm Loaded!' });
});
// 監聽來自網頁主線程的指令 (UCI 協議)
onmessage = function (e) {
const cmd = e.data;
console.log("收到主線程指令: ", cmd);
// 將指令傳送給 C++ 的 standard input
if (engine && typeof engine.ccall === 'function') {
// 假設編譯時將輸入接口導出,或通過內置 stdin 處理
// 註:Stockfish 家族通常會重定向 stdin/stdout
}
// 這裡模擬引擎計算並回傳結果給主線程
// 實際完整對接中,Emscripten 會自動將引擎的 stdout 透過 print 函數導出
};
// 重定向 Emscripten 的輸出到主線程
var Module = {
print: function(text) {
postMessage({ type: 'stdout', data: text });
},
printErr: function(text) {
postMessage({ type: 'stderr', data: text });
}
};
```
### 2. 建立前端網頁 (`index.html`)
這是用戶交互的介面,用來向 Worker 發送 UCI 指令(例如 `uci`、`isready`、開始計算 `go depth 10` 等),並接收皮卡魚返回的評估分數與最佳著法(bestmove)。
```html
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>皮卡魚 WebAssembly 網頁測試</title>
<style>
body { font-family: sans-serif; padding: 20px; }
#log { width: 100%; height: 300px; background: #222; color: #0f0; padding: 10px; overflow-y: scroll; font-family: monospace; }
button { padding: 10px 20px; margin-right: 10px; cursor: pointer; }
</style>
</head>
<body>
<h2>皮卡魚 (Pikafish) Wasm 引擎主控台</h2>
<button id="btn-init">1. 初始化引擎</button>
<button id="btn-isready" disabled>2. 檢查準備狀態 (isready)</button>
<button id="btn-go" disabled>3. 開始計算最佳著法 (go depth 5)</button>
<h3>引擎輸出日誌:</h3>
<div id="log"></div>
<script>
const logDiv = document.getElementById('log');
const btnInit = document.getElementById('btn-init');
const btnIsReady = document.getElementById('btn-isready');
const btnGo = document.getElementById('btn-go');
let pikafishWorker = null;
function log(msg) {
logDiv.innerHTML += msg + '<br>';
logDiv.scrollTop = logDiv.scrollHeight;
}
// 1. 初始化 Worker
btnInit.onclick = () => {
log("正在加載 Worker 及 WebAssembly...");
pikafishWorker = new Worker('pikafish-worker.js');
// 監聽 Worker 返回的消息
pikafishWorker.onmessage = function (event) {
const { type, data } = event.data;
if (type === 'ready') {
log(`系統提示: ${data}`);
btnIsReady.disabled = false;
} else {
log(`[Pikafish] ${data}`);
}
};
btnInit.disabled = true;
};
// 2. 發送 isready 握手指令
btnIsReady.onclick = () => {
if (pikafishWorker) {
log("發送指令: uci 和 isready");
pikafishWorker.postMessage('uci');
pikafishWorker.postMessage('isready');
btnGo.disabled = false;
}
};
// 3. 讓引擎計算當前局面
btnGo.onclick = () => {
if (pikafishWorker) {
log("設定初始局面並計算 5 步深度的最佳走法...");
// position startpos 為象棋起手式,go depth 5 限制計算深度為 5 層
pikafishWorker.postMessage('position startpos');
pikafishWorker.postMessage('go depth 5');
}
};
</script>
</body>
</html>
```
---
## 💡 關鍵開發提示:
1. **NNUE 權重文件 (Neural Network):** 皮卡魚是一個 **NNUE** 強化的象棋引擎。Wasm 運行時需要加載 `.nnue` 權重文件才能發揮最大棋力。你可以利用 Emscripten 的 `--preload-file` 參數,將 `.nnue` 權重打包到虛擬文件系統中,或者在 JavaScript 端手動下載後傳給 WebAssembly。
2. **同源策略 (CORS) 限制:** 由於瀏覽器的安全限制,Web Worker **無法透過本地雙擊 `index.html` 運行**。你必須使用本地伺服器(例如 VS Code 的 Live Server 插件,或是執行 `python3 -m http.server 8000`)來託管運行網頁。