Compare commits
12 Commits
97b6934024
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e92942cb8 | |||
| e1d1684c23 | |||
| 33a0878f6a | |||
| 123caf8378 | |||
| 29ebb83ead | |||
| bb3d99b011 | |||
| 5a5a392c8b | |||
| 716527fa8f | |||
| 4998a057f5 | |||
| 160ea82549 | |||
| e5b6886f40 | |||
| ef33b23f70 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ mbb_rook.h
|
||||
tests
|
||||
*.dSYM
|
||||
between_lookup.h
|
||||
diagonals.h
|
||||
|
||||
19
Makefile
19
Makefile
@@ -3,14 +3,15 @@ BUILD ?= debug
|
||||
CC := clang
|
||||
|
||||
CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function
|
||||
CFLAGS.gcc.release := -Ofast
|
||||
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address
|
||||
CFLAGS.gcc.release := -Ofast -march=native -DNDEBUG
|
||||
CFLAGS.gcc.debug := -ggdb -O1 -fsanitize=address
|
||||
|
||||
CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion
|
||||
CFLAGS.clang.release := -Ofast
|
||||
CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address
|
||||
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 -DNSTATS
|
||||
CFLAGS.clang.debug := -g3 -O1 -fsanitize=address,undefined
|
||||
CFLAGS.clang.wasm := \
|
||||
--target=wasm32-unknown-unknown -O3 -nostdlib \
|
||||
--target=wasm32-unknown-unknown -nostdlib -g \
|
||||
-DNSTATS \
|
||||
-Wl,--export-all \
|
||||
-Wl,--no-entry
|
||||
|
||||
@@ -21,9 +22,9 @@ all: tests
|
||||
wasm: chess.wasm
|
||||
|
||||
codegen: codegen.c
|
||||
$(CC) -o $@ $(CFLAGS) $^
|
||||
$(CC) -D_DEFAULT_SOURCE -o $@ $(CFLAGS) $^
|
||||
|
||||
chess.wasm: wasm-compat.c
|
||||
chess.wasm: wasm-compat.c mbb_rook.h mbb_bishop.h engine.h
|
||||
$(CC) -DWASM -o $@ wasm-compat.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm)
|
||||
|
||||
mbb_rook.h: codegen
|
||||
@@ -33,4 +34,4 @@ mbb_bishop.h: codegen
|
||||
./codegen
|
||||
|
||||
tests: tests.c mbb_rook.h mbb_bishop.h engine.h
|
||||
$(CC) -o $@ $(CFLAGS) tests.c
|
||||
$(CC) -D_DEFAULT_SOURCE -o $@ $(CFLAGS) tests.c
|
||||
|
||||
174
board_print.h
174
board_print.h
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctype.h>
|
||||
#include <locale.h>
|
||||
@@ -9,15 +10,15 @@
|
||||
|
||||
#define INDEX_FMT PRIu8
|
||||
|
||||
static void bitboard_print(bitboard b, FILE* out)
|
||||
static void bitboard_print(Bb64 b, FILE* out)
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
fprintf(out, "\n");
|
||||
for (index i = 7; i < 8; i--) {
|
||||
for (Index8 i = 7; i < 8; i--) {
|
||||
fprintf(out, "\033[0m"); /* reset background color */
|
||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
index const n = INDEX_FROM_RF(i,j);
|
||||
for (Index8 j = 0; j < 8; ++j) {
|
||||
Index8 const n = SQ_FROM_RF(i,j);
|
||||
int color;
|
||||
if ((b >> n) & 1) {
|
||||
/* 41: red */
|
||||
@@ -35,68 +36,14 @@ static void bitboard_print(bitboard b, FILE* out)
|
||||
fprintf(out, " A B C D E F G H \n");
|
||||
}
|
||||
|
||||
static void board_print_threats(const struct pos* pos, FILE* out, struct move* move)
|
||||
{
|
||||
enum player const player = pos->player;
|
||||
int buf[8][8] = {0};
|
||||
|
||||
bitboard const threats = all_threats_from_player(pos, player);
|
||||
|
||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
bitboard x = pos->pieces[player][piece];
|
||||
|
||||
for (index i = 7; i < 8; i--) {
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
if (x & (1ULL<<(i*8+j))) {
|
||||
buf[i][j] = piece_unicode[player][piece];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
||||
setlocale(LC_ALL, ""); /* needed for unicode symbols */
|
||||
for (index i = 7; i < 8; i--) {
|
||||
fprintf(out, "\033[0m"); /* reset background color */
|
||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
index const n = INDEX_FROM_RF(i,j);
|
||||
|
||||
if (move && n == move->from) {
|
||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 44: blue*/
|
||||
} else if (move && n == move->to) {
|
||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
||||
}
|
||||
else if ((threats >> n) & 1) {
|
||||
fprintf(out, "\033[%d;%dm", 30, 41); /* 41: red */
|
||||
} else {
|
||||
/* 45: magenta, 47: white */
|
||||
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
||||
}
|
||||
if (buf[i][j]) {
|
||||
fprintf(out, "%lc ", buf[i][j]);
|
||||
} else {
|
||||
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
||||
is not working when buf[i][j] = ' ' */
|
||||
}
|
||||
}
|
||||
fprintf(out, "\033[0m"); /* reset background color */
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, " A B C D E F G H \n");
|
||||
}
|
||||
|
||||
static void tt_print_stats(struct tt* tt, FILE* out)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
double sat = 0;
|
||||
for (size_t i = 0; i < TT_ENTRIES; ++i) {
|
||||
if (tt->entries[i].init)
|
||||
sat += 1.0;
|
||||
}
|
||||
double const pct = sat/TT_ENTRIES;
|
||||
double const pct = sat/((double)tt->mask+1.0);
|
||||
|
||||
fprintf(out, "---- Stats ---\n");
|
||||
fprintf(out, "tt collisions: %"PRIu64"\n", tt->collisions);
|
||||
@@ -104,23 +51,20 @@ static void tt_print_stats(struct tt* tt, FILE* out)
|
||||
fprintf(out, "tt probes: %"PRIu64"\n", tt->probes);
|
||||
fprintf(out, "tt insertions: %"PRIu64"\n", tt->insertions);
|
||||
fprintf(out, "saturation: %.02lf\n", pct);
|
||||
#else
|
||||
fprintf(out, "stats not available with NDEBUG\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void board_print_fen(struct pos const* pos, FILE* out)
|
||||
{
|
||||
int buf[8][8] = {0};
|
||||
|
||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
bitboard x = pos->pieces[player][piece];
|
||||
for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) {
|
||||
for (Piece8 piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
Bb64 x = pos->pieces[side][piece];
|
||||
|
||||
for (index i = 7; i < 8; i--) {
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
for (Index8 i = 7; i < 8; i--) {
|
||||
for (Index8 j = 0; j < 8; ++j) {
|
||||
if (x & (1ULL<<(i*8+j))) {
|
||||
buf[i][j] = piece_char[player][piece];
|
||||
buf[i][j] = piece_char[side][piece];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,22 +91,22 @@ static void board_print_fen(struct pos const* pos, FILE* out)
|
||||
fprintf(out, "/");
|
||||
}
|
||||
|
||||
fprintf(out, " %c ", pos->player == PLAYER_WHITE ? 'w' : 'b');
|
||||
fprintf(out, " %c ", pos->moving_side == SIDE_WHITE ? 'w' : 'b');
|
||||
|
||||
bool any_castle = false;
|
||||
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE]) {
|
||||
if (!pos->castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE]) {
|
||||
fprintf(out, "K");
|
||||
any_castle = true;
|
||||
}
|
||||
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE]) {
|
||||
if (!pos->castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE]) {
|
||||
fprintf(out, "Q");
|
||||
any_castle = true;
|
||||
}
|
||||
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE]) {
|
||||
if (!pos->castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE]) {
|
||||
fprintf(out, "k");
|
||||
any_castle = true;
|
||||
}
|
||||
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE]) {
|
||||
if (!pos->castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE]) {
|
||||
fprintf(out, "q");
|
||||
any_castle = true;
|
||||
}
|
||||
@@ -172,20 +116,20 @@ static void board_print_fen(struct pos const* pos, FILE* out)
|
||||
|
||||
if (pos->ep_targets) {
|
||||
/* should be ep target square in algebraic notation */
|
||||
enum square_index const sqi = bitboard_lsb(pos->ep_targets);
|
||||
Sq8 const sqi = bitboard_lsb(pos->ep_targets);
|
||||
|
||||
assert(sqi >= SQ_INDEX_BEGIN && sqi < SQ_INDEX_COUNT);
|
||||
assuming(sqi >= SQ_BEGIN && sqi < SQ_COUNT);
|
||||
|
||||
enum file_index const fi = index_to_file(sqi);
|
||||
enum rank_index const ri = index_to_rank(sqi);
|
||||
enum file_index const fi = sq_to_file(sqi);
|
||||
enum rank_index const ri = sq_to_rank(sqi);
|
||||
|
||||
int const fch = tolower(file_index_char[fi]);
|
||||
int const rch = tolower(rank_index_char[ri]);
|
||||
int const fch = tolower(file_index_char[fi]);
|
||||
int const rch = tolower(rank_index_char[ri]);
|
||||
|
||||
assert(fch >= 'a' && fch <= 'h');
|
||||
assert(rch >= '1' && rch <= '8');
|
||||
assuming(fch >= 'a' && fch <= 'h');
|
||||
assuming(rch >= '1' && rch <= '8');
|
||||
|
||||
fprintf(out, " %c%c", fch, rch);
|
||||
fprintf(out, " %c%c", fch, rch);
|
||||
} else {
|
||||
fprintf(out, " -");
|
||||
}
|
||||
@@ -196,49 +140,79 @@ static void board_print_fen(struct pos const* pos, FILE* out)
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
|
||||
static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
||||
static void board_print(struct pos const* pos, struct move* move, FILE* out, bool print_threats)
|
||||
{
|
||||
int buf[8][8] = {0};
|
||||
int color[8][8] = {0};
|
||||
|
||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
bitboard x = pos->pieces[player][piece];
|
||||
for (Side8 side = SIDE_BEGIN; side < SIDE_COUNT; ++side) {
|
||||
for (Piece8 piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
Bb64 x = pos->pieces[side][piece];
|
||||
|
||||
for (index i = 7; i < 8; i--) {
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
for (Index8 i = 7; i < 8; i--) {
|
||||
for (Index8 j = 0; j < 8; ++j) {
|
||||
if (x & (1ULL<<(i*8+j))) {
|
||||
buf[i][j] = piece_unicode[player][piece];
|
||||
buf[i][j] = piece_unicode[SIDE_BLACK][piece];
|
||||
color[i][j] = (side == SIDE_WHITE)
|
||||
? 1
|
||||
: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bb64 const threats = all_threats_from_side(pos, pos->moving_side);
|
||||
|
||||
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
||||
setlocale(LC_ALL, "");
|
||||
for (index i = 7; i < 8; i--) {
|
||||
for (Index8 i = 7; i < 8; i--) {
|
||||
fprintf(out, "\033[0m"); /* reset background color */
|
||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||
for (index j = 0; j < 8; ++j) {
|
||||
index const n = INDEX_FROM_RF(i,j);
|
||||
if (move && n == move->from) {
|
||||
fprintf(out, "\033[%d;%dm", 30, 104); /* 44: blue*/
|
||||
} else if (move && n == move->to) {
|
||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
||||
} else {
|
||||
/* 45: magenta, 47: white */
|
||||
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
||||
for (Index8 j = 0; j < 8; ++j) {
|
||||
Index8 const n = SQ_FROM_RF(i,j);
|
||||
|
||||
int bg, fg;
|
||||
|
||||
/* foreground color */
|
||||
/**/ if (color[i][j] == 1) {
|
||||
fg = 97; /* bright white */
|
||||
}
|
||||
else if (color[i][j] == 2) {
|
||||
fg = 30; /* black */
|
||||
}
|
||||
else {
|
||||
fg = 35; /* magenta (should not happen) */
|
||||
}
|
||||
|
||||
/* background color */
|
||||
/**/ if (move && (n == move->to || n == move->from)) {
|
||||
bg = 44; /* bright blue */
|
||||
}
|
||||
else if (print_threats && (threats >> n) & 1) {
|
||||
bg = 41;
|
||||
} else {
|
||||
/* 45: magenta,
|
||||
* 43: yellow */
|
||||
bg = (i+j) % 2 ? 45 : 43;
|
||||
}
|
||||
|
||||
fprintf(out, "\033[%d;%dm", fg, bg);
|
||||
|
||||
if (buf[i][j]) {
|
||||
fprintf(out, "%lc ", buf[i][j]);
|
||||
} else {
|
||||
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
||||
is not working when buf[i][j] = ' ' */
|
||||
is not sufficient when buf[i][j] = ' ' */
|
||||
}
|
||||
}
|
||||
fprintf(out, "\033[0m"); /* reset background color */
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
fprintf(out, " A B C D E F G H \n");
|
||||
fprintf(out, "half moves: %d\n"
|
||||
"full moves: %d\n",
|
||||
pos->halfmoves,
|
||||
pos->fullmoves);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
let exports;
|
||||
|
||||
async function init() {
|
||||
const resp = await fetch("./chess.wasm");
|
||||
if (!resp.ok) {
|
||||
throw new Error("fetch wasm failed ${resp.status} ${resp.statusText}");
|
||||
}
|
||||
|
||||
const { instance } =
|
||||
await WebAssembly.instantiateStreaming(resp, {});
|
||||
const resp = await fetch("./chess.wasm?rand=" + crypto.randomUUID());
|
||||
if (!resp.ok) throw new Error(`fetch wasm failed ${resp.status} ${resp.statusText}`);
|
||||
|
||||
const { instance } = await WebAssembly.instantiateStreaming(resp, {});
|
||||
exports = instance.exports;
|
||||
}
|
||||
|
||||
await init();
|
||||
self.postMessage({ type: "ready" });
|
||||
|
||||
self.onmessage = (e) => {
|
||||
const { id, method, args = [] } = e.data;
|
||||
try {
|
||||
const value = exports[method](...args);
|
||||
let value;
|
||||
value = exports[method](...args);
|
||||
self.postMessage({ id, ok: true, value });
|
||||
} catch (err) {
|
||||
self.postMessage({ id, ok: false, error: String(err?.message ?? err) });
|
||||
self.postMessage({
|
||||
id, ok: false, error: String(err?.message ?? err)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="board"></div>
|
||||
<p id="result"></div>
|
||||
<p id="result"></p>
|
||||
</body>
|
||||
</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({
|
||||
ILLEGAL: -1,
|
||||
NORMAL: 0,
|
||||
CHECK: 1,
|
||||
REPEATS: 2,
|
||||
@@ -15,65 +17,44 @@ const _moveResultName = [
|
||||
"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({
|
||||
White: 0,
|
||||
Black: 1,
|
||||
None: 2,
|
||||
});
|
||||
|
||||
const _playerName = [
|
||||
"White",
|
||||
"Black",
|
||||
"None",
|
||||
];
|
||||
|
||||
const Piece = Object.freeze({
|
||||
Pawn: 0,
|
||||
King: 1,
|
||||
Queen: 2,
|
||||
Empty: 0,
|
||||
Pawn: 1,
|
||||
Knight: 2,
|
||||
Bishop: 3,
|
||||
Rook: 4,
|
||||
Knight: 5,
|
||||
Empty: 6,
|
||||
Queen: 5,
|
||||
King: 6,
|
||||
});
|
||||
|
||||
const _pieceName = [
|
||||
"Pawn",
|
||||
"King",
|
||||
"Queen",
|
||||
"Bishop",
|
||||
"Rook",
|
||||
"Knight",
|
||||
"Empty",
|
||||
];
|
||||
|
||||
const _pieceChar = [
|
||||
'P',
|
||||
'K',
|
||||
'Q',
|
||||
'B',
|
||||
'R',
|
||||
'N',
|
||||
' ',
|
||||
];
|
||||
|
||||
const _pieceUnicode = [
|
||||
['♙', '♔', '♕', '♗', '♖', '♘',], // white
|
||||
['♟', '♚', '♛', '♝', '♜', '♞',], // black
|
||||
['' , '', '', '', '', '', ], // none
|
||||
[' ', '♙', '♘', '♗', '♖', '♕', '♔',], // white
|
||||
[' ', '♟', '♞', '♝', '♜', '♛', '♚',], // black
|
||||
[' ', ' ', ' ', ' ', ' ', ' ', ' ',], // none
|
||||
];
|
||||
|
||||
const indexDeserialize = (n) => ({
|
||||
rank: Math.floor(n / 8), // every day we stray further from God
|
||||
rank: Math.floor(n / 8),
|
||||
file: (n % 8),
|
||||
fileChar() { return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][this.file]; },
|
||||
rankChar() { return ['1', '2', '3', '4', '5', '6', '7', '8'][this.rank]; },
|
||||
});
|
||||
|
||||
|
||||
const serializedIndexFromCoord = (rank, file) => (8*rank + file);
|
||||
|
||||
const indexSerialize = (index) => (serializedIndexFromCoord(index.rank, index.file));
|
||||
const serializedIndexFromCoord = (rank, file) => (8 * rank + file);
|
||||
const indexSerialize = (index) => serializedIndexFromCoord(index.rank, index.file);
|
||||
|
||||
const moveDeserialize = (n) => ({
|
||||
appeal: Number((n >> 24n) & 0xFFn),
|
||||
@@ -83,80 +64,50 @@ const moveDeserialize = (n) => ({
|
||||
});
|
||||
|
||||
const moveSerialize = (move) => (
|
||||
(indexSerialize(move.from) & 0xFF) << 8) | ((indexSerialize(move.to) & 0xFF));
|
||||
((indexSerialize(move.from) & 0xFF) << 8) | (indexSerialize(move.to) & 0xFF)
|
||||
);
|
||||
|
||||
const squareDeserialize = (n) => ({
|
||||
player: n == -1 ? Player.None : (n >> 8) & 0xFF,
|
||||
piece: n == -1 ? Piece.Empty : (n & 0xFF),
|
||||
playerName() {
|
||||
if (this.player === Player.None) {
|
||||
return "Empty";
|
||||
} else {
|
||||
return _playerName[this.player];
|
||||
}
|
||||
},
|
||||
pieceName() {
|
||||
if (this.player === Player.None) {
|
||||
return "";
|
||||
} else {
|
||||
return this.playerName() + _pieceName[this.piece];
|
||||
}
|
||||
},
|
||||
pieceUnicode() {
|
||||
return _pieceUnicode[this.player][this.piece];
|
||||
}
|
||||
});
|
||||
const squareDeserialize = (n) => {
|
||||
if (n === -1) {
|
||||
return {
|
||||
player: Player.None,
|
||||
piece: Piece.Empty,
|
||||
unicode: ' ',
|
||||
};
|
||||
}
|
||||
|
||||
const WasmBoard = async (url) => {
|
||||
/*
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
const bytes = await res.arrayBuffer();
|
||||
const instantiated = await WebAssembly.instantiate(bytes, {});
|
||||
const instance = instantiated.instance ?? instantiated;
|
||||
const exports = instance.exports;
|
||||
const player = (n >> 8) & 0xFF;
|
||||
const piece = n & 0xFF;
|
||||
const unicode = _pieceUnicode[player][piece];
|
||||
|
||||
const wb_init = exports.wb_init;
|
||||
const wb_move = exports.wb_move;
|
||||
const wb_search = exports.wb_search;
|
||||
const wb_board_at = exports.wb_board_at;
|
||||
*/
|
||||
const worker = new Worker("./chess-worker.js", { type: "module" });
|
||||
return {
|
||||
player: player,
|
||||
piece: piece,
|
||||
unicode: unicode,
|
||||
};
|
||||
};
|
||||
|
||||
const WasmBoard = async () => {
|
||||
const worker = new Worker("./chess-worker.js?rand=" + crypto.randomUUID(), { type: "module" });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
worker.addEventListener("message", (e) => {
|
||||
if (e.data?.type === "ready") {
|
||||
resolve();
|
||||
}
|
||||
if (e.data?.type === "ready") resolve();
|
||||
}, { once: true });
|
||||
worker.addEventListener("error", reject, { once: true });
|
||||
});
|
||||
|
||||
const wasmCall = (method, args = []) => new Promise((resolve, reject) => {
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
const onMessage = (e) => {
|
||||
if (e.data?.id !== id) {
|
||||
return;
|
||||
}
|
||||
if (e.data?.id !== id) return;
|
||||
cleanup();
|
||||
if (e.data.ok) {
|
||||
resolve(e.data.value);
|
||||
} 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)."));
|
||||
if (e.data.ok) resolve(e.data.value);
|
||||
else reject(new Error(e.data.error));
|
||||
};
|
||||
const onError = (err) => { cleanup(); reject(err); };
|
||||
const onMessageError = () => { cleanup(); reject(new Error("Worker messageerror")); };
|
||||
|
||||
const cleanup = () => {
|
||||
worker.removeEventListener("message", onMessage);
|
||||
@@ -174,96 +125,202 @@ const WasmBoard = async (url) => {
|
||||
await wasmCall("wb_init", []);
|
||||
|
||||
return {
|
||||
state: MoveResult.Normal,
|
||||
state: MoveResult.NORMAL,
|
||||
|
||||
ongoing: function() /* nil -> bool */ {
|
||||
return (this.state != MoveResult.STALEMATE
|
||||
&& this.state != MoveResult.CHECKMATE);
|
||||
search: async function(depth, timeout) {
|
||||
return wasmCall("wb_search", [depth]).then(moveDeserialize);
|
||||
},
|
||||
|
||||
search: async function(depth) /* Int -> Move */ {
|
||||
return wasmCall("wb_search", [depth])
|
||||
.then(moveDeserialize);
|
||||
},
|
||||
|
||||
move: async function (move) /* (Move | SerializedMove) -> MoveResult */ {
|
||||
move: async function(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);
|
||||
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 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 boardElem = document.getElementById("board");
|
||||
const squares = new Array(64);
|
||||
|
||||
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();
|
||||
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 file = 0; file < 8; ++file) {
|
||||
const idx = 8 * rank + file;
|
||||
const el = document.createElement("div");
|
||||
el.className =
|
||||
"square " + ((file + rank) % 2 ? "dark" : "light");
|
||||
el.dataset.file = filesChars[file];
|
||||
el.dataset.rank = ranksChars[rank];
|
||||
|
||||
const sq = await board.at(serializedIndexFromCoord(rank, file));
|
||||
el.textContent = sq.pieceUnicode();
|
||||
|
||||
el.className = "square " + (((file + rank) % 2) ? "dark" : "light");
|
||||
el.dataset.idx = String(idx);
|
||||
boardElem.appendChild(el);
|
||||
squares[8*rank+file] = 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();
|
||||
squares[idx] = el;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sqElems = await createBoardUI(board);
|
||||
for (let i = 0; i < 200; ++i) {
|
||||
await draw(board, sqElems);
|
||||
await nextFrame();
|
||||
createBoardUI();
|
||||
await draw();
|
||||
|
||||
const move = await board.search(7);
|
||||
let selected = null; // 0..63 or null
|
||||
let inputEnabled = true;
|
||||
|
||||
const fromSq = await board.at(move.from);
|
||||
const toSq = await board.at(move.to);
|
||||
const clearSelection = () => {
|
||||
if (selected !== null) squares[selected].classList.remove("blue");
|
||||
selected = null;
|
||||
};
|
||||
|
||||
const mr = board.move(move);
|
||||
if (mr == MoveResult.STALEMATE || mr == MoveResult.CHECKMATE) {
|
||||
const resultEl = document.getElementById("result");
|
||||
resultEl.textContent = _moveResultName[board.state];
|
||||
draw(board, sqElems);
|
||||
await nextFrame();
|
||||
break;
|
||||
const legalHasFrom = (moves, fromIdx) => {
|
||||
for (const mv of moves) {
|
||||
if (((mv >> 8) & 0xFF) === fromIdx) return true;
|
||||
}
|
||||
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) => {
|
||||
console.error(err);
|
||||
});
|
||||
setStatus("White to move.");
|
||||
};
|
||||
|
||||
run().catch((err) => console.error(err));
|
||||
|
||||
|
||||
133
codegen.c
133
codegen.c
@@ -5,60 +5,48 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* gets the next number with the same bitcount, unless it overflows, in which
|
||||
* it returns the first number with popcount+1 */
|
||||
uint64_t next_magic(uint64_t seed) {
|
||||
|
||||
uint64_t u = seed & -seed; // rightmost 1-bit
|
||||
uint64_t v = seed + u; // add it
|
||||
if (v == 0) {
|
||||
if (seed == ~0ULL) {
|
||||
return 0;
|
||||
}
|
||||
return next_magic((1ULL << (popcount(seed)+1)) - 1);
|
||||
}
|
||||
seed = v + (((v ^ seed) / u) >> 2);
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
struct magic rook_magics[SQ_INDEX_COUNT] = {0ULL};
|
||||
bitboard rook_attacks[SQ_INDEX_COUNT][1<<12ULL] = {0};
|
||||
struct magic rook_magics[SQ_COUNT] = {0ULL};
|
||||
Bb64 rook_attacks[SQ_COUNT][1<<12ULL] = {0};
|
||||
|
||||
index rook_relevant_bits[12] = {0};
|
||||
Index8 rook_relevant_bits[12] = {0};
|
||||
size_t rook_relevant_bits_count = 0;
|
||||
|
||||
struct magic bishop_magics[SQ_INDEX_COUNT] = {0ULL};
|
||||
bitboard bishop_attacks[SQ_INDEX_COUNT][1<<9ULL] = {0};
|
||||
struct magic bishop_magics[SQ_COUNT] = {0ULL};
|
||||
Bb64 bishop_attacks[SQ_COUNT][1<<9ULL] = {0};
|
||||
|
||||
index bishop_relevant_bits[9] = {0};
|
||||
Index8 bishop_relevant_bits[9] = {0};
|
||||
size_t bishop_relevant_bits_count = 0;
|
||||
|
||||
bitboard between_lookup[SQ_INDEX_COUNT][SQ_INDEX_COUNT];
|
||||
Bb64 between_lookup[SQ_COUNT][SQ_COUNT];
|
||||
|
||||
for (enum square_index sq_index = SQ_INDEX_BEGIN; sq_index < SQ_INDEX_COUNT; ++sq_index) {
|
||||
enum file_index file = index_to_file(sq_index);
|
||||
enum rank_index rank = index_to_rank(sq_index);
|
||||
fprintf(stderr, "%s ", square_index_display[sq_index]);
|
||||
Bb64 diagonals[SQ_COUNT];
|
||||
|
||||
for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) {
|
||||
diagonals[sq] = diagonals_from_index(sq);
|
||||
}
|
||||
|
||||
for (Sq8 sq_index = SQ_BEGIN; sq_index < SQ_COUNT; ++sq_index) {
|
||||
enum file_index file = sq_to_file(sq_index);
|
||||
enum rank_index rank = sq_to_rank(sq_index);
|
||||
fprintf(stderr, "%s ", sq8_display[sq_index]);
|
||||
|
||||
/* ROOKS
|
||||
* ======================= */
|
||||
{
|
||||
/* generate attack mask */
|
||||
bitboard atk_mask = 0;
|
||||
Bb64 atk_mask = 0;
|
||||
{
|
||||
atk_mask |= FILE_MASK(file) & ~(RANK_MASK_1 | RANK_MASK_8);
|
||||
atk_mask |= RANK_MASK(rank) & ~(FILE_MASK_A | FILE_MASK_H);
|
||||
atk_mask &= ~SQ_MASK_FROM_RF(rank, file);
|
||||
atk_mask &= ~MASK_FROM_RF(rank, file);
|
||||
|
||||
/* populate relevant bits array */
|
||||
rook_relevant_bits_count = 0;
|
||||
bitboard x = atk_mask;
|
||||
Bb64 x = atk_mask;
|
||||
while (x) {
|
||||
const index lsb = bitboard_pop_lsb(&x);
|
||||
Sq8 const lsb = bitboard_pop_lsb(&x);
|
||||
rook_relevant_bits[rook_relevant_bits_count++] = lsb;
|
||||
}
|
||||
}
|
||||
@@ -66,12 +54,12 @@ int main()
|
||||
|
||||
/* brute force the magic number */
|
||||
for (;;) {
|
||||
bitboard magic = my_rand64() & my_rand64() & my_rand64();
|
||||
Bb64 magic = my_rand64() & my_rand64() & my_rand64();
|
||||
|
||||
bitboard atk = 0ULL;
|
||||
for (bitboard test = 0; test < (1ULL<<rook_relevant_bits_count); test++) {
|
||||
Bb64 atk = 0ULL;
|
||||
for (Bb64 test = 0; test < (1ULL<<rook_relevant_bits_count); test++) {
|
||||
/* map test to an occupany set */
|
||||
bitboard occ = 0ULL;
|
||||
Bb64 occ = 0ULL;
|
||||
for (size_t i = 0; i < rook_relevant_bits_count; i++) {
|
||||
occ |= ((test >> i) & 1ULL) << rook_relevant_bits[i];
|
||||
}
|
||||
@@ -102,32 +90,32 @@ int main()
|
||||
* ====================== */
|
||||
{
|
||||
/* generate attack mask */
|
||||
bitboard atk_mask = 0;
|
||||
Bb64 atk_mask = 0;
|
||||
{
|
||||
atk_mask |= diagonals_from_index(sq_index);
|
||||
atk_mask &= ~(RANK_MASK_1 | RANK_MASK_8);
|
||||
atk_mask &= ~(FILE_MASK_A | FILE_MASK_H);
|
||||
atk_mask &= ~SQ_MASK_FROM_RF(rank, file);
|
||||
atk_mask &= ~MASK_FROM_RF(rank, file);
|
||||
|
||||
/* populate relevant bits array */
|
||||
bishop_relevant_bits_count = 0;
|
||||
bitboard x = atk_mask;
|
||||
Bb64 x = atk_mask;
|
||||
while (x) {
|
||||
const index lsb = bitboard_pop_lsb(&x);
|
||||
Sq8 const lsb = bitboard_pop_lsb(&x);
|
||||
bishop_relevant_bits[bishop_relevant_bits_count++] = lsb;
|
||||
}
|
||||
}
|
||||
bishop_magics[sq_index].mask = atk_mask;
|
||||
|
||||
/* brute force the magic number */
|
||||
bitboard magic = 0ULL;
|
||||
Bb64 magic = 0ULL;
|
||||
for (;;) {
|
||||
magic = my_rand64() & my_rand64() & my_rand64();
|
||||
|
||||
bitboard atk = 0ULL;
|
||||
for (bitboard test = 0; test < (1ULL<<bishop_relevant_bits_count); test++) {
|
||||
Bb64 atk = 0ULL;
|
||||
for (Bb64 test = 0; test < (1ULL<<bishop_relevant_bits_count); test++) {
|
||||
/* map test to an occupany set */
|
||||
bitboard occ = 0ULL;
|
||||
Bb64 occ = 0ULL;
|
||||
for (size_t i = 0; i < bishop_relevant_bits_count; i++) {
|
||||
occ |= ((test >> i) & 1ULL) << bishop_relevant_bits[i];
|
||||
}
|
||||
@@ -155,8 +143,8 @@ int main()
|
||||
/* BETWEEN TABLE
|
||||
* ===================== */
|
||||
{
|
||||
for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; ++i) {
|
||||
for (enum square_index j = SQ_INDEX_BEGIN; j < SQ_INDEX_COUNT; ++j) {
|
||||
for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) {
|
||||
for (Sq8 j = SQ_BEGIN; j < SQ_COUNT; ++j) {
|
||||
between_lookup[i][j] = between_mask(i, j);
|
||||
}
|
||||
}
|
||||
@@ -167,6 +155,23 @@ int main()
|
||||
#define TAB " "
|
||||
#define NL "\n"
|
||||
|
||||
{ /* diagonals */
|
||||
FILE* f = fopen("diagonals.h", "w");
|
||||
if (!f) {
|
||||
perror("fopen");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fprintf(f, "static const Bb64 diagonals[SQ_COUNT] = {\n");
|
||||
for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) {
|
||||
fprintf(f, "[%s] = 0x%016"BITBOARD_FMT_X",\n",
|
||||
sq8_str[i],
|
||||
diagonals[i]);
|
||||
}
|
||||
fprintf(f,"};\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
{ /* rooks */
|
||||
FILE* f = fopen("mbb_rook.h", "w");
|
||||
if (!f) {
|
||||
@@ -175,15 +180,15 @@ int main()
|
||||
}
|
||||
|
||||
fprintf(f, "#pragma once\n");
|
||||
fprintf(f, "static const struct magic mbb_rook[SQ_INDEX_COUNT] = {\n");
|
||||
for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; i++) {
|
||||
fprintf(f, "static const struct magic mbb_rook[SQ_COUNT] = {\n");
|
||||
for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; i++) {
|
||||
fprintf(f,
|
||||
TAB "[%s] = (struct magic) {"
|
||||
NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||
NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||
NL TAB "},"
|
||||
NL,
|
||||
square_index_str[i],
|
||||
sq8_str[i],
|
||||
rook_magics[i].magic,
|
||||
rook_magics[i].mask);
|
||||
}
|
||||
@@ -191,9 +196,9 @@ int main()
|
||||
|
||||
fprintf(f,"\n");
|
||||
|
||||
fprintf(f, "static const bitboard rook_attacks[SQ_INDEX_COUNT][1ULL<<12ULL] = {\n");
|
||||
for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) {
|
||||
fprintf(f, "[%s] = {\n", square_index_str[sq]);
|
||||
fprintf(f, "static const Bb64 rook_attacks[SQ_COUNT][1ULL<<12ULL] = {\n");
|
||||
for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) {
|
||||
fprintf(f, "[%s] = {\n", sq8_str[sq]);
|
||||
for (size_t i = 0; i < sizeof rook_attacks[sq] / sizeof *rook_attacks[sq]; i++) {
|
||||
fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", rook_attacks[sq][i]);
|
||||
}
|
||||
@@ -212,15 +217,15 @@ int main()
|
||||
}
|
||||
|
||||
fprintf(f, "\n");
|
||||
fprintf(f, "static const struct magic mbb_bishop[SQ_INDEX_COUNT] = {\n");
|
||||
for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; i++) {
|
||||
fprintf(f, "static const struct magic mbb_bishop[SQ_COUNT] = {\n");
|
||||
for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; i++) {
|
||||
fprintf(f,
|
||||
TAB "[%s] = (struct magic) {"
|
||||
NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||
NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||
NL TAB "},"
|
||||
NL,
|
||||
square_index_str[i],
|
||||
sq8_str[i],
|
||||
bishop_magics[i].magic,
|
||||
bishop_magics[i].mask);
|
||||
}
|
||||
@@ -228,15 +233,16 @@ int main()
|
||||
|
||||
fprintf(f,"\n");
|
||||
|
||||
fprintf(f, "static const bitboard bishop_attacks[SQ_INDEX_COUNT][1ULL<<9ULL] = {\n");
|
||||
for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) {
|
||||
fprintf(f, "[%s] = {\n", square_index_str[sq]);
|
||||
fprintf(f, "static const Bb64 bishop_attacks[SQ_COUNT][1ULL<<9ULL] = {\n");
|
||||
for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) {
|
||||
fprintf(f, "[%s] = {\n", sq8_str[sq]);
|
||||
for (size_t i = 0; i < sizeof bishop_attacks[sq] / sizeof *bishop_attacks[sq]; i++) {
|
||||
fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", bishop_attacks[sq][i]);
|
||||
}
|
||||
fprintf(f, "\n},\n");
|
||||
}
|
||||
fprintf(f,"};\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
{ /* between table */
|
||||
@@ -246,15 +252,16 @@ int main()
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fprintf(f, "static const bitboard between_lookup[SQ_INDEX_COUNT][SQ_INDEX_COUNT] = {\n");
|
||||
for (enum square_index i = SQ_INDEX_BEGIN; i < SQ_INDEX_COUNT; ++i) {
|
||||
fprintf(f, "[%s] = {\n", square_index_str[i]);
|
||||
for (enum square_index j = SQ_INDEX_BEGIN; j < SQ_INDEX_COUNT; ++j) {
|
||||
fprintf(f, "static const Bb64 between_lookup[SQ_COUNT][SQ_COUNT] = {\n");
|
||||
for (Sq8 i = SQ_BEGIN; i < SQ_COUNT; ++i) {
|
||||
fprintf(f, "[%s] = {\n", sq8_str[i]);
|
||||
for (Sq8 j = SQ_BEGIN; j < SQ_COUNT; ++j) {
|
||||
fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", between_lookup[i][j]);
|
||||
}
|
||||
fprintf(f, "\n},\n");
|
||||
}
|
||||
fprintf(f,"};\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
#undef TAB
|
||||
|
||||
610
engine-attack-sets.h
Normal file
610
engine-attack-sets.h
Normal file
@@ -0,0 +1,610 @@
|
||||
#pragma once
|
||||
|
||||
#include "engine-types.h"
|
||||
#include "engine-board.h"
|
||||
|
||||
/* `struct magic` is used by magic bitboard lookups, see
|
||||
* rook_attacks_from_index and bishop_attacks_from_index */
|
||||
struct magic {
|
||||
Bb64 mask;
|
||||
Bb64 magic;
|
||||
};
|
||||
|
||||
static Bb64 cardinals_from_index(Sq8 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)
|
||||
{
|
||||
#ifdef CODEGEN
|
||||
enum rank_index const rank = sq_to_rank(sq);
|
||||
enum file_index const file = sq_to_file(sq);
|
||||
|
||||
Bb64 mask = 0ULL;
|
||||
|
||||
_Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned");
|
||||
_Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned");
|
||||
|
||||
enum rank_index r;
|
||||
enum file_index f;
|
||||
for (r = rank+1, f = file+1;
|
||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||
++r, ++f)
|
||||
{
|
||||
mask |= MASK_FROM_RF(r, f);
|
||||
}
|
||||
|
||||
for (r = rank+1, f = file-1;
|
||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||
++r, --f)
|
||||
{
|
||||
mask |= MASK_FROM_RF(r, f);
|
||||
}
|
||||
|
||||
for (r = rank-1, f = file+1;
|
||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||
--r, ++f)
|
||||
{
|
||||
mask |= MASK_FROM_RF(r, f);
|
||||
}
|
||||
|
||||
for (r = rank-1, f = file-1;
|
||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||
--r, --f)
|
||||
{
|
||||
mask |= MASK_FROM_RF(r, f);
|
||||
}
|
||||
return mask;
|
||||
|
||||
#else
|
||||
#if ! __has_include("diagonals.h")
|
||||
#error "compile with -DCODEGEN and run once to generate required header files"
|
||||
#endif
|
||||
#include "diagonals.h" /* defines static Bb64 diagonals[SQ_COUNT]; */
|
||||
return diagonals[sq];
|
||||
#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
|
||||
* ==================
|
||||
*
|
||||
* All piece attack functions rely on the caller masking their own pieces.
|
||||
* e.g. `knight_attacks(knights) & empty`
|
||||
* */
|
||||
|
||||
static Bb64 knight_attacks(Bb64 p)
|
||||
{
|
||||
Bb64 const l1 = MASK_SHIFT_EAST_1(p /*& ~FILE_MASK_H*/);
|
||||
Bb64 const l2 = MASK_SHIFT_EAST_2(p /*& ~(FILE_MASK_G | FILE_MASK_H)*/);
|
||||
Bb64 const r1 = MASK_SHIFT_WEST_1 (p /*& ~FILE_MASK_A*/);
|
||||
Bb64 const r2 = MASK_SHIFT_WEST_2 (p /*& ~(FILE_MASK_A | FILE_MASK_B)*/);
|
||||
Bb64 const h1 = l1 | r1;
|
||||
Bb64 const h2 = l2 | r2;
|
||||
return MASK_SHIFT_NORTH_2(h1)
|
||||
| MASK_SHIFT_SOUTH_2(h1)
|
||||
| MASK_SHIFT_NORTH_1(h2)
|
||||
| MASK_SHIFT_SOUTH_1(h2);
|
||||
}
|
||||
|
||||
static Bb64 knight_attacks_from_index(Sq8 sq)
|
||||
{
|
||||
return knight_attacks(MASK_FROM_SQ(sq));
|
||||
}
|
||||
|
||||
static Bb64 rook_attacks_from_index(Sq8 sq, Bb64 occ)
|
||||
{
|
||||
#ifdef CODEGEN
|
||||
enum rank_index const rank = sq_to_rank(sq);
|
||||
enum file_index const file = sq_to_file(sq);
|
||||
|
||||
Bb64 atk = 0ULL;
|
||||
|
||||
/* following loops assuming rank and file types are unsigned, which relies on C23 enums */
|
||||
_Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned");
|
||||
_Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned");
|
||||
|
||||
for (enum rank_index walk_rank = rank+1; walk_rank <= RANK_INDEX_8; ++walk_rank) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (enum rank_index walk_rank = rank-1; walk_rank <= RANK_INDEX_8; --walk_rank) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (enum file_index walk_file = file+1; walk_file <= FILE_INDEX_H; ++walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (enum file_index walk_file = file-1; walk_file <= FILE_INDEX_H; --walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return atk;
|
||||
|
||||
#else
|
||||
#if ! __has_include("mbb_rook.h")
|
||||
#error "compile with -DCODEGEN and run once to generate required header files"
|
||||
#else
|
||||
|
||||
/* defines
|
||||
- `mbb_rook[SQ_COUNT]`
|
||||
- `Bb64 rook_attacks[SQ_COUNT][1<<12ULL] */
|
||||
#include "mbb_rook.h"
|
||||
|
||||
struct magic const m = mbb_rook[sq];
|
||||
occ &= m.mask;
|
||||
occ *= m.magic;
|
||||
occ >>= (64ULL-12ULL);
|
||||
assuming(rook_attacks[sq][occ] != MASK_FROM_SQ(sq));
|
||||
return rook_attacks[sq][occ];
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static Bb64 rook_attacks(Bb64 p, Bb64 occ)
|
||||
{
|
||||
Bb64 b = 0ULL;
|
||||
while (p) {
|
||||
Sq8 const lsb = bitboard_pop_lsb(&p);
|
||||
b |= rook_attacks_from_index(lsb, occ);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static Bb64 bishop_attacks_from_index(Sq8 sq, Bb64 occ)
|
||||
{
|
||||
#ifdef CODEGEN
|
||||
enum rank_index const rank = sq_to_rank(sq);
|
||||
enum file_index const file = sq_to_file(sq);
|
||||
|
||||
Bb64 atk = 0ULL;
|
||||
|
||||
/* following loops assuming rank and file types are unsigned, which relies on C23 enums */
|
||||
_Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned");
|
||||
_Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned");
|
||||
|
||||
enum rank_index walk_rank;
|
||||
enum file_index walk_file;
|
||||
for (walk_rank = rank+1, walk_file = file+1;
|
||||
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||
++walk_rank, ++walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (walk_rank = rank+1, walk_file = file-1;
|
||||
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||
++walk_rank, --walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (walk_rank = rank-1, walk_file = file+1;
|
||||
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||
--walk_rank, ++walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (walk_rank = rank-1, walk_file = file-1;
|
||||
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||
--walk_rank, --walk_file) {
|
||||
Bb64 const sq = MASK_FROM_RF(walk_rank, walk_file);
|
||||
atk |= sq;
|
||||
if (occ & sq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return atk;
|
||||
#else
|
||||
#if ! __has_include("mbb_bishop.h")
|
||||
#error "compile with -DCODEGEN and run once to generate required header files"
|
||||
#else
|
||||
|
||||
/* defines
|
||||
- `mbb_bishop[SQ_COUNT]`
|
||||
- `Bb64 bishop_attacks[SQ_COUNT][1<<9ULL] */
|
||||
#include "mbb_bishop.h"
|
||||
assuming(sq < SQ_COUNT);
|
||||
struct magic const m = mbb_bishop[sq];
|
||||
occ &= m.mask;
|
||||
occ *= m.magic;
|
||||
occ >>= (64ULL-9ULL);
|
||||
return bishop_attacks[sq][occ];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static Bb64 bishop_attacks(Bb64 p, Bb64 occ)
|
||||
{
|
||||
Bb64 b = 0ULL;
|
||||
while (p) {
|
||||
Sq8 const lsb = bitboard_pop_lsb(&p);
|
||||
b |= bishop_attacks_from_index(lsb, occ);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static Bb64 queen_attacks_from_index(Sq8 sq, Bb64 occ)
|
||||
{
|
||||
return bishop_attacks_from_index(sq, occ) | rook_attacks_from_index(sq, occ);
|
||||
}
|
||||
|
||||
static Bb64 queen_attacks(Bb64 p, Bb64 occ)
|
||||
{
|
||||
Bb64 b = 0ULL;
|
||||
while (p) {
|
||||
Sq8 const lsb = bitboard_pop_lsb(&p);
|
||||
b |= queen_attacks_from_index(lsb, occ);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
#define DEFINE_PAWN_MOVE_FUNCTIONS(identifier, side_enum, direction, starting_rank, next_rank) \
|
||||
static inline Bb64 pawn_moves_##identifier(Bb64 p, Bb64 empty)\
|
||||
{\
|
||||
Bb64 const single_push = MASK_SHIFT_##direction##_1(p) & empty;\
|
||||
Bb64 const double_push =\
|
||||
MASK_SHIFT_##direction##_1(single_push & next_rank) & empty;\
|
||||
return (single_push | double_push);\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_moves_##identifier##_from_index(Sq8 sq, Bb64 empty)\
|
||||
{\
|
||||
return pawn_moves_##identifier(MASK_FROM_SQ(sq), empty);\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_single_push_##identifier(Bb64 p, Bb64 empty)\
|
||||
{\
|
||||
return MASK_SHIFT_##direction##_1(p) & empty;\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_double_push_##identifier(Bb64 p, Bb64 empty)\
|
||||
{\
|
||||
Bb64 const once = MASK_SHIFT_##direction##_1(p) & empty;\
|
||||
Bb64 const twice = MASK_SHIFT_##direction##_1(once) & empty;\
|
||||
return twice;\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_attacks_right_##identifier(Bb64 p)\
|
||||
{\
|
||||
Bb64 const up = MASK_SHIFT_##direction##_1(p);\
|
||||
return MASK_SHIFT_EAST_1(up);\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_attacks_left_##identifier(Bb64 p)\
|
||||
{\
|
||||
Bb64 const up = MASK_SHIFT_##direction##_1(p);\
|
||||
return MASK_SHIFT_WEST_1(up);\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_attacks_##identifier(Bb64 p)\
|
||||
{\
|
||||
Bb64 const up = MASK_SHIFT_##direction##_1(p);\
|
||||
Bb64 const captures_right = MASK_SHIFT_EAST_1(up);\
|
||||
Bb64 const captures_left = MASK_SHIFT_WEST_1(up);\
|
||||
\
|
||||
return (captures_right | captures_left);\
|
||||
}\
|
||||
\
|
||||
static inline Bb64 pawn_attacks_##identifier##_from_index(Sq8 sq)\
|
||||
{\
|
||||
return pawn_attacks_##identifier(MASK_FROM_SQ(sq));\
|
||||
}
|
||||
|
||||
DEFINE_PAWN_MOVE_FUNCTIONS(white, SIDE_WHITE, NORTH, RANK_MASK_2, RANK_MASK_3)
|
||||
DEFINE_PAWN_MOVE_FUNCTIONS(black, SIDE_BLACK, SOUTH, RANK_MASK_7, RANK_MASK_6)
|
||||
|
||||
/* temporary solution, annoying branch */
|
||||
static inline Bb64 pawn_moves_dispatch(Bb64 p, Bb64 empty, Side8 attacker)
|
||||
{
|
||||
if (attacker == SIDE_WHITE) {
|
||||
return pawn_moves_white(p, empty);
|
||||
} else {
|
||||
return pawn_moves_black(p, empty);
|
||||
}
|
||||
}
|
||||
|
||||
/* temporary solution, annoying branch */
|
||||
static inline Bb64 pawn_moves_from_index_dispatch(Sq8 sq, Bb64 empty, Side8 attacker)
|
||||
{
|
||||
if (attacker == SIDE_WHITE) {
|
||||
return pawn_moves_white_from_index(sq, empty);
|
||||
} else {
|
||||
return pawn_moves_black_from_index(sq, empty);
|
||||
}
|
||||
}
|
||||
|
||||
/* temporary solution, annoying branch */
|
||||
static inline Bb64 pawn_attacks_from_index_dispatch(Sq8 sq, Side8 attacker)
|
||||
{
|
||||
if (attacker == SIDE_WHITE) {
|
||||
return pawn_attacks_white_from_index(sq);
|
||||
} else {
|
||||
return pawn_attacks_black_from_index(sq);
|
||||
}
|
||||
}
|
||||
|
||||
static inline Bb64 pawn_attacks_dispatch(Bb64 b, Side8 attacker)
|
||||
{
|
||||
if (attacker == SIDE_WHITE) {
|
||||
return pawn_attacks_white(b);
|
||||
} else {
|
||||
return pawn_attacks_black(b);
|
||||
}
|
||||
}
|
||||
|
||||
static Bb64 king_attacks(Bb64 sq)
|
||||
{
|
||||
/* potential untested improvements:
|
||||
* - lookup table
|
||||
* - union of three files, three ranks and ~sq */
|
||||
Bb64 const n = MASK_SHIFT_NORTH_1(sq);
|
||||
Bb64 const s = MASK_SHIFT_SOUTH_1(sq);
|
||||
Bb64 const w = MASK_SHIFT_WEST_1(sq);
|
||||
Bb64 const e = MASK_SHIFT_EAST_1(sq);
|
||||
|
||||
Bb64 const nw = MASK_SHIFT_WEST_1(n);
|
||||
Bb64 const ne = MASK_SHIFT_EAST_1(n);
|
||||
Bb64 const sw = MASK_SHIFT_WEST_1(s);
|
||||
Bb64 const se = MASK_SHIFT_EAST_1(s);
|
||||
|
||||
return (n | s | w | e | nw | ne | sw | se);
|
||||
}
|
||||
|
||||
static Bb64 king_attacks_from_index(Sq8 sq)
|
||||
{
|
||||
return king_attacks(MASK_FROM_SQ(sq));
|
||||
}
|
||||
|
||||
static Bb64 non_pawn_piece_attacks(Piece8 piece, Bb64 p, Bb64 occ)
|
||||
{
|
||||
assuming(piece != PIECE_PAWN);
|
||||
|
||||
switch (piece) {
|
||||
case PIECE_KNIGHT: {
|
||||
return knight_attacks(p);
|
||||
} break;
|
||||
|
||||
case PIECE_BISHOP: {
|
||||
return bishop_attacks(p, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_ROOK: {
|
||||
return rook_attacks(p, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_QUEEN: {
|
||||
return queen_attacks(p, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_KING: {
|
||||
return king_attacks(p);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
assuming(false);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static Bb64 non_pawn_piece_attacks_from_index(Piece8 piece, Sq8 sq, Bb64 occ)
|
||||
{
|
||||
assuming(piece != PIECE_PAWN);
|
||||
|
||||
switch (piece) {
|
||||
case PIECE_KNIGHT: {
|
||||
return knight_attacks_from_index(sq);
|
||||
} break;
|
||||
|
||||
case PIECE_BISHOP: {
|
||||
return bishop_attacks_from_index(sq, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_ROOK: {
|
||||
return rook_attacks_from_index(sq, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_QUEEN: {
|
||||
return queen_attacks_from_index(sq, occ);
|
||||
} break;
|
||||
|
||||
case PIECE_KING: {
|
||||
return king_attacks_from_index(sq);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
assuming(false);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static Bb64 between_mask(Sq8 a, Sq8 b)
|
||||
{
|
||||
#ifdef CODEGEN
|
||||
enum rank_index const ra = sq_to_rank(a);
|
||||
enum file_index const fa = sq_to_file(a);
|
||||
enum rank_index const rb = sq_to_rank(b);
|
||||
enum file_index const fb = sq_to_file(b);
|
||||
|
||||
/* directional differences */
|
||||
int const dr = (int)rb - (int)ra;
|
||||
int const df = (int)fb - (int)fa;
|
||||
|
||||
int step_r = 0;
|
||||
int step_f = 0;
|
||||
|
||||
if (df == 0 && dr != 0) {
|
||||
step_r = (dr > 0) ? 1 : -1;
|
||||
step_f = 0;
|
||||
}
|
||||
else if (dr == 0 && df != 0) {
|
||||
step_r = 0;
|
||||
step_f = (df > 0) ? 1 : -1;
|
||||
}
|
||||
else if (dr != 0 && (dr > 0 ? dr : -dr) == (df > 0 ? df : -df)) {
|
||||
step_r = (dr > 0) ? 1 : -1;
|
||||
step_f = (df > 0) ? 1 : -1;
|
||||
}
|
||||
else {
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
Bb64 mask = 0ULL;
|
||||
|
||||
enum rank_index r = (enum rank_index)((int)ra + step_r);
|
||||
enum file_index f = (enum file_index)((int)fa + step_f);
|
||||
|
||||
while (r != rb || f != fb) {
|
||||
mask |= MASK_FROM_RF(r, f);
|
||||
r = (enum rank_index)((int)r + step_r);
|
||||
f = (enum file_index)((int)f + step_f);
|
||||
}
|
||||
|
||||
return mask;
|
||||
#else
|
||||
#if ! __has_include("between_lookup.h")
|
||||
#error "compile codegen.c and run once to generate required header files"
|
||||
#else
|
||||
|
||||
/* defines static between_lookup[sq8_COUNT][sq8_COUNT] */
|
||||
#include "between_lookup.h"
|
||||
return between_lookup[a][b];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static Bb64 attacks_to(struct pos const* pos,
|
||||
Bb64 targets,
|
||||
Bb64 pretend_occupied,
|
||||
Bb64 pretend_empty)
|
||||
{
|
||||
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* pw = pos->pieces[SIDE_WHITE];
|
||||
Bb64 const* pb = pos->pieces[SIDE_BLACK];
|
||||
|
||||
Bb64 const wps = pw[PIECE_PAWN];
|
||||
|
||||
Bb64 const bps = pb[PIECE_PAWN];
|
||||
|
||||
Bb64 const ns = (pw[PIECE_KNIGHT]
|
||||
| pb[PIECE_KNIGHT])
|
||||
;
|
||||
Bb64 const ks = (pw[PIECE_KING]
|
||||
| pb[PIECE_KING])
|
||||
;
|
||||
Bb64 const qs = (pw[PIECE_QUEEN]
|
||||
| pb[PIECE_QUEEN])
|
||||
;
|
||||
Bb64 const qrs = (pw[PIECE_ROOK]
|
||||
| pb[PIECE_ROOK]
|
||||
| qs)
|
||||
;
|
||||
Bb64 const qbs = (pw[PIECE_BISHOP]
|
||||
| pb[PIECE_BISHOP]
|
||||
| qs)
|
||||
;
|
||||
|
||||
return ((bps & pawn_attacks_white(targets))
|
||||
| (wps & pawn_attacks_black(targets))
|
||||
| (ns & knight_attacks(targets))
|
||||
| (qbs & bishop_attacks(targets, occ))
|
||||
| (qrs & rook_attacks(targets, occ))
|
||||
| (ks & king_attacks(targets)))
|
||||
& ~pretend_occupied;
|
||||
}
|
||||
|
||||
static
|
||||
Bb64 checkers(struct pos const* pos, Side8 us)
|
||||
{
|
||||
/* TODO: specialize */
|
||||
return attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) & pos->pieces[us][PIECE_EMPTY];
|
||||
}
|
||||
|
||||
static
|
||||
Bb64 pinning_lines_to_target(struct pos const* pos, Side8 us, Sq8 tsq)
|
||||
{
|
||||
Side8 const them = other_side(us);
|
||||
Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY];
|
||||
Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY];
|
||||
|
||||
Bb64 const* p = pos->pieces[them];
|
||||
Bb64 const bqs = p[PIECE_QUEEN] | p[PIECE_BISHOP];
|
||||
Bb64 const rqs = p[PIECE_QUEEN] | p[PIECE_ROOK];
|
||||
|
||||
Bb64 pinners = (bqs & bishop_attacks_from_index(tsq, their_occ))
|
||||
| (rqs & rook_attacks_from_index(tsq, their_occ));
|
||||
|
||||
Bb64 pinned = 0;
|
||||
while (pinners) {
|
||||
Sq8 const sq = bitboard_pop_lsb(&pinners);
|
||||
Bb64 const blockers = between_mask(tsq, sq) & our_occ;
|
||||
if (!bitboard_more_than_one(blockers)) {
|
||||
pinned |= blockers;
|
||||
}
|
||||
}
|
||||
|
||||
return pinned;
|
||||
}
|
||||
|
||||
static
|
||||
Bb64 all_threats_from_side(struct pos const * pos, Side8 who)
|
||||
{
|
||||
Bb64 const occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
|
||||
|
||||
Bb64 const* p = pos->pieces[who];
|
||||
Bb64 t = 0ULL;
|
||||
t |= pawn_attacks_dispatch(p[PIECE_PAWN], who);
|
||||
t |= knight_attacks(p[PIECE_KNIGHT]);
|
||||
t |= bishop_attacks(p[PIECE_BISHOP], occ);
|
||||
t |= rook_attacks(p[PIECE_ROOK], occ);
|
||||
t |= queen_attacks(p[PIECE_QUEEN], occ);
|
||||
t |= king_attacks(p[PIECE_KING]);
|
||||
return t;
|
||||
}
|
||||
|
||||
143
engine-bitboard.h
Normal file
143
engine-bitboard.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
typedef uint64_t Bb64;
|
||||
|
||||
#define BITBOARD( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||
(Bb64)\
|
||||
0b##\
|
||||
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||
h7##g7##f7##e7##d7##c7##b7##a7##\
|
||||
h6##g6##f6##e6##d6##c6##b6##a6##\
|
||||
h5##g5##f5##e5##d5##c5##b5##a5##\
|
||||
h4##g4##f4##e4##d4##c4##b4##a4##\
|
||||
h3##g3##f3##e3##d3##c3##b3##a3##\
|
||||
h2##g2##f2##e2##d2##c2##b2##a2##\
|
||||
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||
ULL
|
||||
|
||||
#define FILE_MASK(n) MASK_SHIFT_EAST((Bb64)0x0101010101010101ULL, n)
|
||||
#define RANK_MASK(n) MASK_SHIFT_NORTH((Bb64)0x00000000000000FFULL, n)
|
||||
#define MASK_FROM_SQ(idx) (((Bb64)1ULL)<<((Index8)idx))
|
||||
#define MASK_FROM_RF(rank, file) (MASK_FROM_SQ(SQ_FROM_RF(rank,file)))
|
||||
|
||||
enum : Bb64 {
|
||||
FILE_MASK_A = FILE_MASK(FILE_INDEX_A),
|
||||
FILE_MASK_B = FILE_MASK(FILE_INDEX_B),
|
||||
FILE_MASK_C = FILE_MASK(FILE_INDEX_C),
|
||||
FILE_MASK_D = FILE_MASK(FILE_INDEX_D),
|
||||
FILE_MASK_E = FILE_MASK(FILE_INDEX_E),
|
||||
FILE_MASK_F = FILE_MASK(FILE_INDEX_F),
|
||||
FILE_MASK_G = FILE_MASK(FILE_INDEX_G),
|
||||
FILE_MASK_H = FILE_MASK(FILE_INDEX_H),
|
||||
|
||||
RANK_MASK_1 = RANK_MASK(RANK_INDEX_1),
|
||||
RANK_MASK_2 = RANK_MASK(RANK_INDEX_2),
|
||||
RANK_MASK_3 = RANK_MASK(RANK_INDEX_3),
|
||||
RANK_MASK_4 = RANK_MASK(RANK_INDEX_4),
|
||||
RANK_MASK_5 = RANK_MASK(RANK_INDEX_5),
|
||||
RANK_MASK_6 = RANK_MASK(RANK_INDEX_6),
|
||||
RANK_MASK_7 = RANK_MASK(RANK_INDEX_7),
|
||||
RANK_MASK_8 = RANK_MASK(RANK_INDEX_8),
|
||||
|
||||
MASK_SHIFT_EAST_GUARD_0 = ~0ULL,
|
||||
MASK_SHIFT_EAST_GUARD_1 = ~FILE_MASK_H,
|
||||
MASK_SHIFT_EAST_GUARD_2 = ~(FILE_MASK_G | FILE_MASK_H),
|
||||
MASK_SHIFT_EAST_GUARD_3 = ~(FILE_MASK_F | FILE_MASK_G | FILE_MASK_H),
|
||||
MASK_SHIFT_EAST_GUARD_4 = ~(FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H),
|
||||
MASK_SHIFT_EAST_GUARD_5 = ~(FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H),
|
||||
MASK_SHIFT_EAST_GUARD_6 = ~(FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H),
|
||||
MASK_SHIFT_EAST_GUARD_7 = ~(FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G | FILE_MASK_H),
|
||||
|
||||
MASK_SHIFT_WEST_GUARD_0 = ~0ULL,
|
||||
MASK_SHIFT_WEST_GUARD_1 = ~FILE_MASK_A,
|
||||
MASK_SHIFT_WEST_GUARD_2 = ~(FILE_MASK_A | FILE_MASK_B),
|
||||
MASK_SHIFT_WEST_GUARD_3 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C),
|
||||
MASK_SHIFT_WEST_GUARD_4 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D),
|
||||
MASK_SHIFT_WEST_GUARD_5 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E),
|
||||
MASK_SHIFT_WEST_GUARD_6 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F),
|
||||
MASK_SHIFT_WEST_GUARD_7 = ~(FILE_MASK_A | FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G),
|
||||
};
|
||||
|
||||
#define _MASK_SHIFT_EAST_GUARDED(b, n) \
|
||||
(((Bb64)(b) & MASK_SHIFT_EAST_GUARD_##n) << (Index8)(n))
|
||||
|
||||
#define _MASK_SHIFT_WEST_GUARDED(b, n) \
|
||||
(((Bb64)(b) & MASK_SHIFT_WEST_GUARD_##n) >> (Index8)(n))
|
||||
|
||||
/* MASK_SHIFT_EAST_n shifts the bitboard right by n ranks without wrapping */
|
||||
#define MASK_SHIFT_EAST_1(b) _MASK_SHIFT_EAST_GUARDED(b, 1)
|
||||
#define MASK_SHIFT_EAST_2(b) _MASK_SHIFT_EAST_GUARDED(b, 2)
|
||||
#define MASK_SHIFT_EAST_3(b) _MASK_SHIFT_EAST_GUARDED(b, 3)
|
||||
#define MASK_SHIFT_EAST_4(b) _MASK_SHIFT_EAST_GUARDED(b, 4)
|
||||
#define MASK_SHIFT_EAST_5(b) _MASK_SHIFT_EAST_GUARDED(b, 5)
|
||||
#define MASK_SHIFT_EAST_6(b) _MASK_SHIFT_EAST_GUARDED(b, 6)
|
||||
#define MASK_SHIFT_EAST_7(b) _MASK_SHIFT_EAST_GUARDED(b, 7)
|
||||
#define MASK_SHIFT_EAST_8(b) _MASK_SHIFT_EAST_GUARDED(b, 8)
|
||||
|
||||
/* MASK_SHIFT_WEST_n shifts the bitboard left by n ranks without wrapping */
|
||||
#define MASK_SHIFT_WEST_1(b) _MASK_SHIFT_WEST_GUARDED(b, 1)
|
||||
#define MASK_SHIFT_WEST_2(b) _MASK_SHIFT_WEST_GUARDED(b, 2)
|
||||
#define MASK_SHIFT_WEST_3(b) _MASK_SHIFT_WEST_GUARDED(b, 3)
|
||||
#define MASK_SHIFT_WEST_4(b) _MASK_SHIFT_WEST_GUARDED(b, 4)
|
||||
#define MASK_SHIFT_WEST_5(b) _MASK_SHIFT_WEST_GUARDED(b, 5)
|
||||
#define MASK_SHIFT_WEST_6(b) _MASK_SHIFT_WEST_GUARDED(b, 6)
|
||||
#define MASK_SHIFT_WEST_7(b) _MASK_SHIFT_WEST_GUARDED(b, 7)
|
||||
#define MASK_SHIFT_WEST_8(b) _MASK_SHIFT_WEST_GUARDED(b, 8)
|
||||
|
||||
/* MASK_SHIFT_NORTH_n shifts the bitboard towards rank 8 by n ranks without wrapping */
|
||||
#define MASK_SHIFT_NORTH_1(b) MASK_SHIFT_NORTH(b, 1)
|
||||
#define MASK_SHIFT_NORTH_2(b) MASK_SHIFT_NORTH(b, 2)
|
||||
#define MASK_SHIFT_NORTH_3(b) MASK_SHIFT_NORTH(b, 3)
|
||||
#define MASK_SHIFT_NORTH_4(b) MASK_SHIFT_NORTH(b, 4)
|
||||
#define MASK_SHIFT_NORTH_5(b) MASK_SHIFT_NORTH(b, 5)
|
||||
#define MASK_SHIFT_NORTH_6(b) MASK_SHIFT_NORTH(b, 6)
|
||||
#define MASK_SHIFT_NORTH_7(b) MASK_SHIFT_NORTH(b, 7)
|
||||
#define MASK_SHIFT_NORTH_8(b) MASK_SHIFT_NORTH(b, 8)
|
||||
|
||||
/* MASK_SHIFT_SOUTH_n shifts the bitboard towards rank 1 by n ranks without wrapping */
|
||||
#define MASK_SHIFT_SOUTH_1(b) MASK_SHIFT_SOUTH(b, 1)
|
||||
#define MASK_SHIFT_SOUTH_2(b) MASK_SHIFT_SOUTH(b, 2)
|
||||
#define MASK_SHIFT_SOUTH_3(b) MASK_SHIFT_SOUTH(b, 3)
|
||||
#define MASK_SHIFT_SOUTH_4(b) MASK_SHIFT_SOUTH(b, 4)
|
||||
#define MASK_SHIFT_SOUTH_5(b) MASK_SHIFT_SOUTH(b, 5)
|
||||
#define MASK_SHIFT_SOUTH_6(b) MASK_SHIFT_SOUTH(b, 6)
|
||||
#define MASK_SHIFT_SOUTH_7(b) MASK_SHIFT_SOUTH(b, 7)
|
||||
#define MASK_SHIFT_SOUTH_8(b) MASK_SHIFT_SOUTH(b, 8)
|
||||
|
||||
enum {
|
||||
#define X(file, rank) SQMASK_##file##rank = MASK_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file),
|
||||
SQUARES_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
static Sq8 bitboard_lsb(Bb64 p)
|
||||
{
|
||||
assuming(p != 0);
|
||||
return (Sq8)__builtin_ffsll((int64_t)p) - 1;
|
||||
}
|
||||
|
||||
static Sq8 bitboard_pop_lsb(Bb64* p)
|
||||
{
|
||||
Sq8 const lsb = bitboard_lsb(*p);
|
||||
*p &= ~(1ULL<<(lsb));
|
||||
return lsb;
|
||||
}
|
||||
|
||||
static inline uint64_t bitboard_popcount(Bb64 b)
|
||||
{
|
||||
return (uint64_t)__builtin_popcountll(b);
|
||||
}
|
||||
|
||||
static inline bool bitboard_more_than_one(Bb64 b)
|
||||
{
|
||||
return b & (b - 1);
|
||||
}
|
||||
|
||||
437
engine-board.h
Normal file
437
engine-board.h
Normal file
@@ -0,0 +1,437 @@
|
||||
#pragma once
|
||||
|
||||
#include "engine-types.h"
|
||||
#include "engine-tt.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct board {
|
||||
struct pos {
|
||||
Bb64 pieces[SIDE_COUNT][PIECE_COUNT];
|
||||
Side8 moving_side;
|
||||
bool castling_illegal[SIDE_COUNT][CASTLE_COUNT];
|
||||
Bb64 ep_targets;
|
||||
|
||||
int halfmoves; /* FIXME: this duplicates the hist.length value */
|
||||
int fullmoves;
|
||||
uint64_t hash;
|
||||
} pos;
|
||||
|
||||
struct tt tt;
|
||||
|
||||
/* used for repeated board state detection only */
|
||||
struct history {
|
||||
struct pos items[50];
|
||||
size_t length;
|
||||
} hist;
|
||||
|
||||
/* Used only for square->piece lookups, not central to logic. Does not
|
||||
* store which side owns the square piece, and checking non-occupied
|
||||
* squares is undefined
|
||||
*
|
||||
* TODO: make mailbox smaller, only 3 bits are needed per piece
|
||||
* */
|
||||
Piece8 mailbox[SQ_COUNT];
|
||||
};
|
||||
|
||||
/* attack-sets depends on the definition of struct pos, whoops ¯\_(ツ)_/¯ */
|
||||
#include "engine-attack-sets.h"
|
||||
|
||||
#define BOARD_INIT (struct board) { \
|
||||
.pos = { \
|
||||
.fullmoves = 1, \
|
||||
.pieces = { \
|
||||
[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_ROOK] = SQMASK_A1 | SQMASK_H1, \
|
||||
[PIECE_KNIGHT] = SQMASK_B1 | SQMASK_G1, \
|
||||
[PIECE_BISHOP] = SQMASK_C1 | SQMASK_F1, \
|
||||
[PIECE_QUEEN] = SQMASK_D1, \
|
||||
[PIECE_KING] = SQMASK_E1, \
|
||||
}, \
|
||||
[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_ROOK] = SQMASK_A8 | SQMASK_H8, \
|
||||
[PIECE_KNIGHT] = SQMASK_B8 | SQMASK_G8, \
|
||||
[PIECE_BISHOP] = SQMASK_C8 | SQMASK_F8, \
|
||||
[PIECE_QUEEN] = SQMASK_D8, \
|
||||
[PIECE_KING] = SQMASK_E8, \
|
||||
} \
|
||||
}, \
|
||||
.hash = ~0ULL, \
|
||||
}, \
|
||||
.mailbox = { \
|
||||
[SQ_A1] = PIECE_ROOK, \
|
||||
[SQ_B1] = PIECE_KNIGHT, \
|
||||
[SQ_C1] = PIECE_BISHOP, \
|
||||
[SQ_D1] = PIECE_QUEEN, \
|
||||
[SQ_E1] = PIECE_KING, \
|
||||
[SQ_F1] = PIECE_BISHOP, \
|
||||
[SQ_G1] = PIECE_KNIGHT, \
|
||||
[SQ_H1] = PIECE_ROOK, \
|
||||
[SQ_A2] = PIECE_PAWN, \
|
||||
[SQ_B2] = PIECE_PAWN, \
|
||||
[SQ_C2] = PIECE_PAWN, \
|
||||
[SQ_D2] = PIECE_PAWN, \
|
||||
[SQ_E2] = PIECE_PAWN, \
|
||||
[SQ_F2] = PIECE_PAWN, \
|
||||
[SQ_G2] = PIECE_PAWN, \
|
||||
[SQ_H2] = PIECE_PAWN, \
|
||||
[SQ_A7] = PIECE_PAWN, \
|
||||
[SQ_B7] = PIECE_PAWN, \
|
||||
[SQ_C7] = PIECE_PAWN, \
|
||||
[SQ_D7] = PIECE_PAWN, \
|
||||
[SQ_E7] = PIECE_PAWN, \
|
||||
[SQ_F7] = PIECE_PAWN, \
|
||||
[SQ_G7] = PIECE_PAWN, \
|
||||
[SQ_H7] = PIECE_PAWN, \
|
||||
[SQ_A8] = PIECE_ROOK, \
|
||||
[SQ_B8] = PIECE_KNIGHT, \
|
||||
[SQ_C8] = PIECE_BISHOP, \
|
||||
[SQ_D8] = PIECE_QUEEN, \
|
||||
[SQ_E8] = PIECE_KING, \
|
||||
[SQ_F8] = PIECE_BISHOP, \
|
||||
[SQ_G8] = PIECE_KNIGHT, \
|
||||
[SQ_H8] = PIECE_ROOK, \
|
||||
}, \
|
||||
.hist = {0}, \
|
||||
}
|
||||
|
||||
void board_init(struct board* b)
|
||||
{
|
||||
if (b->pos.fullmoves == 0 && b->pos.hash == 0) {
|
||||
*b = BOARD_INIT;
|
||||
}
|
||||
if (b->tt.entries == NULL) {
|
||||
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_MADV_RANDOM);
|
||||
b->tt.mask = mask;
|
||||
if (b->tt.entries == NULL) {
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
||||
{
|
||||
/* TODO: this function is not tested for malicious/corrupted inputs */
|
||||
struct {
|
||||
char const* buf;
|
||||
size_t i;
|
||||
size_t len;
|
||||
} fen = {
|
||||
.buf = fen_str,
|
||||
.i = 0,
|
||||
.len = strlen(fen_str),
|
||||
};
|
||||
#define BUF_GETCHAR(x) (assuming(x.i < x.len), x.buf[x.i++])
|
||||
|
||||
my_memset(&b->pos, 0, sizeof b->pos);
|
||||
b->pos.hash = ~0ULL;
|
||||
|
||||
for (enum rank_index ri = RANK_INDEX_8; ri < RANK_INDEX_COUNT; --ri) {
|
||||
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||
char const ch = BUF_GETCHAR(fen);
|
||||
if (my_isdigit(ch)) {
|
||||
if (ch == '0') {
|
||||
__builtin_trap();
|
||||
}
|
||||
fi += ch - '0' - 1;
|
||||
} else {
|
||||
struct piece_side const p = piece_and_side_from_char[(uint8_t)ch];
|
||||
Bb64 const sq_mask = MASK_FROM_RF(ri, fi);
|
||||
b->pos.pieces[p.side][p.piece] |= sq_mask;
|
||||
b->pos.pieces[p.side][PIECE_EMPTY] &= ~sq_mask;
|
||||
b->mailbox[SQ_FROM_RF(ri, fi)] = p.piece;
|
||||
}
|
||||
}
|
||||
(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 */
|
||||
char const ch = BUF_GETCHAR(fen);
|
||||
if (ch == 'w') {
|
||||
b->pos.moving_side = SIDE_WHITE;
|
||||
} else if (ch == 'b') {
|
||||
b->pos.moving_side = SIDE_BLACK;
|
||||
} else {
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
|
||||
{ /* castling */
|
||||
char ch = BUF_GETCHAR(fen);
|
||||
if (ch != ' ') {
|
||||
__builtin_trap();
|
||||
}
|
||||
b->pos.castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE] = true;
|
||||
b->pos.castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE] = true;
|
||||
b->pos.castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE] = true;
|
||||
b->pos.castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE] = true;
|
||||
do {
|
||||
ch = BUF_GETCHAR(fen);
|
||||
switch (ch) {
|
||||
case 'K': b->pos.castling_illegal[SIDE_WHITE][CASTLE_KINGSIDE] = false; break;
|
||||
case 'Q': b->pos.castling_illegal[SIDE_WHITE][CASTLE_QUEENSIDE] = false; break;
|
||||
case 'k': b->pos.castling_illegal[SIDE_BLACK][CASTLE_KINGSIDE] = false; break;
|
||||
case 'q': b->pos.castling_illegal[SIDE_BLACK][CASTLE_QUEENSIDE] = false; break;
|
||||
case ' ': break;
|
||||
case '-': break;
|
||||
default: {
|
||||
__builtin_trap();
|
||||
} break;
|
||||
}
|
||||
} while (ch != ' ');
|
||||
}
|
||||
|
||||
{ /* en passent */
|
||||
char const ch = BUF_GETCHAR(fen);
|
||||
if (ch != '-') {
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
|
||||
{ /* halfmoves */
|
||||
b->pos.halfmoves = 0;
|
||||
char ch = BUF_GETCHAR(fen);
|
||||
while (ch != ' ') {
|
||||
b->pos.halfmoves *= 10;
|
||||
b->pos.halfmoves += ch-'0';
|
||||
ch = BUF_GETCHAR(fen);
|
||||
}
|
||||
}
|
||||
|
||||
{ /* fullmoves */
|
||||
b->pos.fullmoves = 0;
|
||||
char ch = BUF_GETCHAR(fen);
|
||||
while (ch != ' ') {
|
||||
b->pos.fullmoves *= 10;
|
||||
b->pos.fullmoves += ch-'0';
|
||||
ch = BUF_GETCHAR(fen);
|
||||
}
|
||||
}
|
||||
#undef BUF_GETCHAR
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum move_result {
|
||||
MR_NORMAL,
|
||||
MR_CHECK,
|
||||
MR_REPEATS, /* means this board state has been observed before */
|
||||
MR_STALEMATE,
|
||||
MR_CHECKMATE,
|
||||
};
|
||||
|
||||
/* does not check validity */
|
||||
static enum move_result move_piece(struct pos* restrict pos,
|
||||
Side8 us,
|
||||
struct history* restrict hist,
|
||||
Piece8 mailbox[restrict static SQ_COUNT],
|
||||
struct move move)
|
||||
{
|
||||
//Side8 const us = pos->moving_side;
|
||||
Side8 const them = other_side(us);
|
||||
|
||||
Piece8 const from_piece = mailbox[move.from];
|
||||
Piece8 const to_piece = mailbox[move.to];
|
||||
|
||||
Bb64 const from_mask = MASK_FROM_SQ(move.from);
|
||||
Bb64 const to_mask = MASK_FROM_SQ(move.to);
|
||||
|
||||
Sq8 const krook = (us == SIDE_WHITE) ? SQ_H1 : SQ_H8;
|
||||
Sq8 const qrook = (us == SIDE_WHITE) ? SQ_A1 : SQ_A8;
|
||||
Sq8 const krook_to = (us == SIDE_WHITE) ? SQ_F1 : SQ_F8;
|
||||
Sq8 const qrook_to = (us == SIDE_WHITE) ? SQ_D1 : SQ_D8;
|
||||
Sq8 const ksq = (us == SIDE_WHITE) ? SQ_E1 : SQ_E8;
|
||||
Sq8 const kcast_sq = (us == SIDE_WHITE) ? SQ_G1 : SQ_G8;
|
||||
Sq8 const qcast_sq = (us == SIDE_WHITE) ? SQ_C1 : SQ_C8;
|
||||
Sq8 const their_krook = (us != SIDE_WHITE) ? SQ_H1 : SQ_H8;
|
||||
Sq8 const their_qrook = (us != SIDE_WHITE) ? SQ_A1 : SQ_A8;
|
||||
|
||||
#define POS_MOVE(side, piece, from, to) \
|
||||
do { \
|
||||
Bb64 const x = MASK_FROM_SQ(from) | MASK_FROM_SQ(to); \
|
||||
pos->pieces[side][piece] ^= x; \
|
||||
pos->pieces[side][PIECE_EMPTY] ^= x; \
|
||||
pos->hash = tt_hash_update(pos->hash, from, side, piece); \
|
||||
pos->hash = tt_hash_update(pos->hash, to, side, piece); \
|
||||
mailbox[to] = piece; \
|
||||
if (piece == PIECE_PAWN) pos->halfmoves = 0; \
|
||||
} while (0)
|
||||
|
||||
#define POS_REMOVE(owner, piece, at) \
|
||||
do { \
|
||||
Bb64 const x = MASK_FROM_SQ(at); \
|
||||
pos->pieces[owner][piece] &= ~x; \
|
||||
pos->pieces[owner][PIECE_EMPTY] |= x; \
|
||||
pos->hash = tt_hash_update(pos->hash, at, owner, piece); \
|
||||
hist->length = 0; \
|
||||
pos->halfmoves = 0; \
|
||||
} while (0)
|
||||
|
||||
#define POS_ADD(owner, piece, at) \
|
||||
do { \
|
||||
Bb64 const x = MASK_FROM_SQ(at); \
|
||||
pos->pieces[owner][piece] |= x; \
|
||||
pos->pieces[owner][PIECE_EMPTY] &= ~x; \
|
||||
pos->hash = tt_hash_update(pos->hash, at, owner, piece); \
|
||||
mailbox[at] = piece; \
|
||||
pos->halfmoves = 0; \
|
||||
hist->length = 0; \
|
||||
} while (0)
|
||||
|
||||
Bb64 const ep_targets_now = pos->ep_targets;
|
||||
if (ep_targets_now) {
|
||||
pos->hash = tt_hash_update_ep_targets(pos->hash, bitboard_lsb(ep_targets_now));
|
||||
}
|
||||
pos->ep_targets = 0ULL;
|
||||
|
||||
/* castle kingside, legality is checked by the caller */
|
||||
/**/ if (from_piece == PIECE_KING && move.from == ksq && move.to == kcast_sq) {
|
||||
POS_MOVE(us, PIECE_KING, ksq, kcast_sq);
|
||||
POS_MOVE(us, PIECE_ROOK, krook, krook_to);
|
||||
}
|
||||
/* castle queenside, legality is checked by the caller */
|
||||
else if (from_piece == PIECE_KING && move.from == ksq && move.to == qcast_sq) {
|
||||
POS_MOVE(us, PIECE_KING, ksq, qcast_sq);
|
||||
POS_MOVE(us, PIECE_ROOK, qrook, qrook_to);
|
||||
}
|
||||
/* regular move / capture */
|
||||
else {
|
||||
POS_MOVE(us, from_piece, move.from, move.to);
|
||||
/* capture */
|
||||
if (to_mask & ~pos->pieces[them][PIECE_EMPTY]) {
|
||||
POS_REMOVE(them, to_piece, move.to);
|
||||
}
|
||||
|
||||
/* promote / ep */
|
||||
if (from_piece == PIECE_PAWN) {
|
||||
Bb64 const finishline = (us == SIDE_WHITE ? RANK_MASK_8 : RANK_MASK_1);
|
||||
|
||||
/* en passent */
|
||||
/**/ if (to_mask & ep_targets_now) {
|
||||
Sq8 const ti =
|
||||
(us == SIDE_WHITE)
|
||||
? SQ_SHIFT_SOUTH(move.to, 1)
|
||||
: SQ_SHIFT_NORTH(move.to, 1);
|
||||
POS_REMOVE(them, PIECE_PAWN, ti);
|
||||
}
|
||||
/* pawn double push -> new ep_target */
|
||||
else if (us == SIDE_WHITE && (from_mask & RANK_MASK_2) && (to_mask & RANK_MASK_4)) {
|
||||
pos->ep_targets |= MASK_SHIFT_NORTH_1(from_mask);
|
||||
pos->hash = tt_hash_update_ep_targets(pos->hash, SQ_SHIFT_NORTH(move.from, 1));
|
||||
}
|
||||
else if (us == SIDE_BLACK && (from_mask & RANK_MASK_7) && (to_mask & RANK_MASK_5)) {
|
||||
pos->ep_targets |= MASK_SHIFT_SOUTH_1(from_mask);
|
||||
pos->hash = tt_hash_update_ep_targets(pos->hash, SQ_SHIFT_SOUTH(move.from, 1));
|
||||
}
|
||||
/* promote */
|
||||
else if (to_mask & finishline) {
|
||||
/* already moved to `move.to` */
|
||||
POS_REMOVE(us, PIECE_PAWN, move.to);
|
||||
POS_ADD(us, PIECE_QUEEN, move.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* castling rights */
|
||||
if (!pos->castling_illegal[us][CASTLE_KINGSIDE]) {
|
||||
if (move.from == ksq || move.from == krook) {
|
||||
pos->castling_illegal[us][CASTLE_KINGSIDE] = true;
|
||||
pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_KINGSIDE);
|
||||
}
|
||||
}
|
||||
if (!pos->castling_illegal[us][CASTLE_QUEENSIDE]) {
|
||||
if (move.from == ksq || move.from == qrook) {
|
||||
pos->castling_illegal[us][CASTLE_QUEENSIDE] = true;
|
||||
pos->hash = tt_hash_update_castling_rights(pos->hash, us, CASTLE_QUEENSIDE);
|
||||
}
|
||||
}
|
||||
|
||||
if (move.to == their_krook) {
|
||||
if (!pos->castling_illegal[them][CASTLE_KINGSIDE]) {
|
||||
pos->castling_illegal[them][CASTLE_KINGSIDE] = true;
|
||||
pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_KINGSIDE);
|
||||
}
|
||||
}
|
||||
if (move.to == their_qrook) {
|
||||
if (!pos->castling_illegal[them][CASTLE_QUEENSIDE]) {
|
||||
pos->castling_illegal[them][CASTLE_QUEENSIDE] = true;
|
||||
pos->hash = tt_hash_update_castling_rights(pos->hash, them, CASTLE_QUEENSIDE);
|
||||
}
|
||||
}
|
||||
|
||||
pos->hash = tt_hash_switch_side(pos->hash);
|
||||
pos->moving_side = them;
|
||||
pos->fullmoves += (pos->moving_side == SIDE_BLACK);
|
||||
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);
|
||||
|
||||
for (size_t i = 0; i < hist->length; ++i) {
|
||||
_Static_assert(sizeof *pos == sizeof hist->items[i]);
|
||||
|
||||
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)
|
||||
&& hist->items[i].moving_side == pos->moving_side
|
||||
&& hist->items[i].ep_targets == pos->ep_targets)
|
||||
{
|
||||
return MR_STALEMATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos->halfmoves > 50) {
|
||||
return MR_STALEMATE;
|
||||
}
|
||||
else {
|
||||
return MR_NORMAL;
|
||||
}
|
||||
|
||||
#undef POS_MOVE
|
||||
#undef POS_ADD
|
||||
#undef POS_REMOVE
|
||||
}
|
||||
|
||||
static enum move_result board_move(struct board* b, struct move move)
|
||||
{
|
||||
return move_piece(&b->pos,
|
||||
b->pos.moving_side,
|
||||
&b->hist,
|
||||
b->mailbox,
|
||||
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);
|
||||
}
|
||||
473
engine-evaluations.h
Normal file
473
engine-evaluations.h
Normal file
@@ -0,0 +1,473 @@
|
||||
/* TODO: candidate for code-generation */
|
||||
|
||||
#pragma once
|
||||
|
||||
enum game_progress {
|
||||
GP_OPENING,
|
||||
GP_MIDDLE,
|
||||
GP_END,
|
||||
GP_COUNT,
|
||||
};
|
||||
|
||||
static char const* game_progress_str[GP_COUNT] = {
|
||||
[GP_OPENING] = "GP_OPENING",
|
||||
[GP_MIDDLE] = "GP_MIDDLE",
|
||||
[GP_END] = "GP_END",
|
||||
};
|
||||
|
||||
static enum game_progress endgameness(struct pos const* pos)
|
||||
{
|
||||
/* piece_value is already defined similarly elsewhere, but this one should
|
||||
* remain independent of minor tweaks in the global table */
|
||||
static int const piece_value[PIECE_COUNT] = {
|
||||
[PIECE_KING] = 0,
|
||||
[PIECE_PAWN] = 1,
|
||||
[PIECE_BISHOP] = 3,
|
||||
[PIECE_KNIGHT] = 3,
|
||||
[PIECE_ROOK] = 5,
|
||||
[PIECE_QUEEN] = 9,
|
||||
};
|
||||
|
||||
int npm = 0; /* non-pawn material */
|
||||
for (Side8 pl = SIDE_BEGIN; pl < SIDE_COUNT; ++pl) {
|
||||
npm += piece_value[PIECE_QUEEN] * bitboard_popcount(pos->pieces[pl][PIECE_QUEEN]);
|
||||
npm += piece_value[PIECE_BISHOP] * bitboard_popcount(pos->pieces[pl][PIECE_BISHOP]);
|
||||
npm += piece_value[PIECE_KNIGHT] * bitboard_popcount(pos->pieces[pl][PIECE_KNIGHT]);
|
||||
npm += piece_value[PIECE_ROOK] * bitboard_popcount(pos->pieces[pl][PIECE_ROOK]);
|
||||
}
|
||||
|
||||
/**/ if (npm > 24) {
|
||||
return GP_OPENING;
|
||||
}
|
||||
else if (npm > 16) {
|
||||
return GP_MIDDLE;
|
||||
}
|
||||
else {
|
||||
return GP_END;
|
||||
}
|
||||
}
|
||||
|
||||
#define BITBOARD_WHITE( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||
BITBOARD( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1)
|
||||
|
||||
#define BITBOARD_BLACK( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||
BITBOARD( \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8)
|
||||
|
||||
#define REL_RANK_1 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1)
|
||||
|
||||
#define REL_RANK_2 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_3 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_4 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_5 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_6 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_7 \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_RANK_8 \
|
||||
REL_BITBOARD( \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
|
||||
#define DIAGONAL_A1_H8 \
|
||||
BITBOARD( \
|
||||
0,0,0,0,0,0,0,1, \
|
||||
0,0,0,0,0,0,1,0, \
|
||||
0,0,0,0,0,1,0,0, \
|
||||
0,0,0,0,1,0,0,0, \
|
||||
0,0,0,1,0,0,0,0, \
|
||||
0,0,1,0,0,0,0,0, \
|
||||
0,1,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0)
|
||||
|
||||
#define DIAGONAL_A8_H1 \
|
||||
BITBOARD( \
|
||||
1,0,0,0,0,0,0,0, \
|
||||
0,1,0,0,0,0,0,0, \
|
||||
0,0,1,0,0,0,0,0, \
|
||||
0,0,0,1,0,0,0,0, \
|
||||
0,0,0,0,1,0,0,0, \
|
||||
0,0,0,0,0,1,0,0, \
|
||||
0,0,0,0,0,0,1,0, \
|
||||
0,0,0,0,0,0,0,1)
|
||||
|
||||
#define REL_BISHOP_KING_ATTACK \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,1,1, \
|
||||
0,0,0,0,0,1,1,0, \
|
||||
0,0,0,0,1,1,0,0, \
|
||||
0,0,0,1,1,0,0,0, \
|
||||
0,0,1,1,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0, \
|
||||
1,1,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_BISHOP_QUEEN_ATTACK \
|
||||
REL_BITBOARD( \
|
||||
1,1,0,0,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0, \
|
||||
0,0,1,1,0,0,0,0, \
|
||||
0,0,0,1,1,0,0,0, \
|
||||
0,0,0,0,1,1,0,0, \
|
||||
0,0,0,0,0,1,1,0, \
|
||||
0,0,0,0,0,0,1,1, \
|
||||
0,0,0,0,0,0,0,1)
|
||||
|
||||
#define REL_KING_CASTLE_KINGSIDE \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,1,0)
|
||||
|
||||
#define REL_KING_CASTLE_QUEENSIDE \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0)
|
||||
|
||||
#define CORNERS \
|
||||
BITBOARD( \
|
||||
1,0,0,0,0,0,0,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,1)
|
||||
|
||||
#define REL_EARLY_PAWN_STRUCTURE \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0, \
|
||||
1,1,1,1,1,0,0,0, \
|
||||
1,1,1,1,1,0,0,0, \
|
||||
1,1,1,1,1,0,0,0, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define REL_PAWN_KINGSIDE \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,1, \
|
||||
0,0,0,0,0,1,1,0, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
|
||||
#define REL_UNDEVELOPED_BISHOPS \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,1,0,0,1,0,0)
|
||||
|
||||
#define REL_UNDEVELOPED_KNIGHTS \
|
||||
REL_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,1,0,0,0,0,1,0)
|
||||
|
||||
#define BOARD_CENTER_6X6 \
|
||||
((FILE_MASK_B | FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F | FILE_MASK_G) \
|
||||
& (RANK_MASK_2 | RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6 | RANK_MASK_7))
|
||||
|
||||
#define BOARD_CENTER_4X4 \
|
||||
((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
||||
& (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6))
|
||||
|
||||
#define BOARD_CENTER_2X2 \
|
||||
((FILE_MASK_D | FILE_MASK_E) \
|
||||
& (RANK_MASK_4 | RANK_MASK_5))
|
||||
|
||||
#define POSITIONAL_MODIFIER_COUNT 4
|
||||
|
||||
/* ------------------------------ early game ------------------------------- */
|
||||
|
||||
#define EARLY_POSITIONAL_BONUS_0 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
|
||||
X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
|
||||
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
||||
X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
|
||||
X(PIECE_QUEEN, -15, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
||||
X(PIECE_ROOK, 10, FILE_MASK_D | FILE_MASK_E) \
|
||||
/**/
|
||||
|
||||
#define EARLY_POSITIONAL_BONUS_1 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
|
||||
X(PIECE_BISHOP, 5, REL_BISHOP_KING_ATTACK) \
|
||||
/**/
|
||||
|
||||
#define EARLY_POSITIONAL_BONUS_2 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, -18, ~REL_EARLY_PAWN_STRUCTURE) \
|
||||
X(PIECE_KNIGHT, -10, REL_UNDEVELOPED_KNIGHTS) \
|
||||
X(PIECE_BISHOP, -10, REL_UNDEVELOPED_BISHOPS) \
|
||||
/**/
|
||||
|
||||
#define EARLY_POSITIONAL_BONUS_3 \
|
||||
/* piece bonus area*/ \
|
||||
/**/
|
||||
|
||||
|
||||
/* ------------------------------ middle game ------------------------------ */
|
||||
/* (almost same as opening for now, but queen is not punished for moving) */
|
||||
|
||||
#define MIDDLE_POSITIONAL_BONUS_0 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
|
||||
X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
|
||||
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
||||
X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
|
||||
/**/
|
||||
|
||||
#define MIDDLE_POSITIONAL_BONUS_1 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
|
||||
X(PIECE_BISHOP, 7, REL_BISHOP_KING_ATTACK) \
|
||||
X(PIECE_QUEEN, 7, REL_BISHOP_KING_ATTACK) \
|
||||
/**/
|
||||
|
||||
#define MIDDLE_POSITIONAL_BONUS_2 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 2, REL_PAWN_KINGSIDE) \
|
||||
/**/
|
||||
|
||||
#define MIDDLE_POSITIONAL_BONUS_3 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6) \
|
||||
X(PIECE_KNIGHT, 5, BOARD_CENTER_6X6) \
|
||||
/**/
|
||||
|
||||
/* ------------------------------- end game -------------------------------- */
|
||||
|
||||
#define LATE_POSITIONAL_BONUS_0 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \
|
||||
X(PIECE_KING, 10, BOARD_CENTER_6X6) \
|
||||
/**/
|
||||
|
||||
#define LATE_POSITIONAL_BONUS_1 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6) \
|
||||
X(PIECE_KING, 10, BOARD_CENTER_4X4) \
|
||||
/**/
|
||||
|
||||
#define LATE_POSITIONAL_BONUS_2 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 70, REL_RANK_7) \
|
||||
X(PIECE_KING, 10, BOARD_CENTER_2X2) \
|
||||
/**/
|
||||
|
||||
#define LATE_POSITIONAL_BONUS_3 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_KING, -50, ~BOARD_CENTER_6X6) \
|
||||
/**/
|
||||
|
||||
struct posmod {
|
||||
Bb64 const area;
|
||||
Score16 const val;
|
||||
};
|
||||
|
||||
static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz)
|
||||
{
|
||||
static struct posmod const
|
||||
lookup[SIDE_COUNT][GP_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = {
|
||||
#define X(p, b, a) [p] = {.area = (a), .val = b},
|
||||
#define REL_BITBOARD BITBOARD_WHITE
|
||||
[SIDE_WHITE] = {
|
||||
[GP_OPENING] = {
|
||||
{EARLY_POSITIONAL_BONUS_0},
|
||||
{EARLY_POSITIONAL_BONUS_1},
|
||||
{EARLY_POSITIONAL_BONUS_2},
|
||||
{EARLY_POSITIONAL_BONUS_3},
|
||||
},
|
||||
[GP_MIDDLE] = {
|
||||
{MIDDLE_POSITIONAL_BONUS_0},
|
||||
{MIDDLE_POSITIONAL_BONUS_1},
|
||||
{MIDDLE_POSITIONAL_BONUS_2},
|
||||
{MIDDLE_POSITIONAL_BONUS_3},
|
||||
},
|
||||
[GP_END] = {
|
||||
{LATE_POSITIONAL_BONUS_0},
|
||||
{LATE_POSITIONAL_BONUS_1},
|
||||
{LATE_POSITIONAL_BONUS_2},
|
||||
{LATE_POSITIONAL_BONUS_3},
|
||||
},
|
||||
},
|
||||
#undef REL_BITBOARD
|
||||
|
||||
#define REL_BITBOARD BITBOARD_BLACK
|
||||
[SIDE_BLACK] = {
|
||||
[GP_OPENING] = {
|
||||
{EARLY_POSITIONAL_BONUS_0},
|
||||
{EARLY_POSITIONAL_BONUS_1},
|
||||
{EARLY_POSITIONAL_BONUS_2},
|
||||
{EARLY_POSITIONAL_BONUS_3},
|
||||
},
|
||||
[GP_MIDDLE] = {
|
||||
{MIDDLE_POSITIONAL_BONUS_0},
|
||||
{MIDDLE_POSITIONAL_BONUS_1},
|
||||
{MIDDLE_POSITIONAL_BONUS_2},
|
||||
{MIDDLE_POSITIONAL_BONUS_3},
|
||||
},
|
||||
[GP_END] = {
|
||||
{LATE_POSITIONAL_BONUS_0},
|
||||
{LATE_POSITIONAL_BONUS_1},
|
||||
{LATE_POSITIONAL_BONUS_2},
|
||||
{LATE_POSITIONAL_BONUS_3},
|
||||
},
|
||||
}
|
||||
#undef REL_BITBOARD
|
||||
};
|
||||
|
||||
return lookup[pl][st][layer][pz];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#undef POSITIONAL_BONUS_0
|
||||
#undef POSITIONAL_BONUS_1
|
||||
#undef POSITIONAL_BONUS_2
|
||||
#undef POSITIONAL_BONUS_3
|
||||
#undef CORNERS
|
||||
#undef BOARD_CENTER_4X4
|
||||
#undef BOARD_CENTER_2X2
|
||||
#undef BITBOARD_WHITE
|
||||
#undef BITBOARD_BLACK
|
||||
#undef REL_DIAGONAL_A1_H8
|
||||
#undef REL_DIAGONAL_A8_H1
|
||||
#undef REL_BISHOP_KING_ATTACK
|
||||
#undef REL_BISHOP_QUEEN_ATTACK
|
||||
#undef REL_KING_CASTLE_KINGSIDE
|
||||
#undef REL_KING_CASTLE_QUEENSIDE
|
||||
18
engine-macros.h
Normal file
18
engine-macros.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#define STR(x) #x
|
||||
|
||||
#if !defined(NDEBUG) && defined(USE_PRINTF)
|
||||
#define assuming(expr) \
|
||||
((expr) ? 0 : (fprintf(stderr, "assumption <" #expr "> failed on line %d\n", __LINE__), __builtin_trap(), 0))
|
||||
#elif !defined(NDEBUG)
|
||||
#define assuming(expr) \
|
||||
((expr) ? 0 : (__builtin_trap(), 0))
|
||||
#else
|
||||
#define assuming(expr) ((expr) ? 0 : (__builtin_unreachable(), 0))
|
||||
#endif
|
||||
|
||||
#define CACHE_LINE_SIZE 64
|
||||
|
||||
#define LIKELY(expr) __builtin_expect((expr), 1)
|
||||
#define UNLIKELY(expr) __builtin_expect((expr), 0)
|
||||
252
engine-move-generation.h
Normal file
252
engine-move-generation.h
Normal file
@@ -0,0 +1,252 @@
|
||||
#pragma once
|
||||
|
||||
#define MOVE_MAX 128
|
||||
|
||||
#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \
|
||||
{.from = SQ_E1, .to = SQ_G1}
|
||||
|
||||
#define MOVE_CASTLE_QUEENSIDE_WHITE (struct move) \
|
||||
{.from = SQ_E1, .to = SQ_C1}
|
||||
|
||||
#define MOVE_CASTLE_KINGSIDE_BLACK (struct move) \
|
||||
{.from = SQ_E8, .to = SQ_G8}
|
||||
|
||||
#define MOVE_CASTLE_QUEENSIDE_BLACK (struct move) \
|
||||
{.from = SQ_E8, .to = SQ_C8}
|
||||
|
||||
static inline
|
||||
struct move move_make(Index8 from, Index8 to)
|
||||
{
|
||||
return (struct move){.from = from, .to = to};
|
||||
}
|
||||
|
||||
enum move_gen_type {
|
||||
MG_ALL,
|
||||
MG_CAPTURES,
|
||||
MG_CHECKS,
|
||||
MG_QUIETS,
|
||||
};
|
||||
|
||||
static void all_pseudolegal_from_piece(struct pos const* restrict pos,
|
||||
enum move_gen_type type,
|
||||
Piece8 piece,
|
||||
Side8 us,
|
||||
Bb64 piece_mask,
|
||||
Bb64 allowed,
|
||||
size_t* restrict out_count,
|
||||
struct move out[restrict static MOVE_MAX])
|
||||
{
|
||||
assuming(piece != PIECE_PAWN);
|
||||
|
||||
Side8 them = other_side(us);
|
||||
|
||||
Bb64 const our_occ = ~pos->pieces[us][PIECE_EMPTY];
|
||||
Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);
|
||||
|
||||
if (type == MG_CHECKS) {
|
||||
allowed &= non_pawn_piece_attacks(piece, pos->pieces[them][PIECE_KING], all_occ);
|
||||
}
|
||||
|
||||
/* pray this gets constprop'ed */
|
||||
while (piece_mask) {
|
||||
Sq8 from = bitboard_pop_lsb(&piece_mask);
|
||||
Bb64 move_mask = non_pawn_piece_attacks_from_index(piece, from, all_occ)
|
||||
& ~our_occ
|
||||
& allowed
|
||||
;
|
||||
while (move_mask) {
|
||||
Sq8 const to = bitboard_pop_lsb(&move_mask);
|
||||
assuming(*out_count < MOVE_MAX); \
|
||||
out[(*out_count)++] = move_make(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(side, opp_side, side_enum, inverse_direction, pawn_rank) \
|
||||
static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
|
||||
enum move_gen_type type,\
|
||||
Bb64 piece_mask,\
|
||||
Bb64 allowed,\
|
||||
size_t* restrict out_count,\
|
||||
struct move out[restrict static MOVE_MAX])\
|
||||
{\
|
||||
Bb64 const all_occ = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]);\
|
||||
\
|
||||
if (type == MG_CHECKS) {\
|
||||
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
|
||||
}\
|
||||
Bb64 sp = pawn_single_push_##side(piece_mask, ~all_occ)\
|
||||
& allowed\
|
||||
;\
|
||||
while (sp) {\
|
||||
Sq8 const to = bitboard_pop_lsb(&sp);\
|
||||
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\
|
||||
out[(*out_count)++] = move_make(from, to);\
|
||||
}\
|
||||
\
|
||||
Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\
|
||||
& allowed\
|
||||
;\
|
||||
while (dp) {\
|
||||
Sq8 const to = bitboard_pop_lsb(&dp);\
|
||||
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\
|
||||
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(black, white, SIDE_BLACK, NORTH, RANK_MASK_7)
|
||||
|
||||
#define DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(side, opp_side, side_enum, inverse_direction) \
|
||||
static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
|
||||
enum move_gen_type type,\
|
||||
Bb64 piece_mask,\
|
||||
Bb64 allowed,\
|
||||
size_t* restrict out_count,\
|
||||
struct move out[restrict static MOVE_MAX])\
|
||||
{\
|
||||
Bb64 const their_occ = ~pos->pieces[other_side(side_enum)][PIECE_EMPTY];\
|
||||
\
|
||||
if (type == MG_CHECKS) {\
|
||||
allowed &= pawn_attacks_##opp_side(pos->pieces[other_side(side_enum)][PIECE_KING]);\
|
||||
}\
|
||||
\
|
||||
Bb64 ratk = pawn_attacks_right_##side(piece_mask)\
|
||||
& (their_occ | pos->ep_targets)\
|
||||
& allowed\
|
||||
;\
|
||||
while (ratk) {\
|
||||
Sq8 const to = bitboard_pop_lsb(&ratk);\
|
||||
Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
||||
assuming(*out_count < MOVE_MAX); \
|
||||
out[(*out_count)++] = move_make(from, to);\
|
||||
}\
|
||||
\
|
||||
Bb64 latk = pawn_attacks_left_##side(piece_mask)\
|
||||
& (their_occ | pos->ep_targets)\
|
||||
& allowed\
|
||||
;\
|
||||
while (latk) {\
|
||||
Sq8 const to = bitboard_pop_lsb(&latk);\
|
||||
Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
||||
assuming(*out_count < MOVE_MAX); \
|
||||
out[(*out_count)++] = move_make(from, to);\
|
||||
}\
|
||||
}
|
||||
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH)
|
||||
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(black, white, SIDE_BLACK, NORTH)
|
||||
|
||||
static void all_pseudolegal_moves(struct pos const* restrict pos,
|
||||
enum move_gen_type type,
|
||||
Side8 us,
|
||||
size_t* restrict out_count,
|
||||
struct move out[restrict static MOVE_MAX])
|
||||
{
|
||||
Side8 const them = other_side(us);
|
||||
if (type == MG_CHECKS && pos->pieces[them][PIECE_KING] == 0ULL) {
|
||||
return;
|
||||
}
|
||||
Bb64 const kmask = pos->pieces[us][PIECE_KING];
|
||||
assuming(kmask);
|
||||
Sq8 const ksq = bitboard_lsb(kmask);
|
||||
|
||||
|
||||
Bb64 const chk = checkers(pos, us);
|
||||
|
||||
Bb64 const their_threats = all_threats_from_side(pos, them);
|
||||
|
||||
Bb64 const their_occ = ~pos->pieces[them][PIECE_EMPTY];
|
||||
|
||||
Bb64 allowed;
|
||||
if (type == MG_CAPTURES) {
|
||||
allowed = their_occ;
|
||||
} else if (type == MG_QUIETS) {
|
||||
allowed = ~their_occ;
|
||||
} else if (type == MG_ALL) {
|
||||
allowed = ~0ULL;
|
||||
} else {
|
||||
allowed = ~0ULL;
|
||||
}
|
||||
|
||||
Bb64 king_allowed = allowed;
|
||||
|
||||
if (chk) {
|
||||
if (bitboard_more_than_one(chk)) {
|
||||
if (type == MG_ALL) {
|
||||
/* no other piece can fix check if the king has two attackers or more */
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats, out_count, out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Sq8 const asq = bitboard_lsb(chk);
|
||||
Bb64 const blocking_mask = between_mask(ksq, asq);
|
||||
allowed &= blocking_mask | chk;
|
||||
}
|
||||
|
||||
if (type == MG_CAPTURES || type == MG_CHECKS || type == MG_ALL) {
|
||||
Bb64 const x = (type == MG_CHECKS ? pos->ep_targets : 0ULL);
|
||||
if (us == SIDE_WHITE) {
|
||||
all_pseudolegal_pawn_attacks_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out);
|
||||
} else {
|
||||
all_pseudolegal_pawn_attacks_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed | x, out_count, out);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == MG_QUIETS || type == MG_CHECKS || type == MG_ALL) {
|
||||
if (us == SIDE_WHITE) {
|
||||
all_pseudolegal_pawn_moves_white(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out);
|
||||
} else {
|
||||
all_pseudolegal_pawn_moves_black(pos, type, pos->pieces[us][PIECE_PAWN], allowed, out_count, out);
|
||||
}
|
||||
}
|
||||
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_BISHOP, us, pos->pieces[us][PIECE_BISHOP], allowed, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_KNIGHT, us, pos->pieces[us][PIECE_KNIGHT], allowed, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_ROOK, us, pos->pieces[us][PIECE_ROOK], allowed, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_QUEEN, us, pos->pieces[us][PIECE_QUEEN], allowed, out_count, out);
|
||||
|
||||
all_pseudolegal_from_piece(pos, type, PIECE_KING, us, pos->pieces[us][PIECE_KING], ~their_threats & king_allowed, out_count, out);
|
||||
|
||||
if (type == MG_CHECKS) {
|
||||
Bb64 discovering = pinning_lines_to_target(pos, us, bitboard_lsb(pos->pieces[them][PIECE_KING]));
|
||||
|
||||
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_BISHOP, us, discovering & pos->pieces[us][PIECE_BISHOP], ~discovering, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KNIGHT, us, discovering & pos->pieces[us][PIECE_KNIGHT], ~discovering, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_ROOK, us, discovering & pos->pieces[us][PIECE_ROOK], ~discovering, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_QUEEN, us, discovering & pos->pieces[us][PIECE_QUEEN], ~discovering, out_count, out);
|
||||
all_pseudolegal_from_piece(pos, MG_ALL, PIECE_KING, us, discovering & pos->pieces[us][PIECE_KING], ~discovering & ~their_threats, out_count, out);
|
||||
}
|
||||
|
||||
/* castling */
|
||||
if (!chk && type != MG_CAPTURES) {
|
||||
bool can_castle_kingside, can_castle_queenside;
|
||||
Bb64 const blocked = ~(pos->pieces[SIDE_WHITE][PIECE_EMPTY] & pos->pieces[SIDE_BLACK][PIECE_EMPTY]) | their_threats;
|
||||
if (us == SIDE_WHITE) {
|
||||
can_castle_kingside = !(blocked & (SQMASK_F1 | SQMASK_G1))
|
||||
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_H1)
|
||||
&& !pos->castling_illegal[us][CASTLE_KINGSIDE];
|
||||
can_castle_queenside = !(blocked & (SQMASK_C1 | SQMASK_D1))
|
||||
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_A1)
|
||||
&& !pos->castling_illegal[us][CASTLE_QUEENSIDE];
|
||||
} else {
|
||||
can_castle_kingside = !(blocked & (SQMASK_F8 | SQMASK_G8))
|
||||
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_H8)
|
||||
&& !pos->castling_illegal[us][CASTLE_KINGSIDE];
|
||||
can_castle_queenside = !(blocked & (SQMASK_C8 | SQMASK_D8))
|
||||
&& (pos->pieces[us][PIECE_ROOK] & SQMASK_A8)
|
||||
&& !pos->castling_illegal[us][CASTLE_QUEENSIDE];
|
||||
}
|
||||
|
||||
if (can_castle_kingside) {
|
||||
out[(*out_count)++] = us == SIDE_WHITE
|
||||
? MOVE_CASTLE_KINGSIDE_WHITE
|
||||
: MOVE_CASTLE_KINGSIDE_BLACK;
|
||||
}
|
||||
|
||||
if (can_castle_queenside) {
|
||||
out[(*out_count)++] = us == SIDE_WHITE
|
||||
? MOVE_CASTLE_QUEENSIDE_WHITE
|
||||
: MOVE_CASTLE_QUEENSIDE_BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
engine-tt.h
Normal file
142
engine-tt.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include "engine-types.h"
|
||||
|
||||
struct search_option {
|
||||
uint64_t hash;
|
||||
Score16 score;
|
||||
struct move move;
|
||||
int depth : 4;
|
||||
int init : 1;
|
||||
enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3;
|
||||
};
|
||||
|
||||
#define TT_ADDRESS_BITS 24
|
||||
#define TT_ENTRIES (1ULL<<TT_ADDRESS_BITS)
|
||||
#define TT_MASK (TT_ENTRIES-1)
|
||||
struct tt {
|
||||
struct search_option* entries/*[TT_ENTRIES]*/; /* must be initialized somewhere */
|
||||
size_t mask;
|
||||
|
||||
/* stats */
|
||||
uint64_t collisions;
|
||||
uint64_t hits;
|
||||
uint64_t probes;
|
||||
uint64_t overwritten;
|
||||
uint64_t insertions;
|
||||
};
|
||||
|
||||
struct zobrist {
|
||||
uint64_t piece_keys[SQ_COUNT][SIDE_COUNT][PIECE_COUNT];
|
||||
uint64_t ep_targets[SQ_COUNT];
|
||||
uint64_t castling_keys[SIDE_COUNT][CASTLE_COUNT];
|
||||
bool init;
|
||||
};
|
||||
|
||||
static struct zobrist zobrist;
|
||||
|
||||
static void init_zobrist()
|
||||
{
|
||||
for (Sq8 sq = SQ_BEGIN; sq < SQ_COUNT; ++sq) {
|
||||
for (Side8 pl = SIDE_BEGIN; pl < SIDE_COUNT; ++pl) {
|
||||
for (Piece8 piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||
zobrist.piece_keys[sq][pl][piece] = my_rand64();
|
||||
}
|
||||
}
|
||||
zobrist.ep_targets[sq] = my_rand64();
|
||||
}
|
||||
for (Side8 pl = SIDE_BEGIN; pl < SIDE_COUNT; ++pl) {
|
||||
for (enum castle_direction d = CASTLE_BEGIN; d < CASTLE_COUNT; ++d) {
|
||||
zobrist.castling_keys[pl][d] = my_rand64();
|
||||
}
|
||||
}
|
||||
zobrist.init = true;
|
||||
}
|
||||
|
||||
static inline uint64_t tt_hash_update(uint64_t hash,
|
||||
Sq8 sq,
|
||||
Side8 us,
|
||||
Piece8 piece)
|
||||
{
|
||||
if (!zobrist.init) {
|
||||
init_zobrist();
|
||||
}
|
||||
return hash ^ zobrist.piece_keys[sq][us][piece];
|
||||
}
|
||||
|
||||
static inline uint64_t tt_hash_update_castling_rights(uint64_t hash,
|
||||
Side8 us,
|
||||
enum castle_direction dir)
|
||||
{
|
||||
if (!zobrist.init) {
|
||||
init_zobrist();
|
||||
}
|
||||
return hash ^ zobrist.castling_keys[us][dir];
|
||||
}
|
||||
|
||||
static inline uint64_t tt_hash_update_ep_targets(uint64_t hash, Sq8 sq)
|
||||
{
|
||||
if (!zobrist.init) {
|
||||
init_zobrist();
|
||||
}
|
||||
assuming(sq < SQ_COUNT);
|
||||
return hash ^ zobrist.ep_targets[sq];
|
||||
}
|
||||
|
||||
static inline uint64_t tt_hash_switch_side(uint64_t hash)
|
||||
{
|
||||
if (!zobrist.init) {
|
||||
init_zobrist();
|
||||
}
|
||||
return ~hash;
|
||||
}
|
||||
|
||||
static inline struct search_option tt_get(struct tt* tt, uint64_t hash)
|
||||
{
|
||||
uint64_t const addr = hash & tt->mask;
|
||||
struct search_option tte = tt->entries[addr];
|
||||
|
||||
#ifndef NSTATS
|
||||
tt->probes += 1;
|
||||
if (tte.init && tte.hash == hash) {
|
||||
tt->hits += 1;
|
||||
} else if (tte.init && tte.hash != hash) {
|
||||
tt->collisions += 1;
|
||||
}
|
||||
#endif
|
||||
return tte;
|
||||
}
|
||||
|
||||
static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so)
|
||||
{
|
||||
uint64_t const addr = hash & tt->mask;
|
||||
so.init = true;
|
||||
tt->entries[addr] = so;
|
||||
#ifndef NSTATS
|
||||
tt->insertions += 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* tt_insert_maybe inserts only if heuristics say it's a good idea. There are
|
||||
* two considerations:
|
||||
* - higher depth saves more work per probe hit
|
||||
* - entries closer to the leaves are more likely to be searched multiple time
|
||||
*/
|
||||
static inline void tt_insert_maybe(struct tt* tt, uint64_t hash, struct search_option so)
|
||||
{
|
||||
uint64_t const addr = hash & tt->mask;
|
||||
|
||||
#if 0
|
||||
struct search_option* tte = &tt->entries[addr];
|
||||
if (so.depth < tte->depth) {
|
||||
*tte = so;
|
||||
}
|
||||
#endif
|
||||
|
||||
so.init = true;
|
||||
tt->entries[addr] = so;
|
||||
#ifndef NSTATS
|
||||
tt->insertions += 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
348
engine-types.h
Normal file
348
engine-types.h
Normal file
@@ -0,0 +1,348 @@
|
||||
#pragma once
|
||||
|
||||
/* --- Score16 (centipawns) ---- */
|
||||
|
||||
typedef int16_t Score16;
|
||||
|
||||
enum {
|
||||
SCORE_INF = 9999,
|
||||
SCORE_CHECKMATE = 999,
|
||||
};
|
||||
|
||||
/* ----------- Index8 ----------- */
|
||||
typedef uint8_t Index8;
|
||||
|
||||
#define MASK_SHIFT_NORTH(b, n) ((b) << ((Index8)(n)*8U))
|
||||
#define MASK_SHIFT_SOUTH(b, n) ((b) >> ((Index8)(n)*8U))
|
||||
#define MASK_SHIFT_WEST(b, n) ((b) >> ((Index8)(n)*1U))
|
||||
#define MASK_SHIFT_EAST(b, n) ((b) << ((Index8)(n)*1U))
|
||||
|
||||
#define SQ_SHIFT_NORTH(i, n) ((i) + ((Index8)(n)*8U))
|
||||
#define SQ_SHIFT_SOUTH(i, n) ((i) - ((Index8)(n)*8U))
|
||||
#define SQ_SHIFT_WEST(i, n) ((i) - ((Index8)(n)*1U))
|
||||
#define SQ_SHIFT_EAST(i, n) ((i) + ((Index8)(n)*1U))
|
||||
|
||||
#define SQ_FROM_RF(rank, file) ((Index8)(8U*(Index8)(rank) + (Index8)(file)))
|
||||
|
||||
/* ----------- file_index ----------- */
|
||||
enum file_index : Index8 {
|
||||
FILE_INDEX_BEGIN,
|
||||
FILE_INDEX_A = FILE_INDEX_BEGIN,
|
||||
FILE_INDEX_B,
|
||||
FILE_INDEX_C,
|
||||
FILE_INDEX_D,
|
||||
FILE_INDEX_E,
|
||||
FILE_INDEX_F,
|
||||
FILE_INDEX_G,
|
||||
FILE_INDEX_H,
|
||||
FILE_INDEX_COUNT,
|
||||
};
|
||||
|
||||
char const file_index_char[FILE_INDEX_COUNT] = {
|
||||
[FILE_INDEX_A] = 'A',
|
||||
[FILE_INDEX_B] = 'B',
|
||||
[FILE_INDEX_C] = 'C',
|
||||
[FILE_INDEX_D] = 'D',
|
||||
[FILE_INDEX_E] = 'E',
|
||||
[FILE_INDEX_F] = 'F',
|
||||
[FILE_INDEX_G] = 'G',
|
||||
[FILE_INDEX_H] = 'H',
|
||||
};
|
||||
|
||||
/* ----------- rank_index ----------- */
|
||||
enum rank_index : Index8 {
|
||||
RANK_INDEX_BEGIN,
|
||||
RANK_INDEX_1 = RANK_INDEX_BEGIN,
|
||||
RANK_INDEX_2,
|
||||
RANK_INDEX_3,
|
||||
RANK_INDEX_4,
|
||||
RANK_INDEX_5,
|
||||
RANK_INDEX_6,
|
||||
RANK_INDEX_7,
|
||||
RANK_INDEX_8,
|
||||
RANK_INDEX_COUNT,
|
||||
};
|
||||
|
||||
char const rank_index_char[RANK_INDEX_COUNT] = {
|
||||
[RANK_INDEX_1] = '1',
|
||||
[RANK_INDEX_2] = '2',
|
||||
[RANK_INDEX_3] = '3',
|
||||
[RANK_INDEX_4] = '4',
|
||||
[RANK_INDEX_5] = '5',
|
||||
[RANK_INDEX_6] = '6',
|
||||
[RANK_INDEX_7] = '7',
|
||||
[RANK_INDEX_8] = '8',
|
||||
};
|
||||
|
||||
/* ----------- Sq8 ----------- */
|
||||
|
||||
#define SQMASK_PREFIX SQMASK_
|
||||
#define SQUARES_LIST_BEGIN \
|
||||
X(A, 1)
|
||||
#define SQUARES_LIST \
|
||||
X(A, 1) \
|
||||
X(A, 2) \
|
||||
X(A, 3) \
|
||||
X(A, 4) \
|
||||
X(A, 5) \
|
||||
X(A, 6) \
|
||||
X(A, 7) \
|
||||
X(A, 8) \
|
||||
X(B, 1) \
|
||||
X(B, 2) \
|
||||
X(B, 3) \
|
||||
X(B, 4) \
|
||||
X(B, 5) \
|
||||
X(B, 6) \
|
||||
X(B, 7) \
|
||||
X(B, 8) \
|
||||
X(C, 1) \
|
||||
X(C, 2) \
|
||||
X(C, 3) \
|
||||
X(C, 4) \
|
||||
X(C, 5) \
|
||||
X(C, 6) \
|
||||
X(C, 7) \
|
||||
X(C, 8) \
|
||||
X(D, 1) \
|
||||
X(D, 2) \
|
||||
X(D, 3) \
|
||||
X(D, 4) \
|
||||
X(D, 5) \
|
||||
X(D, 6) \
|
||||
X(D, 7) \
|
||||
X(D, 8) \
|
||||
X(E, 1) \
|
||||
X(E, 2) \
|
||||
X(E, 3) \
|
||||
X(E, 4) \
|
||||
X(E, 5) \
|
||||
X(E, 6) \
|
||||
X(E, 7) \
|
||||
X(E, 8) \
|
||||
X(F, 1) \
|
||||
X(F, 2) \
|
||||
X(F, 3) \
|
||||
X(F, 4) \
|
||||
X(F, 5) \
|
||||
X(F, 6) \
|
||||
X(F, 7) \
|
||||
X(F, 8) \
|
||||
X(G, 1) \
|
||||
X(G, 2) \
|
||||
X(G, 3) \
|
||||
X(G, 4) \
|
||||
X(G, 5) \
|
||||
X(G, 6) \
|
||||
X(G, 7) \
|
||||
X(G, 8) \
|
||||
X(H, 1) \
|
||||
X(H, 2) \
|
||||
X(H, 3) \
|
||||
X(H, 4) \
|
||||
X(H, 5) \
|
||||
X(H, 6) \
|
||||
X(H, 7) \
|
||||
X(H, 8)
|
||||
|
||||
typedef enum sq8 : uint8_t {
|
||||
#define X(file, rank) SQ_##file##rank = SQ_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file),
|
||||
SQUARES_LIST
|
||||
SQ_COUNT,
|
||||
SQ_POISONED,
|
||||
#undef X
|
||||
/* define iterator begin enum */
|
||||
#define X(file, rank) SQ_BEGIN = SQ_##file##rank,
|
||||
SQUARES_LIST_BEGIN
|
||||
#undef X
|
||||
} Sq8;
|
||||
|
||||
static char* const sq8_display[SQ_COUNT] = {
|
||||
#define X(file, rank) \
|
||||
[SQ_##file##rank] = STR(file##rank),
|
||||
SQUARES_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
static char* const sq8_str[SQ_COUNT] = {
|
||||
#define X(file, rank) \
|
||||
[SQ_##file##rank] = STR(SQ_##file##rank),
|
||||
SQUARES_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
static inline enum file_index sq_to_file(Sq8 p)
|
||||
{
|
||||
return p % 8ULL;
|
||||
}
|
||||
|
||||
static inline enum rank_index sq_to_rank(Sq8 p)
|
||||
{
|
||||
return p / 8ULL;
|
||||
}
|
||||
|
||||
/* ----------- Side8 ----------- */
|
||||
typedef enum side : uint8_t {
|
||||
SIDE_WHITE,
|
||||
SIDE_BLACK,
|
||||
SIDE_COUNT,
|
||||
SIDE_BEGIN = SIDE_WHITE,
|
||||
} Side8;
|
||||
|
||||
static inline Side8 other_side(Side8 p)
|
||||
{
|
||||
return (p == SIDE_WHITE ? SIDE_BLACK : SIDE_WHITE);
|
||||
}
|
||||
|
||||
static char const* side_str[SIDE_COUNT] = {
|
||||
[SIDE_WHITE] = "SIDE_WHITE",
|
||||
[SIDE_BLACK] = "SIDE_BLACK",
|
||||
};
|
||||
|
||||
/* ----------- Piece8 ----------- */
|
||||
|
||||
/* https://en.wikipedia.org/wiki/X_macro */
|
||||
/* enum value white char white unicode black char black unicode */
|
||||
#define PIECES \
|
||||
X(PIECE_EMPTY, 0, ' ', ' ', ' ', ' ') \
|
||||
X(PIECE_PAWN, 100, 'P', 0x2659, 'p', 0x265F) \
|
||||
X(PIECE_KNIGHT, 301, 'N', 0x2658, 'n', 0x265E) \
|
||||
X(PIECE_BISHOP, 302, 'B', 0x2657, 'b', 0x265D) \
|
||||
X(PIECE_ROOK, 500, 'R', 0x2656, 'r', 0x265C) \
|
||||
X(PIECE_QUEEN, 900, 'Q', 0x2655, 'q', 0x265B) \
|
||||
X(PIECE_KING, 1600, 'K', 0x2654, 'k', 0x265A) \
|
||||
/**/
|
||||
|
||||
typedef enum piece : uint8_t {
|
||||
#define X(e, v, wc, wu, bc, bu) e,
|
||||
PIECES
|
||||
PIECE_COUNT,
|
||||
PIECE_BEGIN = PIECE_PAWN,
|
||||
PIECE_POISONED, /* used as undefined value in debug builds */
|
||||
#undef X
|
||||
} Piece8;
|
||||
|
||||
static Score16 piece_value[PIECE_COUNT] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = v,
|
||||
PIECES
|
||||
#undef X
|
||||
};
|
||||
|
||||
static char const* piece_str[PIECE_COUNT] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = STR(e),
|
||||
PIECES
|
||||
#undef X
|
||||
};
|
||||
|
||||
struct piece_side {Piece8 piece; Side8 side;} const piece_and_side_from_char[256] = {
|
||||
#define X(e, v, wc, wu, bc, bu) \
|
||||
[(uint8_t)bc] = {.piece = e, .side = SIDE_BLACK}, \
|
||||
[(uint8_t)wc] = {.piece = e, .side = SIDE_WHITE},
|
||||
PIECES
|
||||
#undef X
|
||||
};
|
||||
|
||||
static char const piece_char[SIDE_COUNT][PIECE_COUNT] = {
|
||||
[SIDE_WHITE] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = wc,
|
||||
PIECES
|
||||
#undef X
|
||||
},
|
||||
[SIDE_BLACK] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = bc,
|
||||
PIECES
|
||||
#undef X
|
||||
}
|
||||
};
|
||||
|
||||
static int const piece_unicode[SIDE_COUNT][PIECE_COUNT] = {
|
||||
[SIDE_WHITE] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = wu,
|
||||
PIECES
|
||||
#undef X
|
||||
},
|
||||
[SIDE_BLACK] = {
|
||||
#define X(e, v, wc, wu, bc, bu) [e] = bu,
|
||||
PIECES
|
||||
#undef X
|
||||
}
|
||||
};
|
||||
|
||||
/* ----------- moves ----------- */
|
||||
|
||||
enum {
|
||||
MATTR_PROMOTE = 1<<0,
|
||||
MATTR_POISONED = 1<<1,
|
||||
};
|
||||
|
||||
struct move {
|
||||
Sq8 from;
|
||||
Sq8 to;
|
||||
uint8_t attr;
|
||||
#define APPEAL_MAX UINT8_MAX
|
||||
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,
|
||||
"this static assuming is here to check when sizeof(move) changes");
|
||||
|
||||
#define MOVE_NULL (struct move){0}
|
||||
|
||||
#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 ----------- */
|
||||
enum castle_direction {
|
||||
CASTLE_BEGIN,
|
||||
CASTLE_KINGSIDE = CASTLE_BEGIN,
|
||||
CASTLE_QUEENSIDE,
|
||||
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);
|
||||
}
|
||||
202
evaluations.h
202
evaluations.h
@@ -1,202 +0,0 @@
|
||||
/* TODO: candidate for code-generation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#define BITBOARD_WHITE( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||
(bitboard)\
|
||||
0b##\
|
||||
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||
h7##g7##f7##e7##d7##c7##b7##a7##\
|
||||
h6##g6##f6##e6##d6##c6##b6##a6##\
|
||||
h5##g5##f5##e5##d5##c5##b5##a5##\
|
||||
h4##g4##f4##e4##d4##c4##b4##a4##\
|
||||
h3##g3##f3##e3##d3##c3##b3##a3##\
|
||||
h2##g2##f2##e2##d2##c2##b2##a2##\
|
||||
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||
ULL
|
||||
|
||||
#define BITBOARD_BLACK( \
|
||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||
(bitboard)\
|
||||
0b##\
|
||||
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||
h2##g2##f2##e2##d2##c2##b2##a2##\
|
||||
h3##g3##f3##e3##d3##c3##b3##a3##\
|
||||
h4##g4##f4##e4##d4##c4##b4##a4##\
|
||||
h5##g5##f5##e5##d5##c5##b5##a5##\
|
||||
h6##g6##f6##e6##d6##c6##b6##a6##\
|
||||
h7##g7##f7##e7##d7##c7##b7##a7##\
|
||||
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||
ULL
|
||||
|
||||
#define RELATIVE_DIAGONAL_A1_H8 \
|
||||
RELATIVE_BITBOARD( \
|
||||
0,0,0,0,0,0,0,1, \
|
||||
0,0,0,0,0,0,1,0, \
|
||||
0,0,0,0,0,1,0,0, \
|
||||
0,0,0,0,1,0,0,0, \
|
||||
0,0,0,1,0,0,0,0, \
|
||||
0,0,1,0,0,0,0,0, \
|
||||
0,1,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0)
|
||||
|
||||
#define RELATIVE_DIAGONAL_A8_H1 \
|
||||
RELATIVE_BITBOARD( \
|
||||
1,0,0,0,0,0,0,0, \
|
||||
0,1,0,0,0,0,0,0, \
|
||||
0,0,1,0,0,0,0,0, \
|
||||
0,0,0,1,0,0,0,0, \
|
||||
0,0,0,0,1,0,0,0, \
|
||||
0,0,0,0,0,1,0,0, \
|
||||
0,0,0,0,0,0,1,0, \
|
||||
0,0,0,0,0,0,0,1)
|
||||
|
||||
#define RELATIVE_BISHOP_KING_ATTACK \
|
||||
RELATIVE_BITBOARD( \
|
||||
0,0,0,0,0,0,1,1, \
|
||||
0,0,0,0,0,1,1,0, \
|
||||
0,0,0,0,1,1,0,0, \
|
||||
0,0,0,1,1,0,0,0, \
|
||||
0,0,1,1,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0, \
|
||||
1,1,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0)
|
||||
|
||||
#define RELATIVE_BISHOP_QUEEN_ATTACK \
|
||||
RELATIVE_BITBOARD( \
|
||||
1,1,0,0,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0, \
|
||||
0,0,1,1,0,0,0,0, \
|
||||
0,0,0,1,1,0,0,0, \
|
||||
0,0,0,0,1,1,0,0, \
|
||||
0,0,0,0,0,1,1,0, \
|
||||
0,0,0,0,0,0,1,1, \
|
||||
0,0,0,0,0,0,0,1)
|
||||
|
||||
#define RELATIVE_KING_CASTLE_KINGSIDE \
|
||||
RELATIVE_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,1,0)
|
||||
|
||||
#define RELATIVE_KING_CASTLE_QUEENSIDE \
|
||||
RELATIVE_BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,1,1,0,0,0,0,0)
|
||||
|
||||
#define CORNERS \
|
||||
BITBOARD( \
|
||||
1,0,0,0,0,0,0,1, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,1)
|
||||
|
||||
#define RELATIVE_PAWN_SAFE_ZONE \
|
||||
BITBOARD( \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
0,0,0,0,0,0,0,0, \
|
||||
1,0,0,0,0,0,0,0, \
|
||||
1,0,0,1,1,0,0,0, \
|
||||
1,1,1,1,1,0,0,0, \
|
||||
1,1,1,1,1,0,0,1, \
|
||||
1,1,1,1,1,1,1,1, \
|
||||
0,0,0,0,0,0,0,0)
|
||||
|
||||
#define BOARD_CENTER_4X4 \
|
||||
((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
||||
& (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6))
|
||||
|
||||
#define BOARD_CENTER_2X2 \
|
||||
((FILE_MASK_D | FILE_MASK_E) \
|
||||
& (RANK_MASK_4 | RANK_MASK_5))
|
||||
|
||||
|
||||
#define POSITIONAL_BONUS_0 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \
|
||||
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \
|
||||
X(PIECE_BISHOP, 0.05, RELATIVE_DIAGONAL_A1_H8 | RELATIVE_DIAGONAL_A8_H1) \
|
||||
X(PIECE_KING, 0.15, RELATIVE_KING_CASTLE_KINGSIDE) \
|
||||
X(PIECE_QUEEN, -0.10, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
||||
/**/
|
||||
|
||||
#define POSITIONAL_BONUS_1 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \
|
||||
X(PIECE_BISHOP, 0.05, RELATIVE_BISHOP_KING_ATTACK) \
|
||||
/**/
|
||||
|
||||
#define POSITIONAL_BONUS_2 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_PAWN, -0.10, ~RELATIVE_PAWN_SAFE_ZONE) \
|
||||
X(PIECE_BISHOP, 0.05, CORNERS) \
|
||||
/**/
|
||||
|
||||
#define POSITIONAL_BONUS_3 \
|
||||
/* piece bonus area*/ \
|
||||
X(PIECE_BISHOP, 0.02, RELATIVE_BISHOP_QUEEN_ATTACK)
|
||||
|
||||
#define POSITIONAL_MODIFIER_COUNT 4
|
||||
static struct {bitboard const area; double const val;} const
|
||||
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = {
|
||||
#define X(p, b, a) [p] = {.area = (a), .val = b},
|
||||
#define RELATIVE_BITBOARD BITBOARD_WHITE
|
||||
[PLAYER_WHITE] = {
|
||||
{POSITIONAL_BONUS_0},
|
||||
{POSITIONAL_BONUS_1},
|
||||
{POSITIONAL_BONUS_2},
|
||||
{POSITIONAL_BONUS_3},
|
||||
},
|
||||
[PLAYER_BLACK] = {
|
||||
#undef RELATIVE_BITBOARD
|
||||
#define RELATIVE_BITBOARD BITBOARD_BLACK
|
||||
{POSITIONAL_BONUS_0},
|
||||
{POSITIONAL_BONUS_1},
|
||||
{POSITIONAL_BONUS_2},
|
||||
{POSITIONAL_BONUS_3},
|
||||
}
|
||||
#undef X
|
||||
};
|
||||
|
||||
|
||||
#undef CORNERS
|
||||
#undef BOARD_CENTER_4X4
|
||||
#undef BOARD_CENTER_2X2
|
||||
#undef BITBOARD_WHITE
|
||||
#undef BITBOARD_BLACK
|
||||
#undef RELATIVE_DIAGONAL_A1_H8
|
||||
#undef RELATIVE_DIAGONAL_A8_H1
|
||||
#undef RELATIVE_BISHOP_KING_ATTACK
|
||||
#undef RELATIVE_BISHOP_QUEEN_ATTACK
|
||||
#undef RELATIVE_KING_CASTLE_KINGSIDE
|
||||
#undef RELATIVE_KING_CASTLE_QUEENSIDE
|
||||
4
sys.h
4
sys.h
@@ -20,8 +20,8 @@ static size_t g_buf_len = 0;
|
||||
|
||||
static void* sys_mmap_anon_shared(size_t size, int, int)
|
||||
{
|
||||
/* FIXME: this program relies on very few memory allocations, a simple bump
|
||||
* allocator works for now, but will cause memory leaks in the future */
|
||||
/* FIXME: this program relies on very few memory allocations, a simple bump
|
||||
* allocator works for now, but will cause memory leaks in the future */
|
||||
size = (size + 7ULL) & ~7ULL;
|
||||
|
||||
if (g_buf_len + size > sizeof g_buf) {
|
||||
|
||||
441
tests.c
441
tests.c
@@ -1,6 +1,11 @@
|
||||
|
||||
#define FEATURE_STOPPABLE_SEARCH
|
||||
#define FEATURE_USE_PRINTF
|
||||
#undef NSTATS
|
||||
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include <unistd.h> /* usleep */
|
||||
#include <pthread.h>
|
||||
|
||||
#include "engine.h"
|
||||
#include "board_print.h"
|
||||
@@ -11,10 +16,12 @@
|
||||
|
||||
/* Tests are mostly generated by ChatGPT */
|
||||
|
||||
#define test_expect(expr) if (!(expr)) { fprintf(stderr, "test failed: expected <" #expr ">\n"); exit(EXIT_FAILURE); }
|
||||
|
||||
static void print_rook_test(const char *label,
|
||||
enum square_index sq,
|
||||
bitboard all_occ,
|
||||
bitboard own_occ)
|
||||
Sq8 sq,
|
||||
Bb64 all_occ,
|
||||
Bb64 own_occ)
|
||||
{
|
||||
printf("\n%s\n", label);
|
||||
printf("All occ:\n");
|
||||
@@ -22,7 +29,7 @@ static void print_rook_test(const char *label,
|
||||
printf("Own occ:\n");
|
||||
bitboard_print(own_occ, stdout);
|
||||
|
||||
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
printf("Rook attacks:\n");
|
||||
bitboard_print(attacks, stdout);
|
||||
}
|
||||
@@ -30,84 +37,84 @@ static void print_rook_test(const char *label,
|
||||
static void test_rooks()
|
||||
{
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_A1;
|
||||
const bitboard rook = SQ_MASK_A1;
|
||||
const bitboard all_occ = rook;
|
||||
const bitboard own_occ = rook;
|
||||
const Sq8 sq = SQ_A1;
|
||||
const Bb64 rook = SQMASK_A1;
|
||||
const Bb64 all_occ = rook;
|
||||
const Bb64 own_occ = rook;
|
||||
|
||||
/* Expected: full rank 1 and file A, except A1 */
|
||||
bitboard expected = (FILE_MASK_A | RANK_MASK_1) & ~SQ_MASK_A1;
|
||||
Bb64 expected = (FILE_MASK_A | RANK_MASK_1) & ~SQMASK_A1;
|
||||
|
||||
print_rook_test("Test 1: Rook at A1, empty board", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_A1;
|
||||
const bitboard rook = SQ_MASK_A1;
|
||||
const bitboard own_block = SQ_MASK_A2 | SQ_MASK_B1;
|
||||
const bitboard all_occ = rook | own_block;
|
||||
const bitboard own_occ = all_occ;
|
||||
const Sq8 sq = SQ_A1;
|
||||
const Bb64 rook = SQMASK_A1;
|
||||
const Bb64 own_block = SQMASK_A2 | SQMASK_B1;
|
||||
const Bb64 all_occ = rook | own_block;
|
||||
const Bb64 own_occ = all_occ;
|
||||
|
||||
/* Expected: no legal moves (immediately blocked both directions) */
|
||||
const bitboard expected = 0ULL;
|
||||
const Bb64 expected = 0ULL;
|
||||
|
||||
print_rook_test("Test 2: Rook at A1, own blockers A2, B1", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_A1;
|
||||
const bitboard rook = SQ_MASK_A1;
|
||||
const bitboard enemies = SQ_MASK_A3 | SQ_MASK_C1;
|
||||
const bitboard all_occ = rook | enemies;
|
||||
const bitboard own_occ = rook;
|
||||
const Sq8 sq = SQ_A1;
|
||||
const Bb64 rook = SQMASK_A1;
|
||||
const Bb64 enemies = SQMASK_A3 | SQMASK_C1;
|
||||
const Bb64 all_occ = rook | enemies;
|
||||
const Bb64 own_occ = rook;
|
||||
|
||||
/*
|
||||
* Expected:
|
||||
* - Along file A: A2, A3 (enemy at A3 is capturable, stop there)
|
||||
* - Along rank 1: B1, C1 (enemy at C1 capturable, stop there)
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_A2 | SQ_MASK_A3;
|
||||
expected |= SQ_MASK_B1 | SQ_MASK_C1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_A2 | SQMASK_A3;
|
||||
expected |= SQMASK_B1 | SQMASK_C1;
|
||||
|
||||
print_rook_test("Test 3: Rook at A1, enemy blockers A3, C1", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Rook Test 6: center rook on empty board */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_E5;
|
||||
const bitboard rook = SQ_MASK_E5;
|
||||
const bitboard all_occ = rook;
|
||||
const bitboard own_occ = rook;
|
||||
const Sq8 sq = SQ_E5;
|
||||
const Bb64 rook = SQMASK_E5;
|
||||
const Bb64 all_occ = rook;
|
||||
const Bb64 own_occ = rook;
|
||||
|
||||
/* Full rank 5 and file E, except E5 itself */
|
||||
bitboard expected = (FILE_MASK_E | RANK_MASK_5) & ~SQ_MASK_E5;
|
||||
Bb64 expected = (FILE_MASK_E | RANK_MASK_5) & ~SQMASK_E5;
|
||||
|
||||
print_rook_test("Rook Test 6: E5, empty board", sq, all_occ, own_occ);
|
||||
|
||||
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Rook Test 7: center rook, mixed blockers on rays */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_E5;
|
||||
const bitboard rook = SQ_MASK_E5;
|
||||
const Sq8 sq = SQ_E5;
|
||||
const Bb64 rook = SQMASK_E5;
|
||||
|
||||
/* Friendly: E7 and C5; Enemy: E3 and H5 */
|
||||
const bitboard friends = rook | SQ_MASK_E7 | SQ_MASK_C5;
|
||||
const bitboard enemies = SQ_MASK_E3 | SQ_MASK_H5;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Bb64 friends = rook | SQMASK_E7 | SQMASK_C5;
|
||||
const Bb64 enemies = SQMASK_E3 | SQMASK_H5;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/*
|
||||
* From E5:
|
||||
@@ -116,73 +123,73 @@ static void test_rooks()
|
||||
* Left: D5, then friendly C5 (stop before C5)
|
||||
* Right:F5, G5, H5 (enemy, included, then stop)
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_E6;
|
||||
expected |= SQ_MASK_E4 | SQ_MASK_E3;
|
||||
expected |= SQ_MASK_D5;
|
||||
expected |= SQ_MASK_F5 | SQ_MASK_G5 | SQ_MASK_H5;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_E6;
|
||||
expected |= SQMASK_E4 | SQMASK_E3;
|
||||
expected |= SQMASK_D5;
|
||||
expected |= SQMASK_F5 | SQMASK_G5 | SQMASK_H5;
|
||||
|
||||
print_rook_test("Rook Test 7: E5, friends E7/C5, enemies E3/H5", sq, all_occ, own_occ);
|
||||
|
||||
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Rook Test 8: edge rook on empty board (top edge, not corner) */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_C8;
|
||||
const bitboard rook = SQ_MASK_C8;
|
||||
const bitboard all_occ = rook;
|
||||
const bitboard own_occ = rook;
|
||||
const Sq8 sq = SQ_C8;
|
||||
const Bb64 rook = SQMASK_C8;
|
||||
const Bb64 all_occ = rook;
|
||||
const Bb64 own_occ = rook;
|
||||
|
||||
/*
|
||||
* From C8:
|
||||
* Down file C: C7..C1
|
||||
* Across rank 8: A8,B8,D8,E8,F8,G8,H8
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= (FILE_MASK_C & ~SQ_MASK_C8);
|
||||
expected |= (RANK_MASK_8 & ~SQ_MASK_C8);
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= (FILE_MASK_C & ~SQMASK_C8);
|
||||
expected |= (RANK_MASK_8 & ~SQMASK_C8);
|
||||
|
||||
print_rook_test("Rook Test 8: C8, empty board", sq, all_occ, own_occ);
|
||||
|
||||
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Rook Test 9: rook completely boxed in by friendly orthogonal neighbors */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_D4;
|
||||
const bitboard rook = SQ_MASK_D4;
|
||||
const bitboard friends = rook |
|
||||
SQ_MASK_D5 | SQ_MASK_D3 |
|
||||
SQ_MASK_C4 | SQ_MASK_E4;
|
||||
const bitboard enemies = 0ULL;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Sq8 sq = SQ_D4;
|
||||
const Bb64 rook = SQMASK_D4;
|
||||
const Bb64 friends = rook |
|
||||
SQMASK_D5 | SQMASK_D3 |
|
||||
SQMASK_C4 | SQMASK_E4;
|
||||
const Bb64 enemies = 0ULL;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/* All four rays are immediately blocked by own pieces */
|
||||
const bitboard expected = 0ULL;
|
||||
const Bb64 expected = 0ULL;
|
||||
|
||||
print_rook_test("Rook Test 9: D4, boxed by own pieces at D5/D3/C4/E4",
|
||||
sq, all_occ, own_occ);
|
||||
|
||||
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Rook Test 10: rook on file with non-interfering off-ray pieces */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_A4;
|
||||
const bitboard rook = SQ_MASK_A4;
|
||||
const Sq8 sq = SQ_A4;
|
||||
const Bb64 rook = SQMASK_A4;
|
||||
|
||||
/* Pieces placed off the rook's rank/file; they should have no effect */
|
||||
const bitboard off_ray = SQ_MASK_C1 | SQ_MASK_F6 | SQ_MASK_H8;
|
||||
const Bb64 off_ray = SQMASK_C1 | SQMASK_F6 | SQMASK_H8;
|
||||
(void)off_ray;
|
||||
const bitboard friends = rook | SQ_MASK_C1;
|
||||
const bitboard enemies = SQ_MASK_F6 | SQ_MASK_H8;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Bb64 friends = rook | SQMASK_C1;
|
||||
const Bb64 enemies = SQMASK_F6 | SQMASK_H8;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/*
|
||||
* From A4:
|
||||
@@ -190,22 +197,21 @@ static void test_rooks()
|
||||
* Rank 4: B4..H4
|
||||
* Pieces not on file A or rank 4 must not change attacks.
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= (FILE_MASK_A | RANK_MASK_4) & ~SQ_MASK_A4;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= (FILE_MASK_A | RANK_MASK_4) & ~SQMASK_A4;
|
||||
|
||||
print_rook_test("Rook Test 10: A4, random off-ray pieces C1/F6/H8",
|
||||
sq, all_occ, own_occ);
|
||||
|
||||
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void print_bishop_test(const char *label,
|
||||
enum square_index sq,
|
||||
bitboard all_occ,
|
||||
bitboard own_occ)
|
||||
Sq8 sq,
|
||||
Bb64 all_occ,
|
||||
Bb64 own_occ)
|
||||
{
|
||||
fprintf(stderr, "\n%s\n", label);
|
||||
fprintf(stderr, "All occ:\n");
|
||||
@@ -213,7 +219,7 @@ static void print_bishop_test(const char *label,
|
||||
fprintf(stderr, "Own occ:\n");
|
||||
bitboard_print(own_occ, stderr);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
fprintf(stderr, "Bishop attacks:\n");
|
||||
bitboard_print(attacks, stderr);
|
||||
}
|
||||
@@ -222,10 +228,10 @@ static void test_bishops(void)
|
||||
{
|
||||
/* Test 1: Bishop at D4 on empty board (only bishop present) */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_D4;
|
||||
const bitboard bishop = SQ_MASK_D4;
|
||||
const bitboard all_occ = bishop;
|
||||
const bitboard own_occ = bishop;
|
||||
const Sq8 sq = SQ_D4;
|
||||
const Bb64 bishop = SQMASK_D4;
|
||||
const Bb64 all_occ = bishop;
|
||||
const Bb64 own_occ = bishop;
|
||||
|
||||
/*
|
||||
* Expected diagonals from D4:
|
||||
@@ -234,24 +240,24 @@ static void test_bishops(void)
|
||||
* SE: E3, F2, G1
|
||||
* SW: C3, B2, A1
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_E5 | SQ_MASK_F6 | SQ_MASK_G7 | SQ_MASK_H8;
|
||||
expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7;
|
||||
expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1;
|
||||
expected |= SQ_MASK_C3 | SQ_MASK_B2 | SQ_MASK_A1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_E5 | SQMASK_F6 | SQMASK_G7 | SQMASK_H8;
|
||||
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
|
||||
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
|
||||
expected |= SQMASK_C3 | SQMASK_B2 | SQMASK_A1;
|
||||
|
||||
print_bishop_test("Bishop Test 1: D4, empty board", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Test 2: Bishop at C1 on empty board (only bishop present) */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_C1;
|
||||
const bitboard bishop = SQ_MASK_C1;
|
||||
const bitboard all_occ = bishop;
|
||||
const bitboard own_occ = bishop;
|
||||
const Sq8 sq = SQ_C1;
|
||||
const Bb64 bishop = SQMASK_C1;
|
||||
const Bb64 all_occ = bishop;
|
||||
const Bb64 own_occ = bishop;
|
||||
|
||||
/*
|
||||
* From C1, diagonals:
|
||||
@@ -259,25 +265,25 @@ static void test_bishops(void)
|
||||
* NW: B2, A3
|
||||
* SE / SW: none (edge of board)
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_D2 | SQ_MASK_E3 | SQ_MASK_F4 |
|
||||
SQ_MASK_G5 | SQ_MASK_H6;
|
||||
expected |= SQ_MASK_B2 | SQ_MASK_A3;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_D2 | SQMASK_E3 | SQMASK_F4 |
|
||||
SQMASK_G5 | SQMASK_H6;
|
||||
expected |= SQMASK_B2 | SQMASK_A3;
|
||||
|
||||
print_bishop_test("Bishop Test 2: C1, empty board", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Test 3: Bishop at D4, friendly blockers at F6 and B2 (no enemies) */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_D4;
|
||||
const bitboard bishop = SQ_MASK_D4;
|
||||
const bitboard friends = bishop | SQ_MASK_F6 | SQ_MASK_B2;
|
||||
const bitboard enemies = 0ULL;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Sq8 sq = SQ_D4;
|
||||
const Bb64 bishop = SQMASK_D4;
|
||||
const Bb64 friends = bishop | SQMASK_F6 | SQMASK_B2;
|
||||
const Bb64 enemies = 0ULL;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/*
|
||||
* From D4:
|
||||
@@ -286,26 +292,26 @@ static void test_bishops(void)
|
||||
* NW: C5, B6, A7 (no blockers)
|
||||
* SE: E3, F2, G1 (no blockers)
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_E5;
|
||||
expected |= SQ_MASK_C3;
|
||||
expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7;
|
||||
expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_E5;
|
||||
expected |= SQMASK_C3;
|
||||
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
|
||||
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
|
||||
|
||||
print_bishop_test("Bishop Test 3: D4, friendly blockers F6, B2", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Test 4: Bishop at D4, enemy blockers at F6 and B2 (no other friends) */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_D4;
|
||||
const bitboard bishop = SQ_MASK_D4;
|
||||
const bitboard friends = bishop;
|
||||
const bitboard enemies = SQ_MASK_F6 | SQ_MASK_B2;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Sq8 sq = SQ_D4;
|
||||
const Bb64 bishop = SQMASK_D4;
|
||||
const Bb64 friends = bishop;
|
||||
const Bb64 enemies = SQMASK_F6 | SQMASK_B2;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/*
|
||||
* From D4:
|
||||
@@ -314,27 +320,27 @@ static void test_bishops(void)
|
||||
* NW: C5, B6, A7
|
||||
* SE: E3, F2, G1
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_E5 | SQ_MASK_F6;
|
||||
expected |= SQ_MASK_C3 | SQ_MASK_B2;
|
||||
expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7;
|
||||
expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_E5 | SQMASK_F6;
|
||||
expected |= SQMASK_C3 | SQMASK_B2;
|
||||
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
|
||||
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
|
||||
|
||||
print_bishop_test("Bishop Test 4: D4, enemy blockers F6, B2", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Test 5: Bishop at D4, mixed friend/enemy + another friendly bishop elsewhere */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_D4;
|
||||
const bitboard bishop1 = SQ_MASK_D4; /* tested bishop */
|
||||
const bitboard bishop2 = SQ_MASK_F4; /* another friendly bishop */
|
||||
const bitboard friends = bishop1 | bishop2 | SQ_MASK_F6;
|
||||
const bitboard enemies = SQ_MASK_B2;
|
||||
const bitboard all_occ = friends | enemies;
|
||||
const bitboard own_occ = friends;
|
||||
const Sq8 sq = SQ_D4;
|
||||
const Bb64 bishop1 = SQMASK_D4; /* tested bishop */
|
||||
const Bb64 bishop2 = SQMASK_F4; /* another friendly bishop */
|
||||
const Bb64 friends = bishop1 | bishop2 | SQMASK_F6;
|
||||
const Bb64 enemies = SQMASK_B2;
|
||||
const Bb64 all_occ = friends | enemies;
|
||||
const Bb64 own_occ = friends;
|
||||
|
||||
/*
|
||||
* From D4:
|
||||
@@ -344,24 +350,23 @@ static void test_bishops(void)
|
||||
* SE: E3, F2, G1
|
||||
* Bishop at F4 is irrelevant; it does not sit on a diagonal from D4.
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected |= SQ_MASK_E5;
|
||||
expected |= SQ_MASK_C3 | SQ_MASK_B2;
|
||||
expected |= SQ_MASK_C5 | SQ_MASK_B6 | SQ_MASK_A7;
|
||||
expected |= SQ_MASK_E3 | SQ_MASK_F2 | SQ_MASK_G1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected |= SQMASK_E5;
|
||||
expected |= SQMASK_C3 | SQMASK_B2;
|
||||
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
|
||||
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
|
||||
|
||||
print_bishop_test("Bishop Test 5: D4, mixed friend/enemy + extra bishop F4", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
/* Test 6: Bishop at H8, no occupancy */
|
||||
{
|
||||
const enum square_index sq = SQ_INDEX_H8;
|
||||
const bitboard enemies = 0ULL;
|
||||
const bitboard all_occ = 0ULL;
|
||||
const bitboard own_occ = SQ_MASK_FROM_INDEX(sq);
|
||||
const Sq8 sq = SQ_H8;
|
||||
const Bb64 all_occ = 0ULL;
|
||||
const Bb64 own_occ = MASK_FROM_SQ(sq);
|
||||
|
||||
/*
|
||||
* From D4:
|
||||
@@ -371,29 +376,46 @@ static void test_bishops(void)
|
||||
* SE: E3, F2, G1
|
||||
* Bishop at F4 is irrelevant; it does not sit on a diagonal from D4.
|
||||
*/
|
||||
bitboard expected = 0ULL;
|
||||
expected = SQ_MASK_G7 | SQ_MASK_F6 | SQ_MASK_E5 | SQ_MASK_D4 |
|
||||
SQ_MASK_C3 | SQ_MASK_B2 | SQ_MASK_A1;
|
||||
Bb64 expected = 0ULL;
|
||||
expected = SQMASK_G7 | SQMASK_F6 | SQMASK_E5 | SQMASK_D4 |
|
||||
SQMASK_C3 | SQMASK_B2 | SQMASK_A1;
|
||||
|
||||
print_bishop_test("Bishop Test 6: H8, no occupancy", sq, all_occ, own_occ);
|
||||
|
||||
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
if (attacks != expected) {
|
||||
bitboard_print(attacks, stderr);
|
||||
}
|
||||
assert(attacks == expected);
|
||||
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||
if (attacks != expected) {
|
||||
bitboard_print(attacks, stderr);
|
||||
}
|
||||
test_expect(attacks == expected);
|
||||
}
|
||||
|
||||
printf("\nAll bishop_attacks_from_index tests passed.\n");
|
||||
}
|
||||
|
||||
struct timeout_params {
|
||||
struct searching_flag* x;
|
||||
uint32_t v;
|
||||
useconds_t us;
|
||||
};
|
||||
void* set_after_timeout(void* x)
|
||||
{
|
||||
struct timeout_params* p = x;
|
||||
usleep(p->us);
|
||||
searching_stop(p->x);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
bool const print_threats = true;
|
||||
|
||||
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
||||
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
||||
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
||||
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
||||
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 1
|
||||
#if 0
|
||||
test_rooks();
|
||||
test_bishops();
|
||||
#endif
|
||||
@@ -403,59 +425,81 @@ int main()
|
||||
}
|
||||
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
||||
|
||||
/* board is too big for the stack */
|
||||
struct board* b = malloc(sizeof *b);
|
||||
if (!b) {
|
||||
abort();
|
||||
}
|
||||
*b = BOARD_INIT;
|
||||
/* board could be too big for the stack */
|
||||
struct board* b = malloc(sizeof *b);
|
||||
if (!b) {
|
||||
abort();
|
||||
}
|
||||
*b = BOARD_INIT;
|
||||
board_init(b);
|
||||
|
||||
//board_load_fen_unsafe(&board, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1");
|
||||
//board_load_fen_unsafe(&board, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57");
|
||||
board_print_fen(&b->pos, stdout);
|
||||
board_print(&b->pos, NULL, stdout);
|
||||
|
||||
struct move moves[MOVE_MAX];
|
||||
//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/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(&b->pos, NULL, stdout, print_threats);
|
||||
|
||||
for (int turn = 0; turn < 200; ++turn) {
|
||||
size_t move_count = 0;
|
||||
all_moves(&b->pos, b->pos.player, &move_count, moves);
|
||||
/*
|
||||
move_count = 0;
|
||||
all_pseudolegal_moves(&b->pos, MG_ALL, b->pos.moving_side, &move_count, moves);
|
||||
|
||||
if (move_count == 0) {
|
||||
printf("no moves for %s, aborting\n", player_str[b->pos.player]);
|
||||
board_print_threats(&b->pos, stdout, NULL);
|
||||
b->pos.player = opposite_player(b->pos.player);
|
||||
board_print_threats(&b->pos, stdout, NULL);
|
||||
printf("no moves for %s, aborting\n", side_str[b->pos.moving_side]);
|
||||
board_print(&b->pos, NULL, stdout, print_threats);
|
||||
b->pos.moving_side = other_side(b->pos.moving_side);
|
||||
board_print(&b->pos, NULL, stdout, print_threats);
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
//struct move move = moves[0];
|
||||
struct search_result sr = search(b, b->pos.player, 7);
|
||||
pthread_t timer;
|
||||
struct searching_flag searching;
|
||||
searching_start(&searching);
|
||||
//atomic_init(&searching, 1);
|
||||
#if 1
|
||||
struct timeout_params timer_params = {
|
||||
.x = &searching,
|
||||
.v = false,
|
||||
.us = 2*1000*1000,
|
||||
};
|
||||
pthread_create(&timer, NULL, set_after_timeout, &timer_params);
|
||||
#endif
|
||||
|
||||
struct move move = sr.move;
|
||||
double const score = sr.score;
|
||||
struct search_result sr = search(b, b->pos.moving_side, 25, &searching);
|
||||
|
||||
struct move move = sr.move;
|
||||
Score16 const score = sr.score;
|
||||
|
||||
printf("move %d: {\n"
|
||||
" .from = %s, (%s)\n"
|
||||
" .to = %s,\n"
|
||||
" .score = %lf,\n"
|
||||
" .mask = ",
|
||||
" .score = %d,\n"
|
||||
" .mask = "
|
||||
"",
|
||||
turn,
|
||||
square_index_display[move.from],
|
||||
sq8_display[move.from],
|
||||
piece_str[b->mailbox[move.from]],
|
||||
square_index_display[move.to],
|
||||
score
|
||||
sq8_display[move.to],
|
||||
score
|
||||
);
|
||||
if (move.attr & MATTR_CAPTURE) printf("MATTR_CAPTURE ");
|
||||
if (move.attr & MATTR_PROMOTE) printf("MATTR_PROMOTE ");
|
||||
printf("\n}\n");
|
||||
|
||||
enum move_result const r = board_move_2(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
|
||||
board_print_fen(&b->pos, stdout);
|
||||
tt_print_stats(&b->tt, stdout);
|
||||
board_print(&b->pos, &move, stdout);
|
||||
board_print(&b->pos, &move, stdout, print_threats);
|
||||
fprintf(stderr, "board hist len: %zu\n", b->hist.length);
|
||||
fprintf(stderr, "\n------------------------\n\n\n");
|
||||
#endif
|
||||
|
||||
if (r == MR_STALEMATE) {
|
||||
@@ -463,16 +507,15 @@ int main()
|
||||
break;
|
||||
}
|
||||
|
||||
if (b->pos.pieces[PLAYER_WHITE][PIECE_KING] == 0ULL) {
|
||||
if (b->pos.pieces[SIDE_WHITE][PIECE_KING] == 0ULL) {
|
||||
printf("white king gone!!\n");
|
||||
exit(1);
|
||||
}
|
||||
if (b->pos.pieces[PLAYER_BLACK][PIECE_KING] == 0ULL) {
|
||||
printf("black king gone!!\n");
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
//usleep(1000000);
|
||||
if (b->pos.pieces[SIDE_BLACK][PIECE_KING] == 0ULL) {
|
||||
printf("black king gone!!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
137
wasm-compat.c
137
wasm-compat.c
@@ -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 <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
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_assert(sizeof m.from * CHAR_BIT == 8,
|
||||
"this must be checked if struct move's `from` changes");
|
||||
_Static_assert(sizeof m.to * CHAR_BIT == 8,
|
||||
"this must be checked if struct move's `to` changes");
|
||||
return ((uint32_t)m.appeal << 24ULL)
|
||||
| ((uint32_t)m.attr << 16ULL)
|
||||
| ((uint32_t)m.from << 8ULL)
|
||||
| ((uint32_t)m.to);
|
||||
_Static_assert(sizeof m.from * CHAR_BIT == 8,
|
||||
"this must be checked if struct move's `from` changes");
|
||||
_Static_assert(sizeof m.to * CHAR_BIT == 8,
|
||||
"this must be checked if struct move's `to` changes");
|
||||
|
||||
return ((uint32_t)m.appeal << 24U)
|
||||
| ((uint32_t)m.attr << 16U)
|
||||
| ((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) {
|
||||
/* appeal and attributes are ignored regardless */
|
||||
/*
|
||||
.appeal = (m >> 24) & 0xFF,
|
||||
.attr = (m >> 16) & 0xFF,
|
||||
*/
|
||||
.from = (m >> 8) & 0xFF,
|
||||
.to = (m >> 0) & 0xFF,
|
||||
};
|
||||
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)
|
||||
{
|
||||
struct search_result const sr = search(&g_board, g_board.pos.player, max_depth);
|
||||
return move_serialize(sr.move);
|
||||
struct search_result const sr = search(&g_board, g_board.pos.moving_side, max_depth);
|
||||
return (uint64_t)move_serialize(sr.move);
|
||||
}
|
||||
|
||||
int32_t wb_move(uint32_t move)
|
||||
{
|
||||
struct move const m = move_deserialize(move);
|
||||
enum move_result const mr = board_move_2(&g_board, m);
|
||||
struct move const m = move_deserialize(move);
|
||||
enum move_result const mr = board_move(&g_board, m);
|
||||
|
||||
/* TODO: this checkmate/stalemate check needs to be abstracted better */
|
||||
if (mr == MR_STALEMATE) {
|
||||
return (int32_t)MR_STALEMATE;
|
||||
}
|
||||
struct move moves[MOVE_MAX];
|
||||
size_t move_count = 0ULL;
|
||||
all_moves(&g_board.pos, opposite_player(g_board.pos.player), &move_count, moves);
|
||||
if (move_count == 0ULL) {
|
||||
return MR_CHECKMATE;
|
||||
}
|
||||
/* TODO: this checkmate/stalemate check needs to be abstracted better */
|
||||
if (mr == MR_STALEMATE) {
|
||||
return (int32_t)MR_STALEMATE;
|
||||
}
|
||||
struct move moves[MOVE_MAX];
|
||||
size_t move_count = 0ULL;
|
||||
all_pseudolegal_moves(&g_board.pos, MG_ALL, other_side(g_board.pos.moving_side), &move_count, moves);
|
||||
if (move_count == 0ULL) {
|
||||
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;
|
||||
g_board.tt.entries = g_tt_buf;
|
||||
return ((c & 0xFF) << 8U)
|
||||
| (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)
|
||||
| (pz & 0xFF);
|
||||
}
|
||||
|
||||
int32_t wb_board_at(index at)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Bb64 const m = MASK_FROM_SQ((Sq8)at);
|
||||
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) {
|
||||
return side_piece_serialize(side, pz);
|
||||
}
|
||||
}
|
||||
}
|
||||
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