Fix web layer
This commit is contained in:
4
Makefile
4
Makefile
@@ -24,8 +24,8 @@ wasm: chess.wasm
|
|||||||
codegen: codegen.c
|
codegen: codegen.c
|
||||||
$(CC) -o $@ $(CFLAGS) $^
|
$(CC) -o $@ $(CFLAGS) $^
|
||||||
|
|
||||||
chess.wasm: wasm-compat-chatgpt.c mbb_rook.h mbb_bishop.h engine.h
|
chess.wasm: wasm-compat.c mbb_rook.h mbb_bishop.h engine.h
|
||||||
$(CC) -DWASM -o $@ wasm-compat-chatgpt.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm)
|
$(CC) -DWASM -o $@ wasm-compat.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm)
|
||||||
|
|
||||||
mbb_rook.h: codegen
|
mbb_rook.h: codegen
|
||||||
./codegen
|
./codegen
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
let exports;
|
let exports;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const resp = await fetch("./chess.wasm");
|
const resp = await fetch("./chess.wasm?rand=" + crypto.randomUUID());
|
||||||
if (!resp.ok) throw new Error(`fetch wasm failed ${resp.status} ${resp.statusText}`);
|
if (!resp.ok) throw new Error(`fetch wasm failed ${resp.status} ${resp.statusText}`);
|
||||||
|
|
||||||
const { instance } = await WebAssembly.instantiateStreaming(resp, {});
|
const { instance } = await WebAssembly.instantiateStreaming(resp, {});
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="board"></div>
|
<div id="board"></div>
|
||||||
<p id="result"></div>
|
<p id="result"></p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
375
chess.js
375
chess.js
@@ -1,5 +1,7 @@
|
|||||||
|
/* this code is complete garbage but it's mostly for testing anyways */
|
||||||
|
|
||||||
const MoveResult = Object.freeze({
|
const MoveResult = Object.freeze({
|
||||||
|
ILLEGAL: -1,
|
||||||
NORMAL: 0,
|
NORMAL: 0,
|
||||||
CHECK: 1,
|
CHECK: 1,
|
||||||
REPEATS: 2,
|
REPEATS: 2,
|
||||||
@@ -15,65 +17,44 @@ const _moveResultName = [
|
|||||||
"Checkmate",
|
"Checkmate",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const moveResultName = (mr) => {
|
||||||
|
if (mr === MoveResult.ILLEGAL) return "Illegal";
|
||||||
|
return _moveResultName[mr] ?? `Unknown(${mr})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTerminal = (mr) => (mr === MoveResult.STALEMATE || mr === MoveResult.CHECKMATE);
|
||||||
|
|
||||||
const Player = Object.freeze({
|
const Player = Object.freeze({
|
||||||
White: 0,
|
White: 0,
|
||||||
Black: 1,
|
Black: 1,
|
||||||
None: 2,
|
None: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const _playerName = [
|
|
||||||
"White",
|
|
||||||
"Black",
|
|
||||||
"None",
|
|
||||||
];
|
|
||||||
|
|
||||||
const Piece = Object.freeze({
|
const Piece = Object.freeze({
|
||||||
Pawn: 0,
|
Empty: 0,
|
||||||
King: 1,
|
Pawn: 1,
|
||||||
Queen: 2,
|
Knight: 2,
|
||||||
Bishop: 3,
|
Bishop: 3,
|
||||||
Rook: 4,
|
Rook: 4,
|
||||||
Knight: 5,
|
Queen: 5,
|
||||||
Empty: 6,
|
King: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
const _pieceName = [
|
|
||||||
"Pawn",
|
|
||||||
"King",
|
|
||||||
"Queen",
|
|
||||||
"Bishop",
|
|
||||||
"Rook",
|
|
||||||
"Knight",
|
|
||||||
"Empty",
|
|
||||||
];
|
|
||||||
|
|
||||||
const _pieceChar = [
|
|
||||||
'P',
|
|
||||||
'K',
|
|
||||||
'Q',
|
|
||||||
'B',
|
|
||||||
'R',
|
|
||||||
'N',
|
|
||||||
' ',
|
|
||||||
];
|
|
||||||
|
|
||||||
const _pieceUnicode = [
|
const _pieceUnicode = [
|
||||||
['♙', '♔', '♕', '♗', '♖', '♘',], // white
|
[' ', '♙', '♘', '♗', '♖', '♕', '♔',], // white
|
||||||
['♟', '♚', '♛', '♝', '♜', '♞',], // black
|
[' ', '♟', '♞', '♝', '♜', '♛', '♚',], // black
|
||||||
['' , '', '', '', '', '', ], // none
|
[' ', ' ', ' ', ' ', ' ', ' ', ' ',], // none
|
||||||
];
|
];
|
||||||
|
|
||||||
const indexDeserialize = (n) => ({
|
const indexDeserialize = (n) => ({
|
||||||
rank: Math.floor(n / 8), // every day we stray further from God
|
rank: Math.floor(n / 8),
|
||||||
file: (n % 8),
|
file: (n % 8),
|
||||||
fileChar() { return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][this.file]; },
|
fileChar() { return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][this.file]; },
|
||||||
rankChar() { return ['1', '2', '3', '4', '5', '6', '7', '8'][this.rank]; },
|
rankChar() { return ['1', '2', '3', '4', '5', '6', '7', '8'][this.rank]; },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const serializedIndexFromCoord = (rank, file) => (8 * rank + file);
|
||||||
const serializedIndexFromCoord = (rank, file) => (8*rank + file);
|
const indexSerialize = (index) => serializedIndexFromCoord(index.rank, index.file);
|
||||||
|
|
||||||
const indexSerialize = (index) => (serializedIndexFromCoord(index.rank, index.file));
|
|
||||||
|
|
||||||
const moveDeserialize = (n) => ({
|
const moveDeserialize = (n) => ({
|
||||||
appeal: Number((n >> 24n) & 0xFFn),
|
appeal: Number((n >> 24n) & 0xFFn),
|
||||||
@@ -83,80 +64,50 @@ const moveDeserialize = (n) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const moveSerialize = (move) => (
|
const moveSerialize = (move) => (
|
||||||
(indexSerialize(move.from) & 0xFF) << 8) | ((indexSerialize(move.to) & 0xFF));
|
((indexSerialize(move.from) & 0xFF) << 8) | (indexSerialize(move.to) & 0xFF)
|
||||||
|
);
|
||||||
|
|
||||||
const squareDeserialize = (n) => ({
|
const squareDeserialize = (n) => {
|
||||||
player: n == -1 ? Player.None : (n >> 8) & 0xFF,
|
if (n === -1) {
|
||||||
piece: n == -1 ? Piece.Empty : (n & 0xFF),
|
return {
|
||||||
playerName() {
|
player: Player.None,
|
||||||
if (this.player === Player.None) {
|
piece: Piece.Empty,
|
||||||
return "Empty";
|
unicode: ' ',
|
||||||
} 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 WasmBoard = async (url) => {
|
const player = (n >> 8) & 0xFF;
|
||||||
/*
|
const piece = n & 0xFF;
|
||||||
const res = await fetch(url);
|
const unicode = _pieceUnicode[player][piece];
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
||||||
}
|
|
||||||
const bytes = await res.arrayBuffer();
|
|
||||||
const instantiated = await WebAssembly.instantiate(bytes, {});
|
|
||||||
const instance = instantiated.instance ?? instantiated;
|
|
||||||
const exports = instance.exports;
|
|
||||||
|
|
||||||
const wb_init = exports.wb_init;
|
return {
|
||||||
const wb_move = exports.wb_move;
|
player: player,
|
||||||
const wb_search = exports.wb_search;
|
piece: piece,
|
||||||
const wb_board_at = exports.wb_board_at;
|
unicode: unicode,
|
||||||
*/
|
};
|
||||||
const worker = new Worker("./chess-worker.js", { type: "module" });
|
};
|
||||||
|
|
||||||
|
const WasmBoard = async () => {
|
||||||
|
const worker = new Worker("./chess-worker.js?rand=" + crypto.randomUUID(), { type: "module" });
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
worker.addEventListener("message", (e) => {
|
worker.addEventListener("message", (e) => {
|
||||||
if (e.data?.type === "ready") {
|
if (e.data?.type === "ready") resolve();
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
worker.addEventListener("error", reject, { once: true });
|
worker.addEventListener("error", reject, { once: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const wasmCall = (method, args = []) => new Promise((resolve, reject) => {
|
const wasmCall = (method, args = []) => new Promise((resolve, reject) => {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
const onMessage = (e) => {
|
const onMessage = (e) => {
|
||||||
if (e.data?.id !== id) {
|
if (e.data?.id !== id) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
cleanup();
|
cleanup();
|
||||||
if (e.data.ok) {
|
if (e.data.ok) resolve(e.data.value);
|
||||||
resolve(e.data.value);
|
else reject(new Error(e.data.error));
|
||||||
} else {
|
|
||||||
reject(new Error(e.data.error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onError = (err) => {
|
|
||||||
cleanup();
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMessageError = (e) => {
|
|
||||||
cleanup();
|
|
||||||
reject(new Error("Worker message deserialization failed (messageerror)."));
|
|
||||||
};
|
};
|
||||||
|
const onError = (err) => { cleanup(); reject(err); };
|
||||||
|
const onMessageError = () => { cleanup(); reject(new Error("Worker messageerror")); };
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
worker.removeEventListener("message", onMessage);
|
worker.removeEventListener("message", onMessage);
|
||||||
@@ -174,96 +125,202 @@ const WasmBoard = async (url) => {
|
|||||||
await wasmCall("wb_init", []);
|
await wasmCall("wb_init", []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: MoveResult.Normal,
|
state: MoveResult.NORMAL,
|
||||||
|
|
||||||
ongoing: function() /* nil -> bool */ {
|
search: async function(depth, timeout) {
|
||||||
return (this.state != MoveResult.STALEMATE
|
return wasmCall("wb_search", [depth]).then(moveDeserialize);
|
||||||
&& this.state != MoveResult.CHECKMATE);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
search: async function(depth) /* Int -> Move */ {
|
move: async function(move) {
|
||||||
return wasmCall("wb_search", [depth])
|
|
||||||
.then(moveDeserialize);
|
|
||||||
},
|
|
||||||
|
|
||||||
move: async function (move) /* (Move | SerializedMove) -> MoveResult */ {
|
|
||||||
const m = (typeof move === "number") ? move : moveSerialize(move);
|
const m = (typeof move === "number") ? move : moveSerialize(move);
|
||||||
return wasmCall("wb_move", [m]);
|
const mr = await wasmCall("wb_move", [m]);
|
||||||
|
|
||||||
|
if (mr === MoveResult.NORMAL || mr === MoveResult.CHECK || mr === MoveResult.REPEATS ||
|
||||||
|
mr === MoveResult.STALEMATE || mr === MoveResult.CHECKMATE) {
|
||||||
|
this.state = mr;
|
||||||
|
return mr;
|
||||||
|
}
|
||||||
|
return MoveResult.ILLEGAL;
|
||||||
},
|
},
|
||||||
|
|
||||||
at: async function (index) /* (Index | SerializedIndex) -> Square */ {
|
at: async function(index) {
|
||||||
const i = (typeof index === "number") ? index : indexSerialize(index);
|
const i = (typeof index === "number") ? index : indexSerialize(index);
|
||||||
return wasmCall("wb_board_at", [i])
|
return wasmCall("wb_board_at", [i])
|
||||||
.then(squareDeserialize);
|
.then(squareDeserialize);
|
||||||
|
},
|
||||||
|
|
||||||
|
legalMoves: async function() {
|
||||||
|
const n = await wasmCall("wb_all_moves_len", []);
|
||||||
|
const out = new Uint16Array(n);
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
out[i] = await wasmCall("wb_all_moves_get", [i]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const board = await WasmBoard("./chess-worker.js");
|
const board = await WasmBoard();
|
||||||
|
|
||||||
const nextFrame = () => new Promise(requestAnimationFrame);
|
const boardElem = document.getElementById("board");
|
||||||
|
const resultEl = document.getElementById("result");
|
||||||
|
const setStatus = (s) => { resultEl.textContent = s; };
|
||||||
|
|
||||||
const createBoardUI = async (board) => {
|
const squares = new Array(64);
|
||||||
const boardElem = document.getElementById("board");
|
|
||||||
|
const idxToAlg = (idx) => {
|
||||||
|
const i = indexDeserialize(idx);
|
||||||
|
return `${i.fileChar()}${i.rankChar()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const draw = async () => {
|
||||||
|
for (let rank = 7; rank >= 0; --rank) {
|
||||||
|
for (let file = 0; file < 8; ++file) {
|
||||||
|
const idx = 8 * rank + file;
|
||||||
|
const sq = await board.at(idx);
|
||||||
|
console.log(sq);
|
||||||
|
console.log(sq.unicode);
|
||||||
|
squares[idx].textContent = sq.unicode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBoardUI = () => {
|
||||||
boardElem.replaceChildren();
|
boardElem.replaceChildren();
|
||||||
const filesChars = ["a","b","c","d","e","f","g","h"];
|
|
||||||
const ranksChars = ["8","7","6","5","4","3","2","1"];
|
|
||||||
|
|
||||||
const squares = new Array(64);
|
|
||||||
|
|
||||||
for (let rank = 7; rank >= 0; --rank) {
|
for (let rank = 7; rank >= 0; --rank) {
|
||||||
for (let file = 0; file < 8; ++file) {
|
for (let file = 0; file < 8; ++file) {
|
||||||
|
const idx = 8 * rank + file;
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className =
|
el.className = "square " + (((file + rank) % 2) ? "dark" : "light");
|
||||||
"square " + ((file + rank) % 2 ? "dark" : "light");
|
el.dataset.idx = String(idx);
|
||||||
el.dataset.file = filesChars[file];
|
|
||||||
el.dataset.rank = ranksChars[rank];
|
|
||||||
|
|
||||||
const sq = await board.at(serializedIndexFromCoord(rank, file));
|
|
||||||
el.textContent = sq.pieceUnicode();
|
|
||||||
|
|
||||||
boardElem.appendChild(el);
|
boardElem.appendChild(el);
|
||||||
squares[8*rank+file] = el;
|
squares[idx] = el;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return squares;
|
|
||||||
};
|
|
||||||
|
|
||||||
const draw = async (board, squareElems) => {
|
|
||||||
for (let rank = 7; rank >= 0; --rank) {
|
|
||||||
for (let file = 0; file < 8; ++file) {
|
|
||||||
const sq = await board.at(serializedIndexFromCoord(rank, file));
|
|
||||||
squareElems[8*rank+file].textContent = sq.pieceUnicode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sqElems = await createBoardUI(board);
|
createBoardUI();
|
||||||
for (let i = 0; i < 200; ++i) {
|
await draw();
|
||||||
await draw(board, sqElems);
|
|
||||||
await nextFrame();
|
|
||||||
|
|
||||||
const move = await board.search(7);
|
let selected = null; // 0..63 or null
|
||||||
|
let inputEnabled = true;
|
||||||
|
|
||||||
const fromSq = await board.at(move.from);
|
const clearSelection = () => {
|
||||||
const toSq = await board.at(move.to);
|
if (selected !== null) squares[selected].classList.remove("blue");
|
||||||
|
selected = null;
|
||||||
|
};
|
||||||
|
|
||||||
const mr = board.move(move);
|
const legalHasFrom = (moves, fromIdx) => {
|
||||||
if (mr == MoveResult.STALEMATE || mr == MoveResult.CHECKMATE) {
|
for (const mv of moves) {
|
||||||
const resultEl = document.getElementById("result");
|
if (((mv >> 8) & 0xFF) === fromIdx) return true;
|
||||||
resultEl.textContent = _moveResultName[board.state];
|
|
||||||
draw(board, sqElems);
|
|
||||||
await nextFrame();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const legalHasMove = (moves, fromIdx, toIdx) => {
|
||||||
|
const key = ((fromIdx & 0xFF) << 8) | (toIdx & 0xFF);
|
||||||
|
for (const mv of moves) {
|
||||||
|
if (mv === key) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const engineReply = async (depth) => {
|
||||||
|
console.log("searching");
|
||||||
|
setStatus("Black thinking...");
|
||||||
|
const m = await board.search(depth, 2000);
|
||||||
|
console.log("found move", m, );
|
||||||
|
let sooner = Date.now();
|
||||||
|
const mr = await board.move(m);
|
||||||
|
await draw();
|
||||||
|
let later = Date.now();
|
||||||
|
let seconds = later - sooner;
|
||||||
|
setStatus(`Black played ${idxToAlg(indexSerialize(m.from))}-${idxToAlg(indexSerialize(m.to))} after ${seconds} seconds\nWhite to move...`);
|
||||||
|
return mr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSquareClick = async (idx) => {
|
||||||
|
if (!inputEnabled) return;
|
||||||
|
if (isTerminal(board.state)) return;
|
||||||
|
|
||||||
|
const legalMoves = await board.legalMoves();
|
||||||
|
|
||||||
|
if (selected === null) {
|
||||||
|
const sq = await board.at(idx);
|
||||||
|
if (sq.player !== Player.White) return;
|
||||||
|
|
||||||
|
if (!legalHasFrom(legalMoves, idx)) {
|
||||||
|
clearSelection();
|
||||||
|
setStatus(`No legal moves from ${idxToAlg(idx)}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selected = idx;
|
||||||
|
squares[selected].classList.add("blue");
|
||||||
|
setStatus(`Selected ${idxToAlg(idx)}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx === selected) {
|
||||||
|
clearSelection();
|
||||||
|
setStatus("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickedSq = await board.at(idx);
|
||||||
|
|
||||||
|
// user clicks another white piece: either switch selection (if it has moves) or deselect
|
||||||
|
if (clickedSq.player === Player.White) {
|
||||||
|
clearSelection();
|
||||||
|
|
||||||
|
if (!legalHasFrom(legalMoves, idx)) {
|
||||||
|
setStatus(`No legal moves from ${idxToAlg(idx)}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selected = idx;
|
||||||
|
squares[selected].classList.add("blue");
|
||||||
|
setStatus(`Selected ${idxToAlg(idx)}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = selected;
|
||||||
|
const to = idx;
|
||||||
|
|
||||||
|
if (!legalHasMove(legalMoves, from, to)) {
|
||||||
|
clearSelection();
|
||||||
|
setStatus(`Illegal: ${idxToAlg(from)}-${idxToAlg(to)}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection();
|
||||||
|
inputEnabled = false;
|
||||||
|
|
||||||
|
const mrWhite = await board.move({ from: indexDeserialize(from), to: indexDeserialize(to) });
|
||||||
|
await draw();
|
||||||
|
setStatus(`White played ${idxToAlg(from)}-${idxToAlg(to)}. ${moveResultName(mrWhite)}`);
|
||||||
|
|
||||||
|
if (isTerminal(mrWhite)) {
|
||||||
|
inputEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrBlack = await engineReply(8);
|
||||||
|
if (isTerminal(mrBlack)) {
|
||||||
|
inputEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputEnabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < 64; ++i) {
|
||||||
|
squares[i].addEventListener("click", () => { onSquareClick(i).catch(console.error); });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
run().catch((err) => {
|
setStatus("White to move.");
|
||||||
console.error(err);
|
};
|
||||||
});
|
|
||||||
|
|
||||||
|
run().catch((err) => console.error(err));
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,37 @@
|
|||||||
|
/* The purpose of this file is to be a simple wasm helper layer, all state is global */
|
||||||
|
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
static struct search_option g_tt_buf[TT_ENTRIES];
|
static struct search_option g_tt_buf[TT_ENTRIES];
|
||||||
|
static struct board g_board;
|
||||||
|
static uint16_t g_legal_moves_buf[MOVE_MAX];
|
||||||
|
static uint32_t g_legal_moves_len;
|
||||||
|
|
||||||
static struct board g_board;
|
void wb_init(void)
|
||||||
|
{
|
||||||
|
g_board = BOARD_INIT;
|
||||||
|
g_board.tt.entries = g_tt_buf;
|
||||||
|
g_board.tt.mask = TT_MASK;
|
||||||
|
board_init(&g_board);
|
||||||
|
}
|
||||||
|
|
||||||
static inline uint32_t move_serialize(struct move m)
|
static inline uint32_t move_serialize(struct move m)
|
||||||
{
|
{
|
||||||
_Static_assert(sizeof m.from * CHAR_BIT == 8,
|
_Static_assert(sizeof m.from * CHAR_BIT == 8,
|
||||||
"this must be checked if struct move's `from` changes");
|
"this must be checked if struct move's `from` changes");
|
||||||
_Static_assert(sizeof m.to * CHAR_BIT == 8,
|
_Static_assert(sizeof m.to * CHAR_BIT == 8,
|
||||||
"this must be checked if struct move's `to` changes");
|
"this must be checked if struct move's `to` changes");
|
||||||
return ((uint32_t)m.appeal << 24ULL)
|
|
||||||
| ((uint32_t)m.attr << 16ULL)
|
return ((uint32_t)m.appeal << 24U)
|
||||||
| ((uint32_t)m.from << 8ULL)
|
| ((uint32_t)m.attr << 16U)
|
||||||
| ((uint32_t)m.to);
|
| ((uint32_t)m.from << 8U)
|
||||||
|
| ((uint32_t)m.to << 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline struct move move_deserialize(uint64_t m)
|
static inline struct move move_deserialize(uint32_t m)
|
||||||
{
|
{
|
||||||
return (struct move) {
|
return (struct move) {
|
||||||
/* appeal and attributes are ignored regardless */
|
/* appeal and attributes are ignored regardless */
|
||||||
@@ -34,14 +46,14 @@ static inline struct move move_deserialize(uint64_t m)
|
|||||||
|
|
||||||
uint64_t wb_search(int8_t max_depth)
|
uint64_t wb_search(int8_t max_depth)
|
||||||
{
|
{
|
||||||
struct search_result const sr = search(&g_board, g_board.pos.player, max_depth);
|
struct search_result const sr = search(&g_board, g_board.pos.moving_side, max_depth);
|
||||||
return move_serialize(sr.move);
|
return (uint64_t)move_serialize(sr.move);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t wb_move(uint32_t move)
|
int32_t wb_move(uint32_t move)
|
||||||
{
|
{
|
||||||
struct move const m = move_deserialize(move);
|
struct move const m = move_deserialize(move);
|
||||||
enum move_result const mr = board_move_2(&g_board, m);
|
enum move_result const mr = board_move(&g_board, m);
|
||||||
|
|
||||||
/* TODO: this checkmate/stalemate check needs to be abstracted better */
|
/* TODO: this checkmate/stalemate check needs to be abstracted better */
|
||||||
if (mr == MR_STALEMATE) {
|
if (mr == MR_STALEMATE) {
|
||||||
@@ -49,35 +61,56 @@ int32_t wb_move(uint32_t move)
|
|||||||
}
|
}
|
||||||
struct move moves[MOVE_MAX];
|
struct move moves[MOVE_MAX];
|
||||||
size_t move_count = 0ULL;
|
size_t move_count = 0ULL;
|
||||||
all_moves(&g_board.pos, opposite_player(g_board.pos.player), &move_count, moves);
|
all_pseudolegal_moves(&g_board.pos, MG_ALL, other_side(g_board.pos.moving_side), &move_count, moves);
|
||||||
if (move_count == 0ULL) {
|
if (move_count == 0ULL) {
|
||||||
return MR_CHECKMATE;
|
return (int32_t)MR_CHECKMATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int32_t)mr;
|
return (int32_t)mr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wb_init()
|
|
||||||
{
|
|
||||||
g_board = BOARD_INIT;
|
|
||||||
g_board.tt.entries = g_tt_buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t player_piece_serialize(enum player c, enum piece pz)
|
static int32_t side_piece_serialize(Side8 c, Piece8 pz)
|
||||||
{
|
{
|
||||||
return ((c & 0xFF) << 8U)
|
return ((c & 0xFF) << 8U)
|
||||||
| (pz & 0xFF);
|
| (pz & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t wb_board_at(index at)
|
int32_t wb_board_at(uint8_t at)
|
||||||
{
|
{
|
||||||
bitboard const m = SQ_MASK_FROM_INDEX(at);
|
Bb64 const m = MASK_FROM_SQ((Sq8)at);
|
||||||
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) {
|
||||||
for (enum piece pz = PIECE_BEGIN; pz < PIECE_COUNT; ++pz) {
|
for (Piece8 pz = PIECE_BEGIN; pz < PIECE_COUNT; ++pz) {
|
||||||
if (g_board.pos.pieces[pl][pz] & m) {
|
if (g_board.pos.pieces[side][pz] & m) {
|
||||||
return player_piece_serialize(pl, pz);
|
return side_piece_serialize(side, pz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t wb_all_moves_len(void)
|
||||||
|
{
|
||||||
|
struct move moves[MOVE_MAX];
|
||||||
|
size_t cnt = 0;
|
||||||
|
all_pseudolegal_moves(&g_board.pos, MG_ALL, g_board.pos.moving_side, &cnt, moves);
|
||||||
|
|
||||||
|
if (cnt > MOVE_MAX) {
|
||||||
|
cnt = MOVE_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_legal_moves_len = (uint32_t)cnt;
|
||||||
|
for (size_t i = 0; i < cnt; ++i) {
|
||||||
|
g_legal_moves_buf[i] = (uint16_t)(((uint32_t)moves[i].from << 8U)
|
||||||
|
| (uint32_t)moves[i].to);
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_legal_moves_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t wb_all_moves_get(uint32_t i)
|
||||||
|
{
|
||||||
|
if (i >= g_legal_moves_len) return 0U;
|
||||||
|
return (uint32_t)g_legal_moves_buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user