Compare commits

..

7 Commits

16 changed files with 789 additions and 511 deletions

View File

@@ -4,13 +4,14 @@ CC := clang
CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function
CFLAGS.gcc.release := -Ofast -march=native -DNDEBUG CFLAGS.gcc.release := -Ofast -march=native -DNDEBUG
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address CFLAGS.gcc.debug := -ggdb -O1 -fsanitize=address
CFLAGS.clang := -std=c23 -g -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion CFLAGS.clang := -std=c23 -g -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion -Wno-macro-redefined -Wno-initializer-overrides
CFLAGS.clang.release := -O3 -ffast-math -march=native # -DNDEBUG CFLAGS.clang.release := -O3 -ffast-math -march=native -DNDEBUG -DNSTATS
CFLAGS.clang.debug := -g3 -O0 -fsanitize=address CFLAGS.clang.debug := -g3 -O1 -fsanitize=address,undefined
CFLAGS.clang.wasm := \ CFLAGS.clang.wasm := \
--target=wasm32-unknown-unknown -nostdlib -g \ --target=wasm32-unknown-unknown -nostdlib -g \
-DNSTATS \
-Wl,--export-all \ -Wl,--export-all \
-Wl,--no-entry -Wl,--no-entry
@@ -21,10 +22,10 @@ all: tests
wasm: chess.wasm wasm: chess.wasm
codegen: codegen.c codegen: codegen.c
$(CC) -o $@ $(CFLAGS) $^ $(CC) -D_DEFAULT_SOURCE -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
@@ -33,4 +34,4 @@ mbb_bishop.h: codegen
./codegen ./codegen
tests: tests.c mbb_rook.h mbb_bishop.h engine.h tests: tests.c mbb_rook.h mbb_bishop.h engine.h
$(CC) -o $@ $(CFLAGS) -DUSE_PRINTF tests.c $(CC) -D_DEFAULT_SOURCE -o $@ $(CFLAGS) tests.c

View File

@@ -38,13 +38,12 @@ static void bitboard_print(Bb64 b, FILE* out)
static void tt_print_stats(struct tt* tt, FILE* out) static void tt_print_stats(struct tt* tt, FILE* out)
{ {
#ifndef NDEBUG
double sat = 0; double sat = 0;
for (size_t i = 0; i < TT_ENTRIES; ++i) { for (size_t i = 0; i < TT_ENTRIES; ++i) {
if (tt->entries[i].init) if (tt->entries[i].init)
sat += 1.0; sat += 1.0;
} }
double const pct = sat/TT_ENTRIES; double const pct = sat/((double)tt->mask+1.0);
fprintf(out, "---- Stats ---\n"); fprintf(out, "---- Stats ---\n");
fprintf(out, "tt collisions: %"PRIu64"\n", tt->collisions); fprintf(out, "tt collisions: %"PRIu64"\n", tt->collisions);
@@ -52,10 +51,6 @@ static void tt_print_stats(struct tt* tt, FILE* out)
fprintf(out, "tt probes: %"PRIu64"\n", tt->probes); fprintf(out, "tt probes: %"PRIu64"\n", tt->probes);
fprintf(out, "tt insertions: %"PRIu64"\n", tt->insertions); fprintf(out, "tt insertions: %"PRIu64"\n", tt->insertions);
fprintf(out, "saturation: %.02lf\n", pct); fprintf(out, "saturation: %.02lf\n", pct);
#else
(void)tt;
fprintf(out, "stats not available with NDEBUG\n");
#endif
} }
static void board_print_fen(struct pos const* pos, FILE* out) static void board_print_fen(struct pos const* pos, FILE* out)
@@ -202,10 +197,6 @@ static void board_print(struct pos const* pos, struct move* move, FILE* out, boo
bg = (i+j) % 2 ? 45 : 43; bg = (i+j) % 2 ? 45 : 43;
} }
if ((pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]) & MASK_FROM_SQ(n)) {
bg += 60; /* make bright */
}
fprintf(out, "\033[%d;%dm", fg, bg); fprintf(out, "\033[%d;%dm", fg, bg);
if (buf[i][j]) { if (buf[i][j]) {

View File

@@ -1,26 +1,25 @@
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) { if (!resp.ok) throw new Error(`fetch wasm failed ${resp.status} ${resp.statusText}`);
throw new Error("fetch wasm failed ${resp.status} ${resp.statusText}");
}
const { instance } =
await WebAssembly.instantiateStreaming(resp, {});
const { instance } = await WebAssembly.instantiateStreaming(resp, {});
exports = instance.exports; exports = instance.exports;
} }
await init(); await init();
self.postMessage({ type: "ready" }); self.postMessage({ type: "ready" });
self.onmessage = (e) => { self.onmessage = (e) => {
const { id, method, args = [] } = e.data; const { id, method, args = [] } = e.data;
try { try {
const value = exports[method](...args); let value;
value = exports[method](...args);
self.postMessage({ id, ok: true, value }); self.postMessage({ id, ok: true, value });
} catch (err) { } catch (err) {
self.postMessage({ id, ok: false, error: String(err?.message ?? err) }); self.postMessage({
id, ok: false, error: String(err?.message ?? err)
});
} }
}; };

View File

@@ -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
View File

@@ -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));

View File

@@ -15,6 +15,16 @@ static Bb64 cardinals_from_index(Sq8 p)
return (FILE_MASK(sq_to_file(p)) | RANK_MASK(sq_to_rank(p))); return (FILE_MASK(sq_to_file(p)) | RANK_MASK(sq_to_rank(p)));
} }
static Bb64 cardinals(Bb64 p)
{
Bb64 b = 0ULL;
while (p) {
Sq8 const lsb = bitboard_pop_lsb(&p);
b |= cardinals_from_index(lsb);
}
return b;
}
static Bb64 diagonals_from_index(Sq8 sq) static Bb64 diagonals_from_index(Sq8 sq)
{ {
#ifdef CODEGEN #ifdef CODEGEN
@@ -66,13 +76,22 @@ static Bb64 diagonals_from_index(Sq8 sq)
#endif #endif
} }
static Bb64 diagonals(Bb64 p)
{
Bb64 b = 0ULL;
while (p) {
Sq8 const lsb = bitboard_pop_lsb(&p);
b |= diagonals_from_index(lsb);
}
return b;
}
/* PIECE ATTACKS /* PIECE ATTACKS
* ================== * ==================
* *
* All piece attack functions rely on the caller masking their own pieces. * All piece attack functions rely on the caller masking their own pieces.
* e.g. `knight_attacks(knights) & ~our_occupied` * e.g. `knight_attacks(knights) & empty`
* */ * */
static Bb64 knight_attacks(Bb64 p) static Bb64 knight_attacks(Bb64 p)
@@ -503,7 +522,7 @@ static Bb64 attacks_to(struct pos const* pos,
Bb64 pretend_occupied, Bb64 pretend_occupied,
Bb64 pretend_empty) Bb64 pretend_empty)
{ {
Bb64 const occ_orig = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; Bb64 const occ_orig = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
Bb64 const occ = (occ_orig & ~pretend_empty) | pretend_occupied; Bb64 const occ = (occ_orig & ~pretend_empty) | pretend_occupied;
Bb64 const* pw = pos->pieces[SIDE_WHITE]; Bb64 const* pw = pos->pieces[SIDE_WHITE];
@@ -544,15 +563,15 @@ static
Bb64 checkers(struct pos const* pos, Side8 us) Bb64 checkers(struct pos const* pos, Side8 us)
{ {
/* TODO: specialize */ /* TODO: specialize */
return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & ~pos->occupied[us]; return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & pos->pieces[us][PIECE_EMPTY];
} }
static static
Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq) Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq)
{ {
Side8 const them = other_side(us); Side8 const them = other_side(us);
Bb64 const our_occ = pos->occupied[us]; Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY];
Bb64 const their_occ = pos->occupied[them]; Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY];
Bb64 const* p = pos->pieces[them]; Bb64 const* p = pos->pieces[them];
Bb64 const bqs = p[PIECE_QUEEN] | p[PIECE_BISHOP]; Bb64 const bqs = p[PIECE_QUEEN] | p[PIECE_BISHOP];
@@ -576,7 +595,7 @@ Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq)
static static
Bb64 all_threats_from_side(struct pos const * pos, Side8 who) Bb64 all_threats_from_side(struct pos const * pos, Side8 who)
{ {
Bb64 const occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; Bb64 const occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
Bb64 const* p = pos->pieces[who]; Bb64 const* p = pos->pieces[who];
Bb64 t = 0ULL; Bb64 t = 0ULL;

View File

@@ -12,7 +12,6 @@ struct board {
Side8 moving_side; Side8 moving_side;
bool castling_illegal[SIDE_COUNT][CASTLE_COUNT]; bool castling_illegal[SIDE_COUNT][CASTLE_COUNT];
Bb64 ep_targets; Bb64 ep_targets;
Bb64 occupied[SIDE_COUNT];
int halfmoves; /* FIXME: this duplicates the hist.length value */ int halfmoves; /* FIXME: this duplicates the hist.length value */
int fullmoves; int fullmoves;
@@ -23,7 +22,7 @@ struct board {
/* used for repeated board state detection only */ /* used for repeated board state detection only */
struct history { struct history {
struct pos items[64]; struct pos items[50];
size_t length; size_t length;
} hist; } hist;
@@ -36,11 +35,15 @@ struct board {
Piece8 mailbox[SQ_COUNT]; Piece8 mailbox[SQ_COUNT];
}; };
/* attack-sets depends on the definition of struct pos, whoops ¯\_(ツ)_/¯ */
#include "engine-attack-sets.h"
#define BOARD_INIT (struct board) { \ #define BOARD_INIT (struct board) { \
.pos = { \ .pos = { \
.fullmoves = 1, \ .fullmoves = 1, \
.pieces = { \ .pieces = { \
[SIDE_WHITE] = { \ [SIDE_WHITE] = { \
[PIECE_EMPTY] = RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6 | RANK_MASK_7 | RANK_MASK_8, \
[PIECE_PAWN] = RANK_MASK_2, \ [PIECE_PAWN] = RANK_MASK_2, \
[PIECE_ROOK] = SQMASK_A1 | SQMASK_H1, \ [PIECE_ROOK] = SQMASK_A1 | SQMASK_H1, \
[PIECE_KNIGHT] = SQMASK_B1 | SQMASK_G1, \ [PIECE_KNIGHT] = SQMASK_B1 | SQMASK_G1, \
@@ -49,6 +52,7 @@ struct board {
[PIECE_KING] = SQMASK_E1, \ [PIECE_KING] = SQMASK_E1, \
}, \ }, \
[SIDE_BLACK] = { \ [SIDE_BLACK] = { \
[PIECE_EMPTY] = RANK_MASK_1 | RANK_MASK_2 | RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6, \
[PIECE_PAWN] = RANK_MASK_7, \ [PIECE_PAWN] = RANK_MASK_7, \
[PIECE_ROOK] = SQMASK_A8 | SQMASK_H8, \ [PIECE_ROOK] = SQMASK_A8 | SQMASK_H8, \
[PIECE_KNIGHT] = SQMASK_B8 | SQMASK_G8, \ [PIECE_KNIGHT] = SQMASK_B8 | SQMASK_G8, \
@@ -57,16 +61,6 @@ struct board {
[PIECE_KING] = SQMASK_E8, \ [PIECE_KING] = SQMASK_E8, \
} \ } \
}, \ }, \
.occupied = { \
[SIDE_WHITE] = \
RANK_MASK_2 | SQMASK_A1 | SQMASK_H1 | \
SQMASK_B1 | SQMASK_G1 | SQMASK_C1 | \
SQMASK_F1 | SQMASK_D1 | SQMASK_E1, \
[SIDE_BLACK] = \
RANK_MASK_7 | SQMASK_A8 | SQMASK_H8 | \
SQMASK_B8 | SQMASK_G8| SQMASK_C8 | \
SQMASK_F8| SQMASK_D8| SQMASK_E8, \
}, \
.hash = ~0ULL, \ .hash = ~0ULL, \
}, \ }, \
.mailbox = { \ .mailbox = { \
@@ -112,9 +106,12 @@ void board_init(struct board* b)
*b = BOARD_INIT; *b = BOARD_INIT;
} }
if (b->tt.entries == NULL) { if (b->tt.entries == NULL) {
b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0], size_t const entries = 1<<26;
size_t const mask = entries-1;
b->tt.entries = sys_mmap_anon_shared(entries * sizeof b->tt.entries[0],
SYS_PROT_READ | SYS_PROT_WRITE, SYS_PROT_READ | SYS_PROT_WRITE,
SYS_MADV_RANDOM); SYS_MADV_RANDOM);
b->tt.mask = mask;
if (b->tt.entries == NULL) { if (b->tt.entries == NULL) {
__builtin_trap(); __builtin_trap();
} }
@@ -150,13 +147,20 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
struct piece_side const p = piece_and_side_from_char[(uint8_t)ch]; struct piece_side const p = piece_and_side_from_char[(uint8_t)ch];
Bb64 const sq_mask = MASK_FROM_RF(ri, fi); Bb64 const sq_mask = MASK_FROM_RF(ri, fi);
b->pos.pieces[p.side][p.piece] |= sq_mask; b->pos.pieces[p.side][p.piece] |= sq_mask;
b->pos.occupied[p.side] |= sq_mask; b->pos.pieces[p.side][PIECE_EMPTY] &= ~sq_mask;
b->mailbox[SQ_FROM_RF(ri, fi)] = p.piece; b->mailbox[SQ_FROM_RF(ri, fi)] = p.piece;
} }
} }
(void)BUF_GETCHAR(fen); /* discard '/' or ' ' */ (void)BUF_GETCHAR(fen); /* discard '/' or ' ' */
} }
b->pos.pieces[SIDE_WHITE][PIECE_EMPTY] = ~0ULL;
b->pos.pieces[SIDE_BLACK][PIECE_EMPTY] = ~0ULL;
for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
b->pos.pieces[SIDE_WHITE][PIECE_EMPTY] &= ~b->pos.pieces[SIDE_WHITE][p];
b->pos.pieces[SIDE_BLACK][PIECE_EMPTY] &= ~b->pos.pieces[SIDE_BLACK][p];
}
{ /* active color */ { /* active color */
char const ch = BUF_GETCHAR(fen); char const ch = BUF_GETCHAR(fen);
if (ch == 'w') { if (ch == 'w') {
@@ -234,11 +238,12 @@ enum move_result {
/* does not check validity */ /* does not check validity */
static enum move_result move_piece(struct pos* restrict pos, static enum move_result move_piece(struct pos* restrict pos,
Side8 us,
struct history* restrict hist, struct history* restrict hist,
Piece8 mailbox[restrict static SQ_COUNT], Piece8 mailbox[restrict static SQ_COUNT],
struct move move) struct move move)
{ {
Side8 const us = pos->moving_side; //Side8 const us = pos->moving_side;
Side8 const them = other_side(us); Side8 const them = other_side(us);
Piece8 const from_piece = mailbox[move.from]; Piece8 const from_piece = mailbox[move.from];
@@ -261,7 +266,7 @@ static enum move_result move_piece(struct pos* restrict pos,
do { \ do { \
Bb64 const x = MASK_FROM_SQ(from) | MASK_FROM_SQ(to); \ Bb64 const x = MASK_FROM_SQ(from) | MASK_FROM_SQ(to); \
pos->pieces[side][piece] ^= x; \ pos->pieces[side][piece] ^= x; \
pos->occupied[side] ^= x; \ pos->pieces[side][PIECE_EMPTY] ^= x; \
pos->hash = tt_hash_update(pos->hash, from, side, piece); \ pos->hash = tt_hash_update(pos->hash, from, side, piece); \
pos->hash = tt_hash_update(pos->hash, to, side, piece); \ pos->hash = tt_hash_update(pos->hash, to, side, piece); \
mailbox[to] = piece; \ mailbox[to] = piece; \
@@ -272,7 +277,7 @@ static enum move_result move_piece(struct pos* restrict pos,
do { \ do { \
Bb64 const x = MASK_FROM_SQ(at); \ Bb64 const x = MASK_FROM_SQ(at); \
pos->pieces[owner][piece] &= ~x; \ pos->pieces[owner][piece] &= ~x; \
pos->occupied[owner] &= ~x; \ pos->pieces[owner][PIECE_EMPTY] |= x; \
pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ pos->hash = tt_hash_update(pos->hash, at, owner, piece); \
hist->length = 0; \ hist->length = 0; \
pos->halfmoves = 0; \ pos->halfmoves = 0; \
@@ -282,7 +287,7 @@ static enum move_result move_piece(struct pos* restrict pos,
do { \ do { \
Bb64 const x = MASK_FROM_SQ(at); \ Bb64 const x = MASK_FROM_SQ(at); \
pos->pieces[owner][piece] |= x; \ pos->pieces[owner][piece] |= x; \
pos->occupied[owner] |= x; \ pos->pieces[owner][PIECE_EMPTY] &= ~x; \
pos->hash = tt_hash_update(pos->hash, at, owner, piece); \ pos->hash = tt_hash_update(pos->hash, at, owner, piece); \
mailbox[at] = piece; \ mailbox[at] = piece; \
pos->halfmoves = 0; \ pos->halfmoves = 0; \
@@ -309,7 +314,7 @@ static enum move_result move_piece(struct pos* restrict pos,
else { else {
POS_MOVE(us, from_piece, move.from, move.to); POS_MOVE(us, from_piece, move.from, move.to);
/* capture */ /* capture */
if (to_mask & pos->occupied[them]) { if (to_mask & ~pos->pieces[them][PIECE_EMPTY]) {
POS_REMOVE(them, to_piece, move.to); POS_REMOVE(them, to_piece, move.to);
} }
@@ -375,36 +380,27 @@ static enum move_result move_piece(struct pos* restrict pos,
pos->fullmoves += (pos->moving_side == SIDE_BLACK); pos->fullmoves += (pos->moving_side == SIDE_BLACK);
pos->halfmoves += 1; pos->halfmoves += 1;
assuming(hist->length < sizeof hist->items / sizeof *hist->items);
/* check for repeated moves */
/* TODO: add move_do and move_undo to create proper repeat checks */
assuming(hist->length < 64); assuming(hist->length < 64);
int repetitions = 0;
for (size_t i = 0; i < hist->length; ++i) { for (size_t i = 0; i < hist->length; ++i) {
_Static_assert(sizeof *pos == sizeof hist->items[i]); _Static_assert(sizeof *pos == sizeof hist->items[i]);
if (!my_memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces) if (!my_memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces)
&& !my_memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal) && !my_memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal)
&& hist->items[i].moving_side == pos->moving_side && hist->items[i].moving_side == pos->moving_side
&& hist->items[i].ep_targets == pos->ep_targets) && hist->items[i].ep_targets == pos->ep_targets)
{ {
repetitions += 1; return MR_STALEMATE;
} }
} }
hist->items[hist->length++] = *pos; if (pos->halfmoves > 50) {
if (repetitions >= 3 || pos->halfmoves > 50) {
return MR_STALEMATE; return MR_STALEMATE;
} }
else if (repetitions > 0) {
return MR_REPEATS;
}
else if (pos->halfmoves > 50) {
return MR_STALEMATE;
}
#if 0
else if (attacks_to(pos, pos->pieces[them][PIECE_KING], 0ULL, 0ULL)
& ~pos->occupied[them]) {
return MR_CHECK;
}
#endif
else { else {
return MR_NORMAL; return MR_NORMAL;
} }
@@ -417,7 +413,25 @@ static enum move_result move_piece(struct pos* restrict pos,
static enum move_result board_move(struct board* b, struct move move) static enum move_result board_move(struct board* b, struct move move)
{ {
return move_piece(&b->pos, return move_piece(&b->pos,
b->pos.moving_side,
&b->hist, &b->hist,
b->mailbox, b->mailbox,
move); move);
} }
static bool pos_is_legal(struct pos const* restrict pos)
{
Side8 const mside = pos->moving_side;
Side8 const oside = other_side(mside);
if (pos->pieces[oside][PIECE_KING] & all_threats_from_side(pos, mside)) {
return false;
}
return true;
}
static bool board_is_legal(struct board* b)
{
return pos_is_legal(&b->pos);
}

View File

@@ -20,12 +20,12 @@ static enum game_progress endgameness(struct pos const* pos)
/* piece_value is already defined similarly elsewhere, but this one should /* piece_value is already defined similarly elsewhere, but this one should
* remain independent of minor tweaks in the global table */ * remain independent of minor tweaks in the global table */
static int const piece_value[PIECE_COUNT] = { static int const piece_value[PIECE_COUNT] = {
[PIECE_KING] = 0, [PIECE_KING] = 0,
[PIECE_PAWN] = 1, [PIECE_PAWN] = 1,
[PIECE_BISHOP] = 3, [PIECE_BISHOP] = 3,
[PIECE_KNIGHT] = 3, [PIECE_KNIGHT] = 3,
[PIECE_ROOK] = 5, [PIECE_ROOK] = 5,
[PIECE_QUEEN] = 9, [PIECE_QUEEN] = 9,
}; };
int npm = 0; /* non-pawn material */ int npm = 0; /* non-pawn material */
@@ -314,25 +314,25 @@ BITBOARD( \
#define EARLY_POSITIONAL_BONUS_0 \ #define EARLY_POSITIONAL_BONUS_0 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \ X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \ X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \ X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \ X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
X(PIECE_QUEEN, -0.15, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \ X(PIECE_QUEEN, -15, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
X(PIECE_ROOK, 0.10, FILE_MASK_D | FILE_MASK_E) \ X(PIECE_ROOK, 10, FILE_MASK_D | FILE_MASK_E) \
/**/ /**/
#define EARLY_POSITIONAL_BONUS_1 \ #define EARLY_POSITIONAL_BONUS_1 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \ X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
X(PIECE_BISHOP, 0.05, REL_BISHOP_KING_ATTACK) \ X(PIECE_BISHOP, 5, REL_BISHOP_KING_ATTACK) \
/**/ /**/
#define EARLY_POSITIONAL_BONUS_2 \ #define EARLY_POSITIONAL_BONUS_2 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, -0.18, ~REL_EARLY_PAWN_STRUCTURE) \ X(PIECE_PAWN, -18, ~REL_EARLY_PAWN_STRUCTURE) \
X(PIECE_KNIGHT, -0.10, REL_UNDEVELOPED_KNIGHTS) \ X(PIECE_KNIGHT, -10, REL_UNDEVELOPED_KNIGHTS) \
X(PIECE_BISHOP, -0.10, REL_UNDEVELOPED_BISHOPS) \ X(PIECE_BISHOP, -10, REL_UNDEVELOPED_BISHOPS) \
/**/ /**/
#define EARLY_POSITIONAL_BONUS_3 \ #define EARLY_POSITIONAL_BONUS_3 \
@@ -345,58 +345,58 @@ BITBOARD( \
#define MIDDLE_POSITIONAL_BONUS_0 \ #define MIDDLE_POSITIONAL_BONUS_0 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \ X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \ X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \ X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \ X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
/**/ /**/
#define MIDDLE_POSITIONAL_BONUS_1 \ #define MIDDLE_POSITIONAL_BONUS_1 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \ X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
X(PIECE_BISHOP, 0.07, REL_BISHOP_KING_ATTACK) \ X(PIECE_BISHOP, 7, REL_BISHOP_KING_ATTACK) \
X(PIECE_QUEEN, 0.07, REL_BISHOP_KING_ATTACK) \ X(PIECE_QUEEN, 7, REL_BISHOP_KING_ATTACK) \
/**/ /**/
#define MIDDLE_POSITIONAL_BONUS_2 \ #define MIDDLE_POSITIONAL_BONUS_2 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.02, REL_PAWN_KINGSIDE) \ X(PIECE_PAWN, 2, REL_PAWN_KINGSIDE) \
/**/ /**/
#define MIDDLE_POSITIONAL_BONUS_3 \ #define MIDDLE_POSITIONAL_BONUS_3 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_BISHOP, 0.05, BOARD_CENTER_6X6) \ X(PIECE_BISHOP, 5, BOARD_CENTER_6X6) \
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_6X6) \ X(PIECE_KNIGHT, 5, BOARD_CENTER_6X6) \
/**/ /**/
/* ------------------------------- end game -------------------------------- */ /* ------------------------------- end game -------------------------------- */
#define LATE_POSITIONAL_BONUS_0 \ #define LATE_POSITIONAL_BONUS_0 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \ X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \
X(PIECE_KING, 0.10, BOARD_CENTER_6X6) \ X(PIECE_KING, 10, BOARD_CENTER_6X6) \
/**/ /**/
#define LATE_POSITIONAL_BONUS_1 \ #define LATE_POSITIONAL_BONUS_1 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6) \ X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6) \
X(PIECE_KING, 0.10, BOARD_CENTER_4X4) \ X(PIECE_KING, 10, BOARD_CENTER_4X4) \
/**/ /**/
#define LATE_POSITIONAL_BONUS_2 \ #define LATE_POSITIONAL_BONUS_2 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_PAWN, 0.70, REL_RANK_7) \ X(PIECE_PAWN, 70, REL_RANK_7) \
X(PIECE_KING, 0.10, BOARD_CENTER_2X2) \ X(PIECE_KING, 10, BOARD_CENTER_2X2) \
/**/ /**/
#define LATE_POSITIONAL_BONUS_3 \ #define LATE_POSITIONAL_BONUS_3 \
/* piece bonus area*/ \ /* piece bonus area*/ \
X(PIECE_KING, -0.50, ~BOARD_CENTER_6X6) \ X(PIECE_KING, -50, ~BOARD_CENTER_6X6) \
/**/ /**/
struct posmod { struct posmod {
Bb64 const area; Bb64 const area;
double const val; Score16 const val;
}; };
static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz) static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz)

View File

@@ -11,3 +11,8 @@
#else #else
#define assuming(expr) ((expr) ? 0 : (__builtin_unreachable(), 0)) #define assuming(expr) ((expr) ? 0 : (__builtin_unreachable(), 0))
#endif #endif
#define CACHE_LINE_SIZE 64
#define LIKELY(expr) __builtin_expect((expr), 1)
#define UNLIKELY(expr) __builtin_expect((expr), 0)

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#define MOVE_MAX 128
#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \ #define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \
{.from = SQ_E1, .to = SQ_G1} {.from = SQ_E1, .to = SQ_G1}
@@ -13,35 +15,10 @@
{.from = SQ_E8, .to = SQ_C8} {.from = SQ_E8, .to = SQ_C8}
static inline static inline
struct move move_make(struct pos const* restrict pos, struct move move_make(Index8 from, Index8 to)
Piece8 piece,
Index8 from,
Index8 to,
uint8_t add_attr)
{ {
(void)piece; return (struct move){.from = from, .to = to};
(void)pos;
(void)add_attr;
#if 0
Side8 const us = pos->moving_side;
Side8 const them = other_side(us);
Bb64 const their_occ = pos->occupied[them];
Bb64 const tomask = MASK_FROM_SQ(to);
Bb64 const finishline = (us == SIDE_WHITE ? RANK_MASK_8 : RANK_MASK_1);
uint8_t attr = 0ULL;
#define MASK_IF8(x) ((~(uint8_t)0U) + (uint8_t)!(x))
attr |= MATTR_CAPTURE & MASK_IF8(tomask & their_occ);
attr |= MATTR_CAPTURE & MASK_IF8((piece == PIECE_PAWN) && tomask & pos->ep_targets);
attr |= MATTR_PROMOTE & MASK_IF8((piece == PIECE_PAWN) && (tomask & finishline));
attr |= add_attr;
#undef MASK_IF8
return (struct move){.from = from, .to = to, .attr = attr};
#endif
return (struct move){.from = from, .to = to, .attr = add_attr};
} }
#define MOVE_MAX 128
enum move_gen_type { enum move_gen_type {
MG_ALL, MG_ALL,
@@ -63,8 +40,8 @@ static void all_pseudolegal_from_piece(struct pos const* restrict pos,
Side8 them = other_side(us); Side8 them = other_side(us);
Bb64 const our_occ = pos->occupied[us]; Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY];
Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK]; Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
if (type == MG_CHECKS) { if (type == MG_CHECKS) {
allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ); allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ);
@@ -80,7 +57,7 @@ static void all_pseudolegal_from_piece(struct pos const* restrict pos,
while (move_mask) { while (move_mask) {
Sq8 const to = bitboard_pop_lsb(&move_mask); Sq8 const to = bitboard_pop_lsb(&move_mask);
assuming(*out_count < MOVE_MAX); \ assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(pos, piece, from, to, 0); out[(*out_count)++] = move_make(from, to);
} }
} }
} }
@@ -93,7 +70,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
size_t* restrict out_count,\ size_t* restrict out_count,\
struct move out[restrict static MOVE_MAX])\ struct move out[restrict static MOVE_MAX])\
{\ {\
Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK];\ Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);\
\ \
if (type == MG_CHECKS) {\ if (type == MG_CHECKS) {\
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
@@ -104,7 +81,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
while (sp) {\ while (sp) {\
Sq8 const to = bitboard_pop_lsb(&sp);\ Sq8 const to = bitboard_pop_lsb(&sp);\
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\ Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ out[(*out_count)++] = move_make(from, to);\
}\ }\
\ \
Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\ Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\
@@ -113,7 +90,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
while (dp) {\ while (dp) {\
Sq8 const to = bitboard_pop_lsb(&dp);\ Sq8 const to = bitboard_pop_lsb(&dp);\
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\ Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ out[(*out_count)++] = move_make(from, to);\
}\ }\
} }
DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2) DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2)
@@ -127,7 +104,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
size_t* restrict out_count,\ size_t* restrict out_count,\
struct move out[restrict static MOVE_MAX])\ struct move out[restrict static MOVE_MAX])\
{\ {\
Bb64 const their_occ = pos->occupied[other_side(side_enum)];\ Bb64 const their_occ = ~pos->pieces[other_side(side_enum)][PIECE_EMPTY];\
\ \
if (type == MG_CHECKS) {\ if (type == MG_CHECKS) {\
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\ allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
@@ -141,7 +118,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
Sq8 const to = bitboard_pop_lsb(&ratk);\ Sq8 const to = bitboard_pop_lsb(&ratk);\
Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\ Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
assuming(*out_count < MOVE_MAX); \ assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ out[(*out_count)++] = move_make(from, to);\
}\ }\
\ \
Bb64 latk = pawn_attacks_left_##side(piece_mask)\ Bb64 latk = pawn_attacks_left_##side(piece_mask)\
@@ -152,7 +129,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
Sq8 const to = bitboard_pop_lsb(&latk);\ Sq8 const to = bitboard_pop_lsb(&latk);\
Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\ Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
assuming(*out_count < MOVE_MAX); \ assuming(*out_count < MOVE_MAX); \
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\ out[(*out_count)++] = move_make(from, to);\
}\ }\
} }
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH) DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH)
@@ -177,8 +154,7 @@ static void all_pseudolegal_moves(struct pos const* restrict pos,
Bb64 const their_threats = all_threats_from_side(pos, them); Bb64 const their_threats = all_threats_from_side(pos, them);
Bb64 const their_occ = pos->occupied[them]; Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY];
Bb64 const all_occ = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK];
Bb64 allowed; Bb64 allowed;
if (type == MG_CAPTURES) { if (type == MG_CAPTURES) {
@@ -244,7 +220,7 @@ static void all_pseudolegal_moves(struct pos const* restrict pos,
/* castling */ /* castling */
if (!chk && type != MG_CAPTURES) { if (!chk && type != MG_CAPTURES) {
bool can_castle_kingside, can_castle_queenside; bool can_castle_kingside, can_castle_queenside;
Bb64 const blocked = pos->occupied[SIDE_WHITE] | pos->occupied[SIDE_BLACK] | their_threats; Bb64 const blocked = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]) | their_threats;
if (us == SIDE_WHITE) { if (us == SIDE_WHITE) {
can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1)) can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1))
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_H1) && (pos->pieces[us][PIECE_ROOK] & SQMASK_H1)

View File

@@ -1,28 +1,29 @@
#pragma once #pragma once
#include "engine-types.h"
struct search_option { struct search_option {
/* TODO: optimize order of fields and size */
double score;
struct move move;
uint64_t hash; uint64_t hash;
uint8_t init : 1; Score16 score;
struct move move;
int depth : 4;
int init : 1;
enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3; enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3;
int8_t depth : 4;
}; };
#define TT_ADDRESS_BITS 24 #define TT_ADDRESS_BITS 24
#define TT_ENTRIES (1ULL<<TT_ADDRESS_BITS) #define TT_ENTRIES (1ULL<<TT_ADDRESS_BITS)
#define TT_MASK (TT_ENTRIES-1)
struct tt { struct tt {
struct search_option* entries/*[TT_ENTRIES]*/; /* must be initialized somewhere */ struct search_option* entries/*[TT_ENTRIES]*/; /* must be initialized somewhere */
size_t mask;
#ifndef NDEBUG
/* stats */ /* stats */
uint64_t collisions; uint64_t collisions;
uint64_t hits; uint64_t hits;
uint64_t probes; uint64_t probes;
uint64_t overwritten; uint64_t overwritten;
uint64_t insertions; uint64_t insertions;
#endif
}; };
struct zobrist { struct zobrist {
@@ -92,10 +93,10 @@ static inline uint64_t tt_hash_switch_side(uint64_t hash)
static inline struct search_option tt_get(struct tt* tt, uint64_t hash) static inline struct search_option tt_get(struct tt* tt, uint64_t hash)
{ {
uint64_t const addr = hash % TT_ENTRIES; uint64_t const addr = hash & tt->mask;
struct search_option tte = tt->entries[addr]; struct search_option tte = tt->entries[addr];
#ifndef NDEBUG #ifndef NSTATS
tt->probes += 1; tt->probes += 1;
if (tte.init && tte.hash == hash) { if (tte.init && tte.hash == hash) {
tt->hits += 1; tt->hits += 1;
@@ -108,10 +109,10 @@ static inline struct search_option tt_get(struct tt* tt, uint64_t hash)
static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so) static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so)
{ {
uint64_t const addr = hash % TT_ENTRIES; uint64_t const addr = hash & tt->mask;
so.init = true; so.init = true;
tt->entries[addr] = so; tt->entries[addr] = so;
#ifndef NDEBUG #ifndef NSTATS
tt->insertions += 1; tt->insertions += 1;
#endif #endif
} }
@@ -123,7 +124,7 @@ static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option
*/ */
static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_option so) static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_option so)
{ {
uint64_t const addr = hash % TT_ENTRIES; uint64_t const addr = hash & tt->mask;
#if 0 #if 0
struct search_option* tte = &tt->entries[addr]; struct search_option* tte = &tt->entries[addr];
@@ -134,7 +135,7 @@ static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_o
so.init = true; so.init = true;
tt->entries[addr] = so; tt->entries[addr] = so;
#ifndef NDEBUG #ifndef NSTATS
tt->insertions += 1; tt->insertions += 1;
#endif #endif
} }

View File

@@ -1,5 +1,14 @@
#pragma once #pragma once
/* --- Score16 (centipawns) ---- */
typedef int16_t Score16;
enum {
SCORE_INF = 9999,
SCORE_CHECKMATE = 999,
};
/* ----------- Index8 ----------- */ /* ----------- Index8 ----------- */
typedef uint8_t Index8; typedef uint8_t Index8;
@@ -140,6 +149,7 @@ typedef enum sq8 : uint8_t {
#define X(file, rank) SQ_##file##rank = SQ_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file), #define X(file, rank) SQ_##file##rank = SQ_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file),
SQUARES_LIST SQUARES_LIST
SQ_COUNT, SQ_COUNT,
SQ_POISONED,
#undef X #undef X
/* define iterator begin enum */ /* define iterator begin enum */
#define X(file, rank) SQ_BEGIN = SQ_##file##rank, #define X(file, rank) SQ_BEGIN = SQ_##file##rank,
@@ -192,24 +202,27 @@ static char const* side_str[SIDE_COUNT] = {
/* ----------- Piece8 ----------- */ /* ----------- Piece8 ----------- */
/* https://en.wikipedia.org/wiki/X_macro */ /* https://en.wikipedia.org/wiki/X_macro */
/* enum value white char white unicode black char black unicode */ /* enum value white char white unicode black char black unicode */
#define PIECES \ #define PIECES \
X(PIECE_PAWN, 1.0, 'P', 0x2659, 'p', 0x265F) \ X(PIECE_EMPTY, 0, ' ', ' ', ' ', ' ') \
X(PIECE_KING, 0.0, 'K', 0x2654, 'k', 0x265A) \ X(PIECE_PAWN, 100, 'P', 0x2659, 'p', 0x265F) \
X(PIECE_QUEEN, 9.0, 'Q', 0x2655, 'q', 0x265B) \ X(PIECE_KNIGHT, 301, 'N', 0x2658, 'n', 0x265E) \
X(PIECE_BISHOP, 3.0, 'B', 0x2657, 'b', 0x265D) \ X(PIECE_BISHOP, 302, 'B', 0x2657, 'b', 0x265D) \
X(PIECE_ROOK, 5.0, 'R', 0x2656, 'r', 0x265C) \ X(PIECE_ROOK, 500, 'R', 0x2656, 'r', 0x265C) \
X(PIECE_KNIGHT, 3.0, 'N', 0x2658, 'n', 0x265E) X(PIECE_QUEEN, 900, 'Q', 0x2655, 'q', 0x265B) \
X(PIECE_KING, 1600, 'K', 0x2654, 'k', 0x265A) \
/**/
typedef enum piece : uint8_t { typedef enum piece : uint8_t {
#define X(e, v, wc, wu, bc, bu) e, #define X(e, v, wc, wu, bc, bu) e,
PIECES PIECES
PIECE_COUNT, PIECE_COUNT,
PIECE_BEGIN = 0, PIECE_BEGIN = PIECE_PAWN,
PIECE_POISONED, /* used as undefined value in debug builds */
#undef X #undef X
} Piece8; } Piece8;
static double piece_value[PIECE_COUNT] = { static Score16 piece_value[PIECE_COUNT] = {
#define X(e, v, wc, wu, bc, bu) [e] = v, #define X(e, v, wc, wu, bc, bu) [e] = v,
PIECES PIECES
#undef X #undef X
@@ -255,11 +268,11 @@ static int const piece_unicode[SIDE_COUNT][PIECE_COUNT] = {
} }
}; };
/* ----------- moves ----------- */ /* ----------- moves ----------- */
enum { enum {
MATTR_PROMOTE = 1<<0, MATTR_PROMOTE = 1<<0,
MATTR_POISONED = 1<<1,
}; };
struct move { struct move {
@@ -269,12 +282,33 @@ struct move {
#define APPEAL_MAX UINT8_MAX #define APPEAL_MAX UINT8_MAX
uint8_t appeal; uint8_t appeal;
}; };
/*
* Move32 layout:
* f (from piece): 3 bits
* F (from square): 6 bits
* t (to piece): 3 bits
* T (to square): 6 bits
* p (promotion): ? bits (likely 3)
* [x,x,x,x,x,x,x,x,x,x,x,p,p,p,T,T,T,T,T,T,t,t,t,F,F,F,F,F,F,f,f,f]
* */
typedef uint32_t Move32;
#define MOVE_FROM_PIECE(m) (m & 0b000000000000000000111)
#define MOVE_FROM_SQUARE(m) (m & 0b000000000000111111000)
#define MOVE_TO_PIECE(m) (m & 0b000000000111000000000)
#define MOVE_TO_SQUARE(m) (m & 0b000111111000000000000)
#define MOVE_PROMOTION(m) (m & 0b111000000000000000000)
_Static_assert(sizeof(struct move) == 4, _Static_assert(sizeof(struct move) == 4,
"this static assuming is here to check when sizeof(move) changes"); "this static assuming is here to check when sizeof(move) changes");
#define NULL_MOVE (struct move){0} #define MOVE_NULL (struct move){0}
#define IS_NULL_MOVE(m) ((m).from == (m).to) #define MOVE_POISONED (struct move) {.from = SQ_POISONED, .to = SQ_POISONED, .attr = MATTR_POISONED }
#define IS_MOVE_NULL(m) ((m).from == (m).to)
/* ----------- castle_direction ----------- */ /* ----------- castle_direction ----------- */
enum castle_direction { enum castle_direction {
@@ -283,3 +317,32 @@ enum castle_direction {
CASTLE_QUEENSIDE, CASTLE_QUEENSIDE,
CASTLE_COUNT, CASTLE_COUNT,
}; };
/* -------------- stop flag --------------- */
/* occupy an entire cache line to avoid invalidation from neighboring writes */
struct searching_flag {
alignas(CACHE_LINE_SIZE) atomic_uint_fast8_t v;
uint8_t pad[CACHE_LINE_SIZE - sizeof(atomic_uint_fast8_t)];
};
static inline void searching_init(struct searching_flag* restrict sf)
{
atomic_init(&sf->v, 0);
}
static inline bool searching_still(struct searching_flag const* restrict sf)
{
return atomic_load_explicit(&sf->v, memory_order_relaxed);
}
static inline void searching_start(struct searching_flag* restrict sf)
{
atomic_store_explicit(&sf->v, 1, memory_order_relaxed);
}
static inline void searching_stop(struct searching_flag* restrict sf)
{
atomic_store_explicit(&sf->v, 0, memory_order_relaxed);
}

322
engine.h
View File

@@ -1,5 +1,8 @@
#pragma once #pragma once
/* WIP switches */
#define NEW_SEARCHING
#include "libc-lite.h" #include "libc-lite.h"
#include "sys.h" #include "sys.h"
@@ -8,6 +11,8 @@
/* temp performance counter */ /* temp performance counter */
static uint64_t g_ab_node_volume = 0; static uint64_t g_ab_node_volume = 0;
static uint64_t g_pvs_re_search = 0;
static uint64_t g_pvs_probes = 0;
#include "engine-macros.h" #include "engine-macros.h"
#include "engine-types.h" #include "engine-types.h"
@@ -20,9 +25,6 @@ static uint64_t g_ab_node_volume = 0;
/* --------------------------- MOVE SEARCH --------------------------------- */ /* --------------------------- MOVE SEARCH --------------------------------- */
#define SCORE_INF 1e30
#define SCORE_CHECKMATE 999.0
/* for initial ordering of moves in alphabeta search */ /* for initial ordering of moves in alphabeta search */
static void move_compute_appeal(struct move* restrict m, static void move_compute_appeal(struct move* restrict m,
struct pos const* restrict pos, struct pos const* restrict pos,
@@ -33,86 +35,91 @@ static void move_compute_appeal(struct move* restrict m,
Side8 them = other_side(us); Side8 them = other_side(us);
Piece8 const atk = mailbox[m->from]; Piece8 const atk = mailbox[m->from];
uint8_t n = 1; uint8_t n = 0;
if (MASK_FROM_SQ(m->to) & pos->occupied[them]) { if ((MASK_FROM_SQ(m->to) & pos->pieces[them][PIECE_EMPTY]) == 0) {
n += (uint8_t)piece_value[mailbox[m->to]]; n += (uint8_t)piece_value[mailbox[m->to]];
} }
uint8_t mmv_lva_bonus = 16*n - (uint8_t)piece_value[atk]; /* TODO: remove branch */
if (n) {
m->appeal = mmv_lva_bonus; m->appeal = (uint8_t)(16*n - piece_value[atk]);
} else {
m->appeal = 0;
}
} }
static double board_score_heuristic(struct pos const* pos) static Score16 board_score_heuristic(struct pos const* pos)
{ {
/* this function always evaluates from white's perspective before /* this function always evaluates from white's perspective before
eventually flipping the sign based on `pos` */ eventually flipping the sign based on `pos` */
double score = 0.0; Score16 score = 0;
Bb64 const occw = pos->occupied[SIDE_WHITE]; Bb64 const occw = ~pos->pieces[SIDE_WHITE][PIECE_EMPTY];
Bb64 const occb = pos->occupied[SIDE_BLACK]; Bb64 const occb = ~pos->pieces[SIDE_BLACK][PIECE_EMPTY];
Bb64 const occall = occw | occb;
enum game_progress const gp = endgameness(pos); enum game_progress const gp = endgameness(pos);
if (pos->pieces[SIDE_WHITE][PIECE_KING] == 0) score -= (double)SCORE_CHECKMATE; if (pos->pieces[SIDE_WHITE][PIECE_KING]) {
if (pos->pieces[SIDE_BLACK][PIECE_KING] == 0) score += (double)SCORE_CHECKMATE; score += SCORE_CHECKMATE;
}
if (pos->pieces[SIDE_BLACK][PIECE_KING]) {
score -= SCORE_CHECKMATE;
}
for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) { for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
/* raw material value */ /* raw material value */
score += piece_value[p] * score += piece_value[p] *
((double)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) - ((Score16)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) -
(double)bitboard_popcount(pos->pieces[SIDE_BLACK][p])); (Score16)bitboard_popcount(pos->pieces[SIDE_BLACK][p]));
/* very minor advantage for threat projection to break tied moves */
if (p != PIECE_PAWN) {
score += 0.001 * (
(double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_WHITE][p], occall))
- (double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_BLACK][p], occall)));
}
/* positional bonus, see evaluations.h */ /* positional bonus, see evaluations.h */
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) { for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
score += positional_modifier(SIDE_WHITE, gp, i, p).val * score += positional_modifier(SIDE_WHITE, gp, i, p).val *
( (
(double)bitboard_popcount( (Score16)bitboard_popcount(
pos->pieces[SIDE_WHITE][p] pos->pieces[SIDE_WHITE][p]
& positional_modifier(SIDE_WHITE, gp, i, p).area) & positional_modifier(SIDE_WHITE, gp, i, p).area)
- (double)bitboard_popcount( - (Score16)bitboard_popcount(
pos->pieces[SIDE_BLACK][p] pos->pieces[SIDE_BLACK][p]
& positional_modifier(SIDE_BLACK, gp, i, p).area) & positional_modifier(SIDE_BLACK, gp, i, p).area)
); );
} }
} }
/* bishop pair */
score += 10 * (Score16)bitboard_more_than_one(pos->pieces[SIDE_WHITE][PIECE_BISHOP]);
score -= 10 * (Score16)bitboard_more_than_one(pos->pieces[SIDE_BLACK][PIECE_BISHOP]);
/* pawns defending pieces are desired */ /* pawns defending pieces are desired */
score += 0.03 * ( score += 3 * (
(double)bitboard_popcount( (Score16)bitboard_popcount(
pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw
) )
- (double)bitboard_popcount( - (Score16)bitboard_popcount(
pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb
) )
); );
/* stacked pawns are bad */ /* stacked pawns are bad */
const double k = 0.30; /*
const Score16 k = 30;
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) { for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi)); uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi));
uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi)); uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi));
score -= k * (double)(wstk - (wstk == 1)); score -= k * (Score16)(wstk - (wstk == 1));
score += k * (double)(bstk - (bstk == 1)); score += k * (Score16)(bstk - (bstk == 1));
} }
*/
double sign = (pos->moving_side == SIDE_WHITE) ? 1.0 : -1.0; Score16 sign = (pos->moving_side == SIDE_WHITE) ? 1 : -1;
return sign*score; return sign*score;
} }
static static
struct move moves_linear_search(struct move moves[restrict static MOVE_MAX], struct move moves_linear_search(struct move moves[restrict static MOVE_MAX],
size_t* restrict move_count) size_t* restrict move_count)
{ {
size_t best = 0; size_t best = 0;
assuming(*move_count > 0); assuming(*move_count > 0);
@@ -131,21 +138,21 @@ struct move moves_linear_search(struct move moves[restrict static MOVE_MAX]
/* quiescence is a deep search that only considers captures */ /* quiescence is a deep search that only considers captures */
static static
double quiesce(struct pos const* pos, Score16 quiesce(struct pos const* pos,
Piece8 mailbox[restrict static SQ_COUNT], Piece8 mailbox[restrict static SQ_COUNT],
Side8 us, Side8 us,
double alpha, Score16 alpha,
double beta, Score16 beta,
int8_t depth) int8_t depth)
{ {
if (pos->pieces[us][PIECE_KING] == 0) { if (pos->pieces[us][PIECE_KING] == 0) {
return -SCORE_CHECKMATE; return -(SCORE_CHECKMATE + depth);
} }
Side8 const them = other_side(us); Side8 const them = other_side(us);
double score = board_score_heuristic(pos); Score16 score = board_score_heuristic(pos);
double highscore = score; Score16 highscore = score;
if (highscore >= beta) { if (highscore >= beta) {
return highscore; return highscore;
@@ -155,14 +162,13 @@ double quiesce(struct pos const* pos,
} }
size_t move_count = 0; size_t move_count = 0;
struct move moves[MOVE_MAX]; struct move moves[MOVE_MAX];
all_pseudolegal_moves(pos, MG_CAPTURES, us, &move_count, moves); all_pseudolegal_moves(pos, MG_CAPTURES, us, &move_count, moves);
if (move_count == 0) { if (move_count == 0) {
/* TODO: detect stalemate */ return score;
return -(999.0 + (double)depth);
} }
for (size_t i = 0; i < move_count; ++i) { for (size_t i = 0; i < move_count; ++i) {
move_compute_appeal(&moves[i], pos, us, mailbox); move_compute_appeal(&moves[i], pos, us, mailbox);
} }
@@ -170,7 +176,7 @@ double quiesce(struct pos const* pos,
while (move_count) { while (move_count) {
struct move m = moves_linear_search(moves, &move_count); struct move m = moves_linear_search(moves, &move_count);
assuming((pos->occupied[them] | pos->ep_targets) & MASK_FROM_SQ(m.to)); assuming((~pos->pieces[them][PIECE_EMPTY] | pos->ep_targets) & MASK_FROM_SQ(m.to));
struct pos poscpy = *pos; struct pos poscpy = *pos;
@@ -180,9 +186,9 @@ double quiesce(struct pos const* pos,
/* history is irrelevant when all moves are captures */ /* history is irrelevant when all moves are captures */
static struct history hist; static struct history hist;
hist.length = 0; hist.length = 0;
(void)move_piece(&poscpy, &hist, mailbox_cpy, m); (void)move_piece(&poscpy, us, &hist, mailbox_cpy, m);
score = -quiesce(&poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1); score = (Score16)-quiesce(&poscpy, mailbox_cpy, them, (Score16)(-beta), (Score16)(-alpha), depth - 1);
if (score >= beta) { if (score >= beta) {
highscore = score; highscore = score;
@@ -208,15 +214,26 @@ struct search_option alphabeta_search(struct pos const* pos,
Piece8 mailbox[restrict static SQ_COUNT], Piece8 mailbox[restrict static SQ_COUNT],
Side8 us, Side8 us,
int8_t depth, int8_t depth,
double alpha, Score16 alpha,
double beta, Score16 beta
atomic_bool* searching) #ifdef FEATURE_STOPPABLE_SEARCH
,
struct searching_flag const* restrict searching
#endif
)
{ {
g_ab_node_volume += 1; g_ab_node_volume += 1;
#ifdef FEATURE_STOPPABLE_SEARCH
if (!searching_still(searching)) {
return (struct search_option) { .init = false, .move = MOVE_NULL };
}
#endif
if (pos->pieces[us][PIECE_KING] == 0) { if (pos->pieces[us][PIECE_KING] == 0) {
return (struct search_option) { return (struct search_option) {
.score = -(SCORE_CHECKMATE + (double)depth), .score = -(SCORE_CHECKMATE + depth),
.move = (struct move){0}, .move = (struct move){0},
.depth = 0, .depth = 0,
.hash = pos->hash, .hash = pos->hash,
@@ -226,7 +243,7 @@ struct search_option alphabeta_search(struct pos const* pos,
} }
if (depth <= 0) { if (depth <= 0) {
double sc = quiesce(pos, mailbox, us, alpha, beta, 0); Score16 sc = quiesce(pos, mailbox, us, alpha, beta, 0);
return (struct search_option){ return (struct search_option){
.score = sc, .score = sc,
.move = (struct move){0}, .move = (struct move){0},
@@ -237,15 +254,20 @@ struct search_option alphabeta_search(struct pos const* pos,
}; };
} }
double const alpha_orig = alpha; Score16 const alpha_orig = alpha;
struct move moves[MOVE_MAX]; struct move moves[MOVE_MAX];
size_t move_count = 0; size_t move_count = 0;
Score16 best_score = -SCORE_INF;
struct move best_move = MOVE_NULL;
bool has_principal_move = false;
struct search_option tte = tt_get(tt, pos->hash); struct search_option tte = tt_get(tt, pos->hash);
if (tte.init && tte.hash == pos->hash) { if (tte.init && tte.hash == pos->hash) {
if (tte.depth >= depth) { if (tte.depth >= depth) {
if (tte.flag == TT_EXACT) { if (tte.flag == TT_EXACT) {
assuming(tte.init);
return tte; return tte;
} else if (tte.flag == TT_LOWER) { } else if (tte.flag == TT_LOWER) {
if (tte.score > alpha) alpha = tte.score; if (tte.score > alpha) alpha = tte.score;
@@ -254,6 +276,7 @@ struct search_option alphabeta_search(struct pos const* pos,
} }
if (alpha >= beta) { if (alpha >= beta) {
assuming(tte.init);
return tte; return tte;
} }
} }
@@ -261,23 +284,25 @@ struct search_option alphabeta_search(struct pos const* pos,
moves[move_count] = tte.move; moves[move_count] = tte.move;
moves[move_count].appeal = APPEAL_MAX; moves[move_count].appeal = APPEAL_MAX;
++move_count; ++move_count;
has_principal_move = true;
} }
double best_score = -SCORE_INF; bool first = true;
struct move best_move = NULL_MOVE;
enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS}; enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS};
for (size_t i = 0; i < sizeof types / sizeof *types; ++i) { for (size_t i = 0; i < sizeof types / sizeof *types; ++i) {
all_pseudolegal_moves(pos, types[i], us, &move_count, moves); all_pseudolegal_moves(pos, types[i], us, &move_count, moves);
for (size_t i = 0; i < move_count; ++i) { for (size_t i = (size_t)has_principal_move; i < move_count; ++i) {
move_compute_appeal(&moves[i], pos, us, mailbox); move_compute_appeal(&moves[i], pos, us, mailbox);
} }
while (move_count > 0) { while (move_count > 0) {
if (!atomic_load_explicit(searching, memory_order_relaxed)) { #ifdef FEATURE_STOPPABLE_SEARCH
if (!searching_still(searching)) {
return (struct search_option) { .init = false }; return (struct search_option) { .init = false };
} }
#endif
struct move m = moves_linear_search(moves, &move_count); struct move m = moves_linear_search(moves, &move_count);
size_t const old_hist_len = hist->length; size_t const old_hist_len = hist->length;
@@ -285,27 +310,77 @@ struct search_option alphabeta_search(struct pos const* pos,
Piece8 mailbox_cpy[SQ_COUNT]; Piece8 mailbox_cpy[SQ_COUNT];
my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy); my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy);
enum move_result r = move_piece(&pos_cpy, hist, mailbox_cpy, m); enum move_result r = move_piece(&pos_cpy, us, hist, mailbox_cpy, m);
double score; Score16 score;
if (r == MR_STALEMATE || r == MR_REPEATS) { if (r == MR_STALEMATE || r == MR_REPEATS) {
score = 0.0; score = 0;
} else { } else {
struct search_option so = alphabeta_search(&pos_cpy, if (first) {
hist, struct search_option so = alphabeta_search(&pos_cpy,
tt, hist,
mailbox_cpy, tt,
other_side(us), mailbox_cpy,
depth - 1, other_side(us),
-beta, depth - 1,
-alpha, (Score16)(-beta),
searching); (Score16)(-alpha)
if (!so.init) { #ifdef FEATURE_STOPPABLE_SEARCH
hist->length = old_hist_len; ,
return (struct search_option){ .init = false, .move = NULL_MOVE }; searching
#endif
);
if (!so.init) {
hist->length = old_hist_len;
return so;
}
score = (Score16)-so.score;
first = false;
} else {
struct search_option so;
assuming(alpha < beta);
g_pvs_probes += 1;
so = alphabeta_search(&pos_cpy,
hist,
tt,
mailbox_cpy,
other_side(us),
depth - 1,
(Score16)(-alpha - 1),
(Score16)(-alpha)
#ifdef FEATURE_STOPPABLE_SEARCH
,
searching
#endif
);
if (!so.init) {
hist->length = old_hist_len;
return so;
}
if ((Score16)-so.score > alpha) {
g_pvs_re_search += 1;
so = alphabeta_search(&pos_cpy,
hist,
tt,
mailbox_cpy,
other_side(us),
depth - 1,
(Score16)(-beta),
(Score16)(-alpha)
#ifdef FEATURE_STOPPABLE_SEARCH
,
searching
#endif
);
if (!so.init) {
hist->length = old_hist_len;
return so;
}
}
score = (Score16)-so.score;
} }
score = -so.score;
} }
hist->length = old_hist_len; hist->length = old_hist_len;
@@ -325,8 +400,8 @@ struct search_option alphabeta_search(struct pos const* pos,
finish_search: finish_search:
if (IS_NULL_MOVE(best_move)) { if (IS_MOVE_NULL(best_move)) {
return (struct search_option){ .init = false, .move = NULL_MOVE }; return (struct search_option){ .init = true, .move = MOVE_NULL, .score = -(SCORE_CHECKMATE + depth) };
} }
enum tt_flag flag = TT_EXACT; enum tt_flag flag = TT_EXACT;
@@ -348,15 +423,25 @@ finish_search:
tt_insert(tt, pos->hash, out); tt_insert(tt, pos->hash, out);
assuming(out.init);
return out; return out;
} }
static static
struct search_result {struct move move; double score;} struct search_result {struct move move; Score16 score;}
search(struct board* b, Side8 us, int8_t max_depth, atomic_bool* searching) search(
struct board* restrict b,
Side8 us,
int8_t max_depth
#ifdef FEATURE_STOPPABLE_SEARCH
,
struct searching_flag const* restrict searching
#endif
)
{ {
struct move moves[MOVE_MAX]; struct move moves[MOVE_MAX];
size_t move_count = 0; size_t move_count = 0;
all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves); all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves);
assuming(move_count); assuming(move_count);
@@ -364,55 +449,76 @@ struct search_result {struct move move; double score;}
struct move best_move = moves[0]; struct move best_move = moves[0];
g_ab_node_volume = 0; g_ab_node_volume = 0;
g_pvs_re_search = 0;
g_pvs_probes = 0;
double score = 0.0; Score16 score = 0;
for (int8_t d = 1; for (int8_t d = 1;
d <= max_depth && atomic_load_explicit(searching, memory_order_relaxed); d <= max_depth
#ifdef FEATURE_STOPPABLE_SEARCH
&& searching_still(searching)
#endif
;
++d) ++d)
{ {
double window = SCORE_INF; /* temp debug solution */ Score16 window;
double alpha = score - window;
double beta = score + window; if (d == 1) {
window = SCORE_INF;
} else {
window = 20 + 8*d;
}
#ifdef FEATURE_USE_PRINTF
fprintf(stderr, "depth: %hhd - window %hd\n", d, window);
#endif
while (1) { while (1) {
int al = (int)score - (int)window;
int bt = (int)score + (int)window;
if (al < -SCORE_INF) al = -SCORE_INF;
if (bt > SCORE_INF) bt = SCORE_INF;
Score16 alpha = (Score16)al;
Score16 beta = (Score16)bt;
struct search_option so = struct search_option so =
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta, searching); alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta
#ifdef FEATURE_STOPPABLE_SEARCH
, searching
#endif
);
if (atomic_load_explicit(searching, memory_order_relaxed) == false) { #ifdef FEATURE_STOPPABLE_SEARCH
break; if (!searching_still(searching)) goto stop_search;
} #endif
if (IS_NULL_MOVE(so.move)) { if (IS_MOVE_NULL(so.move)) goto stop_search;
break;
}
if (so.score > alpha && so.score < beta) { if (so.score >= alpha && so.score <= beta) {
score = so.score; score = so.score;
best_move = so.move; best_move = so.move;
break; break;
} }
window *= 2; if (window < SCORE_INF/2) {
#ifdef FEATURE_USE_PRINTF
if (so.score <= alpha) { fprintf(stderr, "depth: %hhd - expanding window to %hd\n", d, window);
alpha = -SCORE_INF;
beta = score + window;
} else {
alpha = score - window;
beta = SCORE_INF;
}
}
#ifdef USE_PRINTF
fprintf(stderr, "depth: %hhd\n", d);
#endif #endif
window *= 2;
} else {
window = SCORE_INF;
}
}
} }
#undef SCORE_INF stop_search:
#ifdef USE_PRINTF #ifdef FEATURE_USE_PRINTF
fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume); fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume);
fprintf(stderr, "pvs re-searches: %'llu\n", g_pvs_re_search);
fprintf(stderr, "pvs probes: %'llu\n", g_pvs_probes);
#endif #endif
return (struct search_result){.move = best_move, .score = score}; return (struct search_result){.move = best_move, .score = score};

4
sys.h
View File

@@ -20,8 +20,8 @@ static size_t g_buf_len = 0;
static void* sys_mmap_anon_shared(size_t size, int, int) static void* sys_mmap_anon_shared(size_t size, int, int)
{ {
/* FIXME: this program relies on very few memory allocations, a simple bump /* FIXME: this program relies on very few memory allocations, a simple bump
* allocator works for now, but will cause memory leaks in the future */ * allocator works for now, but will cause memory leaks in the future */
size = (size + 7ULL) & ~7ULL; size = (size + 7ULL) & ~7ULL;
if (g_buf_len + size > sizeof g_buf) { if (g_buf_len + size > sizeof g_buf) {

52
tests.c
View File

@@ -1,5 +1,8 @@
#define USE_PRINTF #define FEATURE_STOPPABLE_SEARCH
#define FEATURE_USE_PRINTF
#undef NSTATS
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include <unistd.h> /* usleep */ #include <unistd.h> /* usleep */
#include <pthread.h> #include <pthread.h>
@@ -390,15 +393,15 @@ static void test_bishops(void)
} }
struct timeout_params { struct timeout_params {
atomic_bool* x; struct searching_flag* x;
bool v; uint32_t v;
useconds_t us; useconds_t us;
}; };
void* set_after_timeout(void* x) void* set_after_timeout(void* x)
{ {
struct timeout_params* p = x; struct timeout_params* p = x;
usleep(p->us); usleep(p->us);
*p->x = p->v; searching_stop(p->x);
return NULL; return NULL;
} }
@@ -406,9 +409,11 @@ int main()
{ {
bool const print_threats = true; bool const print_threats = true;
printf("sizeof pos: %zu\n", sizeof (struct pos)); printf("sizeof pos: %zu\n", sizeof (struct pos));
printf("sizeof tt: %zu\n", sizeof (struct tt)); printf("sizeof tt: %zu\n", sizeof (struct tt));
printf("sizeof board: %zu\n", sizeof (struct board)); printf("sizeof board: %zu\n", sizeof (struct board));
printf("sizeof search_option: %zu\n", sizeof (struct search_option));
printf("sizeof all tt entries: %zu\n", (1<<26) * sizeof (struct search_option));
#if 0 #if 0
test_rooks(); test_rooks();
@@ -431,13 +436,12 @@ int main()
//board_load_fen_unsafe(b, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1"); //board_load_fen_unsafe(b, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1");
//board_load_fen_unsafe(b, "8/8/2kr4/6R1/4K3/6P1/8/8 b - - 0 1"); //board_load_fen_unsafe(b, "8/8/2kr4/6R1/4K3/6P1/8/8 b - - 0 1");
//board_load_fen_unsafe(b, "8/8/5R2/8/2K3PP/1B2k3/8/8 b - - 1 4"); //board_load_fen_unsafe(b, "8/8/5R2/8/2K3PP/1B2k3/8/8 b - - 1 4");
//board_load_fen_unsafe(b, "4r1k1/b1P1B3/P5pK/1p4P1/7q/8/4p3/1R6 w - - 1 54");
//board_print_fen(b->pos, stdout); //board_print_fen(b->pos, stdout);
board_print(&b->pos, NULL, stdout, print_threats); board_print(&b->pos, NULL, stdout, print_threats);
struct move moves[MOVE_MAX];
size_t move_count;
for (int turn = 0; turn < 200; ++turn) { for (int turn = 0; turn < 200; ++turn) {
/*
move_count = 0; move_count = 0;
all_pseudolegal_moves(&b->pos, MG_ALL, b->pos.moving_side, &move_count, moves); all_pseudolegal_moves(&b->pos, MG_ALL, b->pos.moving_side, &move_count, moves);
@@ -448,10 +452,12 @@ int main()
board_print(&b->pos, NULL, stdout, print_threats); board_print(&b->pos, NULL, stdout, print_threats);
break; break;
} }
*/
pthread_t timer; pthread_t timer;
atomic_bool searching; struct searching_flag searching;
atomic_init(&searching, true); searching_start(&searching);
//atomic_init(&searching, 1);
#if 1 #if 1
struct timeout_params timer_params = { struct timeout_params timer_params = {
.x = &searching, .x = &searching,
@@ -464,12 +470,12 @@ int main()
struct search_result sr = search(b, b->pos.moving_side, 25, &searching); struct search_result sr = search(b, b->pos.moving_side, 25, &searching);
struct move move = sr.move; struct move move = sr.move;
double const score = sr.score; Score16 const score = sr.score;
printf("move %d: {\n" printf("move %d: {\n"
" .from = %s, (%s)\n" " .from = %s, (%s)\n"
" .to = %s,\n" " .to = %s,\n"
" .score = %lf,\n" " .score = %d,\n"
" .mask = " " .mask = "
"", "",
turn, turn,
@@ -482,11 +488,18 @@ int main()
enum move_result const r = board_move(b, move); enum move_result const r = board_move(b, move);
/* illegal board state from an engine move (i.e. hanging king) means checkmate */
if (!board_is_legal(b)) {
printf("checkmate!\n");
break;
}
#if 1 #if 1
board_print_fen(&b->pos, stdout); board_print_fen(&b->pos, stdout);
tt_print_stats(&b->tt, stdout); tt_print_stats(&b->tt, stdout);
board_print(&b->pos, &move, stdout, print_threats); board_print(&b->pos, &move, stdout, print_threats);
fprintf(stderr, "board hist len: %zu\n", b->hist.length); fprintf(stderr, "board hist len: %zu\n", b->hist.length);
fprintf(stderr, "\n------------------------\n\n\n");
#endif #endif
if (r == MR_STALEMATE) { if (r == MR_STALEMATE) {
@@ -496,14 +509,13 @@ int main()
if (b->pos.pieces[SIDE_WHITE][PIECE_KING] == 0ULL) { if (b->pos.pieces[SIDE_WHITE][PIECE_KING] == 0ULL) {
printf("white king gone!!\n"); printf("white king gone!!\n");
exit(1); exit(EXIT_FAILURE);
}
if (b->pos.pieces[SIDE_BLACK][PIECE_KING] == 0ULL) {
printf("black king gone!!\n");
exit(1);
} }
//usleep(1000000); if (b->pos.pieces[SIDE_BLACK][PIECE_KING] == 0ULL) {
printf("black king gone!!\n");
exit(EXIT_FAILURE);
}
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@@ -1,83 +1,116 @@
/* 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)
static inline uint32_t move_serialize(struct move m)
{ {
_Static_assert(sizeof m.from * CHAR_BIT == 8, g_board = BOARD_INIT;
"this must be checked if struct move's `from` changes"); g_board.tt.entries = g_tt_buf;
_Static_assert(sizeof m.to * CHAR_BIT == 8, g_board.tt.mask = TT_MASK;
"this must be checked if struct move's `to` changes"); board_init(&g_board);
return ((uint32_t)m.appeal << 24ULL)
| ((uint32_t)m.attr << 16ULL)
| ((uint32_t)m.from << 8ULL)
| ((uint32_t)m.to);
} }
static inline struct move move_deserialize(uint64_t m) static inline uint32_t move_serialize(struct move m)
{ {
return (struct move) { _Static_assert(sizeof m.from * CHAR_BIT == 8,
/* appeal and attributes are ignored regardless */ "this must be checked if struct move's `from` changes");
/* _Static_assert(sizeof m.to * CHAR_BIT == 8,
.appeal = (m >> 24) & 0xFF, "this must be checked if struct move's `to` changes");
.attr = (m >> 16) & 0xFF,
*/ return ((uint32_t)m.appeal << 24U)
.from = (m >> 8) & 0xFF, | ((uint32_t)m.attr << 16U)
.to = (m >> 0) & 0xFF, | ((uint32_t)m.from << 8U)
}; | ((uint32_t)m.to << 0U);
}
static inline struct move move_deserialize(uint32_t m)
{
return (struct move) {
/* appeal and attributes are ignored regardless */
/*
.appeal = (m >> 24) & 0xFF,
.attr = (m >> 16) & 0xFF,
*/
.from = (m >> 8) & 0xFF,
.to = (m >> 0) & 0xFF,
};
} }
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) {
return (int32_t)MR_STALEMATE; return (int32_t)MR_STALEMATE;
} }
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()
static int32_t side_piece_serialize(Side8 c, Piece8 pz)
{ {
g_board = BOARD_INIT; return ((c & 0xFF) << 8U)
g_board.tt.entries = g_tt_buf; | (pz & 0xFF);
} }
static int32_t player_piece_serialize(enum player c, enum piece pz) int32_t wb_board_at(uint8_t at)
{ {
return ((c & 0xFF) << 8U) Bb64 const m = MASK_FROM_SQ((Sq8)at);
| (pz & 0xFF); for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) {
} for (Piece8 pz = PIECE_BEGIN; pz < PIECE_COUNT; ++pz) {
if (g_board.pos.pieces[side][pz] & m) {
int32_t wb_board_at(index at) return side_piece_serialize(side, pz);
{ }
bitboard const m = SQ_MASK_FROM_INDEX(at); }
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) { }
for (enum piece pz = PIECE_BEGIN; pz < PIECE_COUNT; ++pz) {
if (g_board.pos.pieces[pl][pz] & m) {
return player_piece_serialize(pl, 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];
}