221 lines
4.9 KiB
JavaScript
221 lines
4.9 KiB
JavaScript
|
|
const WasmHost = (() => {
|
|
async function load(url, imports = {}) {
|
|
const res = await fetch(url);
|
|
if (!res.ok) {
|
|
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
}
|
|
const bytes = await res.arrayBuffer();
|
|
const result = await WebAssembly.instantiate(bytes, imports);
|
|
const instance = result.instance ?? result;
|
|
return { instance, exports: instance.exports };
|
|
}
|
|
|
|
function getFn(exportsObj, name) {
|
|
const fn = exportsObj[name];
|
|
if (typeof fn !== "function") {
|
|
throw new Error(`Export "${name}" is not a function`);
|
|
}
|
|
return fn;
|
|
}
|
|
|
|
return { load, getFn };
|
|
})();
|
|
|
|
const MoveResult = Object.freeze({
|
|
Normal: 0,
|
|
Check: 1,
|
|
Repeats: 2,
|
|
Stalemate: 3,
|
|
Checkmate: 4,
|
|
});
|
|
|
|
const _moveResultName = [
|
|
"Normal",
|
|
"Check",
|
|
"Repeats",
|
|
"Stalemate",
|
|
"Checkmate",
|
|
];
|
|
|
|
const Player = Object.freeze({
|
|
White: 0,
|
|
Black: 1,
|
|
None: 2,
|
|
});
|
|
|
|
const _playerName = [
|
|
"White",
|
|
"Black",
|
|
"None",
|
|
];
|
|
|
|
const Piece = Object.freeze({
|
|
Pawn: 0,
|
|
King: 1,
|
|
Queen: 2,
|
|
Bishop: 3,
|
|
Rook: 4,
|
|
Knight: 5,
|
|
Empty: 6,
|
|
});
|
|
|
|
const _pieceName = [
|
|
"Pawn",
|
|
"King",
|
|
"Queen",
|
|
"Bishop",
|
|
"Rook",
|
|
"Knight",
|
|
"Empty",
|
|
];
|
|
|
|
const _pieceChar = [
|
|
'P',
|
|
'K',
|
|
'Q',
|
|
'B',
|
|
'R',
|
|
'N',
|
|
' ',
|
|
];
|
|
|
|
const _pieceUnicode = [
|
|
['♙', '♔', '♕', '♗', '♖', '♘',], // white
|
|
['♟', '♚', '♛', '♝', '♜', '♞',], // black
|
|
['' , '', '', '', '', '', ], // none
|
|
];
|
|
|
|
const indexDeserialize = (n) => ({
|
|
rank: Math.floor(n / 8), // every day we stray further from God
|
|
file: (n % 8),
|
|
fileChar() { return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][this.file]; },
|
|
rankChar() { return ['1', '2', '3', '4', '5', '6', '7', '8'][this.rank]; },
|
|
});
|
|
|
|
|
|
const indexFromCoord = (rank, file) => (8*rank + file);
|
|
|
|
const indexSerialize = (index) => (indexFromCoord(index.rank, index.file));
|
|
|
|
const moveDeserialize = (n) => ({
|
|
appeal: Number((n >> 24n) & 0xFFn),
|
|
attr: Number((n >> 16n) & 0xFFn),
|
|
from: indexDeserialize(Number((n >> 8n) & 0xFFn)),
|
|
to: indexDeserialize(Number((n >> 0n) & 0xFFn)),
|
|
});
|
|
|
|
const moveSerialize = (move) => ((move.from & 0xFF) << 8) | ((move.to & 0xFF));
|
|
|
|
const squareDeserialize = (n) => ({
|
|
player: n == -1 ? Player.None : (n >> 8) & 0xFF,
|
|
piece: n == -1 ? Piece.Empty : (n & 0xFF),
|
|
playerName() {
|
|
if (this.player === Player.None) {
|
|
return "Empty";
|
|
} else {
|
|
return _playerName[this.player];
|
|
}
|
|
},
|
|
pieceName() {
|
|
if (this.player === Player.None) {
|
|
return "";
|
|
} else {
|
|
return this.playerName() + _pieceName[this.piece];
|
|
}
|
|
},
|
|
pieceUnicode() {
|
|
return _pieceUnicode[this.player][this.piece];
|
|
}
|
|
});
|
|
|
|
|
|
|
|
const run = async () => {
|
|
const { exports } = await WasmHost.load("./chess.wasm", {});
|
|
|
|
const wb_init = WasmHost.getFn(exports, "wb_init");
|
|
const wb_move = WasmHost.getFn(exports, "wb_move");
|
|
const wb_search = WasmHost.getFn(exports, "wb_search");
|
|
const wb_board_at = WasmHost.getFn(exports, "wb_board_at");
|
|
|
|
wb_init();
|
|
|
|
const drawBoard = () => {
|
|
const board = document.getElementById("board");
|
|
board.replaceChildren();
|
|
const filesChars = ["a","b","c","d","e","f","g","h"];
|
|
const ranksChars = ["8","7","6","5","4","3","2","1"];
|
|
|
|
for (let rank = 7; rank >= 0; --rank) {
|
|
for (let file = 0; file < 8; ++file) {
|
|
const square = document.createElement("div");
|
|
square.className =
|
|
"square " + ((file + rank) % 2 ? "dark" : "light");
|
|
square.dataset.file = filesChars[file];
|
|
square.dataset.rank = ranksChars[rank];
|
|
|
|
const x = wb_board_at(indexFromCoord(rank, file));
|
|
const sq = squareDeserialize(x);
|
|
square.textContent = sq.pieceUnicode();
|
|
|
|
board.appendChild(square);
|
|
}
|
|
}
|
|
setTimeout(() => {
|
|
// next task, browser had a chance to repaint
|
|
}, 0.01);
|
|
}
|
|
drawBoard();
|
|
|
|
const nextFrame = () => new Promise(requestAnimationFrame);
|
|
|
|
for (let i = 0; i < 200; ++i) {
|
|
drawBoard();
|
|
|
|
await nextFrame();
|
|
|
|
const m = wb_search(7);
|
|
const move = moveDeserialize(m);
|
|
|
|
console.log(move.from);
|
|
console.log(move.to);
|
|
|
|
const fromSqEnc = wb_board_at(indexSerialize(move.from));
|
|
const toSqEnc = wb_board_at(indexSerialize(move.to));
|
|
|
|
console.log(fromSqEnc);
|
|
console.log(toSqEnc);
|
|
|
|
const fromSq = squareDeserialize(fromSqEnc);
|
|
const toSq = squareDeserialize(toSqEnc);
|
|
|
|
console.log(fromSq)
|
|
console.log(toSq)
|
|
|
|
console.log("from-player:", fromSq.playerName());
|
|
console.log("from-piece:", fromSq.pieceName());
|
|
|
|
console.log("to-player:", toSq.playerName());
|
|
console.log("to-piece:", toSq.pieceName());
|
|
|
|
console.log(m);
|
|
const f = move.from;
|
|
const t = move.to;
|
|
console.log("from:", move.from.fileChar(), move.from.rankChar(),
|
|
"to:", move.to.fileChar(), move.to.rankChar());
|
|
|
|
const mr = wb_move(Number(m));
|
|
if (mr == MoveResult.Stalemate || mr == MoveResult.Checkmate) {
|
|
console.log(_moveResultName[mr]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
run().catch((err) => {
|
|
console.error(err);
|
|
});
|
|
|
|
|