Major rework: add search timer, evaluation improvements, more
ADDITIONAL CHANGES Alpha-Beta search * Change back to recursive alphabeta with implicit stack * Add atomic_bool parameter that stops search when set to false * Update tests accordingly Evaluation * Fix bug where black pawns are using white's positional modifier bonus Makefile * Add -march=native to clang release builds
This commit is contained in:
2
Makefile
2
Makefile
@@ -7,7 +7,7 @@ CFLAGS.gcc.release := -Ofast
|
|||||||
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address
|
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address
|
||||||
|
|
||||||
CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion
|
CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion
|
||||||
CFLAGS.clang.release := -Ofast
|
CFLAGS.clang.release := -Ofast -march=native
|
||||||
CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address
|
CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address
|
||||||
CFLAGS.clang.wasm := \
|
CFLAGS.clang.wasm := \
|
||||||
--target=wasm32-unknown-unknown -O3 -nostdlib \
|
--target=wasm32-unknown-unknown -O3 -nostdlib \
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ static void board_print_fen(struct pos const* pos, FILE* out)
|
|||||||
static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
||||||
{
|
{
|
||||||
int buf[8][8] = {0};
|
int buf[8][8] = {0};
|
||||||
|
int color[8][8] = {0};
|
||||||
|
|
||||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||||
@@ -207,7 +208,10 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
|||||||
for (index i = 7; i < 8; i--) {
|
for (index i = 7; i < 8; i--) {
|
||||||
for (index j = 0; j < 8; ++j) {
|
for (index j = 0; j < 8; ++j) {
|
||||||
if (x & (1ULL<<(i*8+j))) {
|
if (x & (1ULL<<(i*8+j))) {
|
||||||
buf[i][j] = piece_unicode[player][piece];
|
buf[i][j] = piece_unicode[PLAYER_BLACK][piece];
|
||||||
|
color[i][j] = (player == PLAYER_WHITE)
|
||||||
|
? 1
|
||||||
|
: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,19 +225,36 @@ static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
|||||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||||
for (index j = 0; j < 8; ++j) {
|
for (index j = 0; j < 8; ++j) {
|
||||||
index const n = INDEX_FROM_RF(i,j);
|
index const n = INDEX_FROM_RF(i,j);
|
||||||
if (move && n == move->from) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 104); /* 44: blue*/
|
int bg, fg;
|
||||||
} else if (move && n == move->to) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
/**/ if (color[i][j] == 1) {
|
||||||
} else {
|
fg = 97; /* bright white */
|
||||||
/* 45: magenta, 47: white */
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
|
||||||
}
|
}
|
||||||
|
else if (color[i][j] == 2) {
|
||||||
|
fg = 30; /* black */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fg = 35; /* magenta (should not happen) */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move && n == move->from) {
|
||||||
|
bg = 104; /* blue */
|
||||||
|
} else if (move && n == move->to) {
|
||||||
|
bg = 44; /* bright blue */
|
||||||
|
} else {
|
||||||
|
/* 45: magenta,
|
||||||
|
* 47: white */
|
||||||
|
bg = (i+j) % 2 ? 45 : 47;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, "\033[%d;%dm", fg, bg);
|
||||||
|
|
||||||
if (buf[i][j]) {
|
if (buf[i][j]) {
|
||||||
fprintf(out, "%lc ", buf[i][j]);
|
fprintf(out, "%lc ", buf[i][j]);
|
||||||
} else {
|
} else {
|
||||||
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
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, "\033[0m"); /* reset background color */
|
||||||
|
|||||||
488
engine.h
488
engine.h
@@ -4,6 +4,7 @@
|
|||||||
#include "sys.h"
|
#include "sys.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define assert(expr) \
|
#define assert(expr) \
|
||||||
@@ -444,19 +445,17 @@ static inline enum rank_index index_to_rank(enum square_index p)
|
|||||||
|
|
||||||
static bitboard cardinals_from_index(enum square_index p)
|
static bitboard cardinals_from_index(enum square_index p)
|
||||||
{
|
{
|
||||||
/* might benefit from a lookup table */
|
return (FILE_MASK(index_to_file(p)) | RANK_MASK(index_to_rank(p)));
|
||||||
return (FILE_MASK(index_to_file(p)) | RANK_MASK(index_to_rank(p)))
|
|
||||||
& ~SQ_MASK_FROM_INDEX(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bitboard diagonals_from_index(enum square_index sq)
|
static bitboard diagonals_from_index(enum square_index sq)
|
||||||
{
|
{
|
||||||
|
#ifdef CODEGEN
|
||||||
enum rank_index const rank = index_to_rank(sq);
|
enum rank_index const rank = index_to_rank(sq);
|
||||||
enum file_index const file = index_to_file(sq);
|
enum file_index const file = index_to_file(sq);
|
||||||
|
|
||||||
bitboard mask = 0ULL;
|
bitboard mask = 0ULL;
|
||||||
|
|
||||||
/* Ensure signed-underflow detection rules match your style */
|
|
||||||
_Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned");
|
_Static_assert(((enum rank_index)-1) > 0, "rank_index must be unsigned");
|
||||||
_Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned");
|
_Static_assert(((enum file_index)-1) > 0, "file_index must be unsigned");
|
||||||
|
|
||||||
@@ -469,7 +468,6 @@ static bitboard diagonals_from_index(enum square_index sq)
|
|||||||
mask |= SQ_MASK_FROM_RF(r, f);
|
mask |= SQ_MASK_FROM_RF(r, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NW (rank+1, file-1) */
|
|
||||||
for (r = rank+1, f = file-1;
|
for (r = rank+1, f = file-1;
|
||||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
++r, --f)
|
++r, --f)
|
||||||
@@ -477,7 +475,6 @@ static bitboard diagonals_from_index(enum square_index sq)
|
|||||||
mask |= SQ_MASK_FROM_RF(r, f);
|
mask |= SQ_MASK_FROM_RF(r, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SE (rank-1, file+1) */
|
|
||||||
for (r = rank-1, f = file+1;
|
for (r = rank-1, f = file+1;
|
||||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
--r, ++f)
|
--r, ++f)
|
||||||
@@ -485,15 +482,21 @@ static bitboard diagonals_from_index(enum square_index sq)
|
|||||||
mask |= SQ_MASK_FROM_RF(r, f);
|
mask |= SQ_MASK_FROM_RF(r, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SW (rank-1, file-1) */
|
|
||||||
for (r = rank-1, f = file-1;
|
for (r = rank-1, f = file-1;
|
||||||
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
--r, --f)
|
--r, --f)
|
||||||
{
|
{
|
||||||
mask |= SQ_MASK_FROM_RF(r, f);
|
mask |= SQ_MASK_FROM_RF(r, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mask;
|
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 bitboard diagonals[SQ_INDEX_COUNT]; */
|
||||||
|
return diagonals[sq];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -914,13 +917,13 @@ struct board {
|
|||||||
enum piece mailbox[SQ_INDEX_COUNT];
|
enum piece mailbox[SQ_INDEX_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
static void move_compute_appeal(struct move* m,
|
static void move_compute_appeal(struct move* restrict m,
|
||||||
struct pos const* pos,
|
struct pos const* restrict pos,
|
||||||
enum player us,
|
enum player us,
|
||||||
enum piece mailbox[restrict static SQ_INDEX_COUNT])
|
enum piece mailbox[restrict static SQ_INDEX_COUNT])
|
||||||
{
|
{
|
||||||
enum player them = opposite_player(us);
|
|
||||||
/* MVV-LVA: https://www.chessprogramming.org/MVV-LVA */
|
/* MVV-LVA: https://www.chessprogramming.org/MVV-LVA */
|
||||||
|
enum player them = opposite_player(us);
|
||||||
enum piece const atk = mailbox[m->from];
|
enum piece const atk = mailbox[m->from];
|
||||||
|
|
||||||
uint8_t n = 1;
|
uint8_t n = 1;
|
||||||
@@ -928,7 +931,10 @@ static void move_compute_appeal(struct move* m,
|
|||||||
n += (uint8_t)piece_value[mailbox[m->to]];
|
n += (uint8_t)piece_value[mailbox[m->to]];
|
||||||
}
|
}
|
||||||
|
|
||||||
m->appeal = 16*n - (uint8_t)piece_value[atk];
|
uint8_t mmv_lva_bonus = 16*n - (uint8_t)piece_value[atk];
|
||||||
|
assert((uint8_t)16*n > (uint8_t)piece_value[atk]);
|
||||||
|
|
||||||
|
m->appeal = mmv_lva_bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BOARD_INIT (struct board) { \
|
#define BOARD_INIT (struct board) { \
|
||||||
@@ -1001,6 +1007,21 @@ static void move_compute_appeal(struct move* m,
|
|||||||
.hist = {0}, \
|
.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) {
|
||||||
|
b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0],
|
||||||
|
SYS_PROT_READ | SYS_PROT_WRITE,
|
||||||
|
SYS_MADV_RANDOM);
|
||||||
|
if (b->tt.entries == NULL) {
|
||||||
|
__builtin_trap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
||||||
{
|
{
|
||||||
/* TODO: this function is not tested for malicious/corrupted inputs */
|
/* TODO: this function is not tested for malicious/corrupted inputs */
|
||||||
@@ -1517,6 +1538,29 @@ static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option
|
|||||||
#endif
|
#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_ENTRIES;
|
||||||
|
|
||||||
|
#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 NDEBUG
|
||||||
|
tt->insertions += 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
enum move_result {
|
enum move_result {
|
||||||
MR_NORMAL,
|
MR_NORMAL,
|
||||||
MR_CHECK,
|
MR_CHECK,
|
||||||
@@ -1708,10 +1752,10 @@ static enum move_result board_move_2(struct board* b, struct move move)
|
|||||||
move);
|
move);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Placeholder heuristic
|
/* --------------------------- MOVE SEARCH --------------------------------- */
|
||||||
*
|
#include "evaluations.h"
|
||||||
* */
|
|
||||||
static double board_score_heuristic(struct pos const* pos)
|
static double board_score_heuristic(struct pos const* pos)
|
||||||
{
|
{
|
||||||
/* this function always evaluates from white's perspective before
|
/* this function always evaluates from white's perspective before
|
||||||
@@ -1723,7 +1767,11 @@ static double board_score_heuristic(struct pos const* pos)
|
|||||||
- static struct {bitboard const area; double const val} const
|
- static struct {bitboard const area; double const val} const
|
||||||
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT];
|
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT];
|
||||||
* */
|
* */
|
||||||
#include "evaluations.h"
|
bitboard const occw = pos->occupied[PLAYER_WHITE];
|
||||||
|
bitboard const occb = pos->occupied[PLAYER_BLACK];
|
||||||
|
bitboard const occall = occw | occb;
|
||||||
|
|
||||||
|
enum game_progress const gp = endgameness(pos);
|
||||||
|
|
||||||
for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
||||||
/* raw material value */
|
/* raw material value */
|
||||||
@@ -1731,34 +1779,35 @@ static double board_score_heuristic(struct pos const* pos)
|
|||||||
((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) -
|
((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) -
|
||||||
(double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p]));
|
(double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p]));
|
||||||
|
|
||||||
/* pawns defending pieces are desired */
|
/* very minor advantage for threat projection to break tied moves */
|
||||||
score += 0.05 * (
|
score += 0.001 * (
|
||||||
(double)bitboard_popcount(
|
(double)bitboard_popcount(piece_attacks(p, PLAYER_WHITE, 0, p, occall, 0))
|
||||||
pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN])
|
- (double)bitboard_popcount(piece_attacks(p, PLAYER_BLACK, 0, p, occall, 0)));
|
||||||
& pos->pieces[PLAYER_WHITE][p]
|
|
||||||
)
|
|
||||||
- (double)bitboard_popcount(
|
|
||||||
pawn_attacks_black(pos->pieces[PLAYER_WHITE][PIECE_PAWN])
|
|
||||||
& pos->pieces[PLAYER_WHITE][p]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* positional bonus, see evaluations.h */
|
/* positional bonus, see evaluations.h */
|
||||||
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
||||||
score += positional_modifier[PLAYER_WHITE][i][p].val *
|
score += positional_modifier(PLAYER_WHITE, gp, i, p).val *
|
||||||
(
|
(
|
||||||
(double)bitboard_popcount(
|
(double)bitboard_popcount(
|
||||||
pos->pieces[PLAYER_WHITE][p]
|
pos->pieces[PLAYER_WHITE][p]
|
||||||
& positional_modifier[PLAYER_WHITE][i][p].area
|
& positional_modifier(PLAYER_WHITE, gp, i, p).area)
|
||||||
)
|
|
||||||
- (double)bitboard_popcount(
|
- (double)bitboard_popcount(
|
||||||
pos->pieces[PLAYER_BLACK][p]
|
pos->pieces[PLAYER_BLACK][p]
|
||||||
& positional_modifier[PLAYER_BLACK][i][p].area
|
& positional_modifier(PLAYER_BLACK, gp, i, p).area)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pawns defending pieces are desired */
|
||||||
|
score += 0.05 * (
|
||||||
|
(double)bitboard_popcount(
|
||||||
|
pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN]) & occw
|
||||||
|
)
|
||||||
|
- (double)bitboard_popcount(
|
||||||
|
pawn_attacks_black(pos->pieces[PLAYER_BLACK][PIECE_PAWN]) & occb
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
/* stacked pawns are bad */
|
/* stacked pawns are bad */
|
||||||
const double k = 0.30;
|
const double k = 0.30;
|
||||||
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||||
@@ -1859,68 +1908,6 @@ double quiesce(struct pos const* pos,
|
|||||||
return highscore;
|
return highscore;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ab_frame {
|
|
||||||
enum search_stage {
|
|
||||||
ST_INIT = 0,
|
|
||||||
ST_LOOP = 1,
|
|
||||||
ST_WAIT_CHILD = 2,
|
|
||||||
} stage;
|
|
||||||
|
|
||||||
struct pos pos;
|
|
||||||
enum piece mailbox[SQ_INDEX_COUNT];
|
|
||||||
enum player us;
|
|
||||||
int8_t depth;
|
|
||||||
uint64_t mattr_filter;
|
|
||||||
|
|
||||||
double alpha;
|
|
||||||
double beta;
|
|
||||||
double alpha_orig;
|
|
||||||
|
|
||||||
struct search_option tte;
|
|
||||||
|
|
||||||
struct move moves[MOVE_MAX];
|
|
||||||
size_t move_count;
|
|
||||||
|
|
||||||
double best_score;
|
|
||||||
struct move best_move;
|
|
||||||
|
|
||||||
size_t old_hist_length;
|
|
||||||
struct move pending_move;
|
|
||||||
|
|
||||||
struct search_option result;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void ab_update_parent_after_score(struct ab_frame *parent,
|
|
||||||
struct tt *tt,
|
|
||||||
struct move m,
|
|
||||||
double score)
|
|
||||||
{
|
|
||||||
if (score > parent->best_score) {
|
|
||||||
parent->best_score = score;
|
|
||||||
parent->best_move = m;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (score > parent->alpha) {
|
|
||||||
parent->alpha = score;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent->alpha >= parent->beta) {
|
|
||||||
struct search_option out = {
|
|
||||||
.score = parent->alpha,
|
|
||||||
.move = parent->best_move,
|
|
||||||
.depth = parent->depth,
|
|
||||||
.hash = parent->pos.hash,
|
|
||||||
.init = true,
|
|
||||||
.flag = TT_LOWER,
|
|
||||||
};
|
|
||||||
tt_insert(tt, parent->pos.hash, out);
|
|
||||||
parent->result = out;
|
|
||||||
|
|
||||||
/* mark parent as complete so it will be popped in st_wait_child. */
|
|
||||||
parent->stage = ST_WAIT_CHILD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
struct search_option alphabeta_search(struct pos const* pos,
|
struct search_option alphabeta_search(struct pos const* pos,
|
||||||
struct history* hist,
|
struct history* hist,
|
||||||
@@ -1928,265 +1915,167 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
enum piece mailbox[restrict static SQ_INDEX_COUNT],
|
enum piece mailbox[restrict static SQ_INDEX_COUNT],
|
||||||
enum player us,
|
enum player us,
|
||||||
int8_t depth,
|
int8_t depth,
|
||||||
uint64_t mattr_filter,
|
|
||||||
double alpha,
|
double alpha,
|
||||||
double beta)
|
double beta,
|
||||||
|
atomic_bool* searching)
|
||||||
{
|
{
|
||||||
struct {
|
if (depth <= 0) {
|
||||||
struct ab_frame stack[50];
|
double sc = quiesce(pos, mailbox, us, alpha, beta, 0);
|
||||||
size_t len;
|
return (struct search_option){
|
||||||
} ab_stack = {0};
|
.score = sc,
|
||||||
|
|
||||||
#define STACK_PUSH(s, x) \
|
|
||||||
do { \
|
|
||||||
assert(s.len < sizeof s.stack / sizeof s.stack[0]); \
|
|
||||||
(s.stack)[(s.len)++] = (x); \
|
|
||||||
} while (0)
|
|
||||||
#define STACK_TOP(s) &(s.stack)[(s.len) - 1]
|
|
||||||
#define STACK_POP(s) (assert(s.len > 0), s.stack[--(s.len)]);
|
|
||||||
#define STACK_EMPTY(s) (s.len == 0)
|
|
||||||
|
|
||||||
struct ab_frame root = {
|
|
||||||
.stage = ST_INIT,
|
|
||||||
.pos = *pos,
|
|
||||||
.us = us,
|
|
||||||
.depth = depth,
|
|
||||||
.mattr_filter = mattr_filter,
|
|
||||||
.alpha = alpha,
|
|
||||||
.beta = beta
|
|
||||||
};
|
|
||||||
my_memcpy(root.mailbox, mailbox, sizeof root.mailbox);
|
|
||||||
|
|
||||||
STACK_PUSH(ab_stack, root);
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
struct ab_frame *fr = STACK_TOP(ab_stack);
|
|
||||||
|
|
||||||
switch (fr->stage) {
|
|
||||||
|
|
||||||
case ST_INIT: {
|
|
||||||
if (fr->depth <= 0) {
|
|
||||||
fr->result = (struct search_option) {
|
|
||||||
.score = quiesce(&fr->pos, fr->mailbox, fr->us,
|
|
||||||
fr->alpha, fr->beta, 0),
|
|
||||||
.move = (struct move){0},
|
.move = (struct move){0},
|
||||||
.depth = 0,
|
.depth = 0,
|
||||||
.hash = fr->pos.hash,
|
.hash = pos->hash,
|
||||||
.init = true,
|
.init = true,
|
||||||
.flag = TT_EXACT,
|
.flag = TT_EXACT,
|
||||||
};
|
};
|
||||||
fr->stage = ST_WAIT_CHILD;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fr->alpha_orig = fr->alpha;
|
double const alpha_orig = alpha;
|
||||||
|
|
||||||
fr->tte = tt_get(tt, fr->pos.hash);
|
struct search_option tte = tt_get(tt, pos->hash);
|
||||||
if (fr->tte.init && fr->tte.hash == fr->pos.hash && fr->tte.depth >= fr->depth) {
|
if (tte.init && tte.hash == pos->hash && tte.depth >= depth) {
|
||||||
if (fr->tte.flag == TT_EXACT) {
|
if (tte.flag == TT_EXACT) {
|
||||||
fr->result = fr->tte;
|
return tte;
|
||||||
fr->stage = ST_WAIT_CHILD;
|
} else if (tte.flag == TT_LOWER) {
|
||||||
break;
|
if (tte.score > alpha) alpha = tte.score;
|
||||||
} else if (fr->tte.flag == TT_LOWER) {
|
} else if (tte.flag == TT_UPPER) {
|
||||||
if (fr->tte.score > fr->alpha) {
|
if (tte.score < beta) beta = tte.score;
|
||||||
fr->alpha = fr->tte.score;
|
|
||||||
}
|
}
|
||||||
} else if (fr->tte.flag == TT_UPPER) {
|
|
||||||
if (fr->tte.score < fr->beta) {
|
if (alpha >= beta) {
|
||||||
fr->beta = fr->tte.score;
|
return tte;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fr->alpha >= fr->beta) {
|
struct move moves[MOVE_MAX];
|
||||||
fr->result = fr->tte;
|
size_t move_count = 0;
|
||||||
fr->stage = ST_WAIT_CHILD;
|
all_moves(pos, us, &move_count, moves);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fr->move_count = 0;
|
|
||||||
all_moves(&fr->pos, fr->us, &fr->move_count, fr->moves);
|
|
||||||
|
|
||||||
/* checkmate or stalemate */
|
/* checkmate or stalemate */
|
||||||
if (fr->move_count == 0) {
|
if (move_count == 0) {
|
||||||
double score = 0.0;
|
double score = 0.0;
|
||||||
if (attacks_to(&fr->pos,
|
if (attacks_to(pos,
|
||||||
fr->pos.pieces[fr->us][PIECE_KING],
|
pos->pieces[us][PIECE_KING],
|
||||||
0ULL, 0ULL) != 0ULL) {
|
0ULL, 0ULL) != 0ULL) {
|
||||||
score = -(999.0 + (double)fr->depth);
|
score = -(999.0 + (double)depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
fr->result = (struct search_option) {
|
return (struct search_option){
|
||||||
.score = score,
|
.score = score,
|
||||||
.move = (struct move){0},
|
.move = (struct move){0},
|
||||||
.depth = fr->depth,
|
.depth = depth,
|
||||||
.hash = fr->pos.hash,
|
.hash = pos->hash,
|
||||||
.init = true,
|
.init = true,
|
||||||
.flag = TT_EXACT,
|
.flag = TT_EXACT,
|
||||||
};
|
};
|
||||||
fr->stage = ST_WAIT_CHILD;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < fr->move_count; ++i) {
|
for (size_t i = 0; i < move_count; ++i) {
|
||||||
move_compute_appeal(&fr->moves[i], &fr->pos, fr->us, fr->mailbox);
|
move_compute_appeal(&moves[i], pos, us, mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* put existing TT entry first */
|
/* put existing TT move first */
|
||||||
if (fr->tte.init && fr->tte.hash == fr->pos.hash) {
|
if (tte.init && tte.hash == pos->hash) {
|
||||||
for (size_t i = 0; i < fr->move_count; ++i) {
|
for (size_t i = 0; i < move_count; ++i) {
|
||||||
if (fr->moves[i].from == fr->tte.move.from &&
|
if (moves[i].from == tte.move.from &&
|
||||||
fr->moves[i].to == fr->tte.move.to) {
|
moves[i].to == tte.move.to) {
|
||||||
fr->moves[i].appeal = APPEAL_MAX;
|
moves[i].appeal = APPEAL_MAX;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fr->best_score = -1e300;
|
double best_score = -1e300;
|
||||||
fr->best_move = fr->moves[0];
|
struct move best_move = moves[0];
|
||||||
fr->stage = ST_LOOP;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ST_LOOP: {
|
while (move_count > 0 && atomic_load_explicit(searching, memory_order_relaxed)) {
|
||||||
if (fr->result.init) {
|
struct move m = moves_linear_search(moves, &move_count);
|
||||||
fr->stage = ST_WAIT_CHILD;
|
|
||||||
break;
|
size_t const old_hist_len = hist->length;
|
||||||
|
struct pos pos_cpy = *pos;
|
||||||
|
enum piece mailbox_cpy[SQ_INDEX_COUNT];
|
||||||
|
my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy);
|
||||||
|
|
||||||
|
enum move_result r = board_move(&pos_cpy, hist, mailbox_cpy, m);
|
||||||
|
|
||||||
|
double score;
|
||||||
|
|
||||||
|
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
||||||
|
score = 0.0;
|
||||||
|
} else {
|
||||||
|
score = -alphabeta_search(&pos_cpy,
|
||||||
|
hist,
|
||||||
|
tt,
|
||||||
|
mailbox_cpy,
|
||||||
|
opposite_player(us),
|
||||||
|
depth - 1,
|
||||||
|
-beta,
|
||||||
|
-alpha,
|
||||||
|
searching).score;
|
||||||
|
}
|
||||||
|
|
||||||
|
hist->length = old_hist_len;
|
||||||
|
|
||||||
|
if (score > best_score) {
|
||||||
|
best_score = score;
|
||||||
|
best_move = m;
|
||||||
|
}
|
||||||
|
if (score > alpha) {
|
||||||
|
alpha = score;
|
||||||
|
}
|
||||||
|
if (alpha >= beta) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fr->move_count == 0) {
|
|
||||||
enum tt_flag flag = TT_EXACT;
|
enum tt_flag flag = TT_EXACT;
|
||||||
if (fr->best_score <= fr->alpha_orig) flag = TT_UPPER;
|
if (best_score <= alpha_orig) {
|
||||||
|
flag = TT_UPPER;
|
||||||
|
}
|
||||||
|
else if (best_score >= beta) {
|
||||||
|
flag = TT_LOWER;
|
||||||
|
}
|
||||||
|
|
||||||
fr->result = (struct search_option) {
|
struct search_option out = (struct search_option){
|
||||||
.score = fr->best_score,
|
.score = best_score,
|
||||||
.move = fr->best_move,
|
.move = best_move,
|
||||||
.depth = fr->depth,
|
.depth = depth,
|
||||||
.hash = fr->pos.hash,
|
.hash = pos->hash,
|
||||||
.init = true,
|
.init = true,
|
||||||
.flag = flag,
|
.flag = flag,
|
||||||
};
|
};
|
||||||
tt_insert(tt, fr->pos.hash, fr->result);
|
|
||||||
fr->stage = ST_WAIT_CHILD;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct move m = moves_linear_search(fr->moves, &fr->move_count);
|
tt_insert(tt, pos->hash, out);
|
||||||
|
|
||||||
if (fr->mattr_filter && !(m.attr & fr->mattr_filter)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fr->old_hist_length = hist->length;
|
|
||||||
|
|
||||||
struct pos child_pos = fr->pos;
|
|
||||||
enum piece child_mailbox[SQ_INDEX_COUNT];
|
|
||||||
my_memcpy(child_mailbox, fr->mailbox, sizeof child_mailbox);
|
|
||||||
|
|
||||||
enum move_result const r = board_move(&child_pos, hist, child_mailbox, m);
|
|
||||||
|
|
||||||
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
|
||||||
hist->length = fr->old_hist_length;
|
|
||||||
ab_update_parent_after_score(fr, tt, m, 0.0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fr->depth - 1 <= 0) {
|
|
||||||
double score = -quiesce(&child_pos,
|
|
||||||
child_mailbox,
|
|
||||||
opposite_player(fr->us),
|
|
||||||
-fr->beta,
|
|
||||||
-fr->alpha,
|
|
||||||
0);
|
|
||||||
|
|
||||||
hist->length = fr->old_hist_length;
|
|
||||||
ab_update_parent_after_score(fr, tt, m, score);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fr->pending_move = m;
|
|
||||||
fr->stage = ST_WAIT_CHILD;
|
|
||||||
|
|
||||||
struct ab_frame child = {0};
|
|
||||||
child.stage = ST_INIT;
|
|
||||||
child.pos = child_pos;
|
|
||||||
my_memcpy(child.mailbox, child_mailbox, sizeof child.mailbox);
|
|
||||||
child.us = opposite_player(fr->us);
|
|
||||||
child.depth = fr->depth - 1;
|
|
||||||
child.mattr_filter = fr->mattr_filter;
|
|
||||||
child.alpha = -fr->beta;
|
|
||||||
child.beta = -fr->alpha;
|
|
||||||
|
|
||||||
STACK_PUSH(ab_stack, child);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ST_WAIT_CHILD: {
|
|
||||||
struct search_option out = fr->result;
|
|
||||||
STACK_POP(ab_stack);
|
|
||||||
|
|
||||||
if (STACK_EMPTY(ab_stack)) {
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ab_frame *parent = STACK_TOP(ab_stack);
|
|
||||||
|
|
||||||
if (parent->stage == ST_WAIT_CHILD) {
|
|
||||||
/* parent is waiting on a child (this frame), propagate */
|
|
||||||
double score = -out.score;
|
|
||||||
struct move m = parent->pending_move;
|
|
||||||
|
|
||||||
hist->length = parent->old_hist_length;
|
|
||||||
|
|
||||||
/* resume parent move loop. */
|
|
||||||
parent->stage = ST_LOOP;
|
|
||||||
|
|
||||||
ab_update_parent_after_score(parent, tt, m, score);
|
|
||||||
} else {
|
|
||||||
/* parent wasn't waiting: treat as a completed frame result being
|
|
||||||
propagated through a completion chain. */
|
|
||||||
parent->result = out;
|
|
||||||
parent->stage = ST_WAIT_CHILD;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
/* unreachable */
|
|
||||||
assert(0);
|
|
||||||
__builtin_unreachable();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct search_result {struct move move; double score;}
|
static struct search_result {struct move move; double score;}
|
||||||
search(struct board* b, enum player us, int8_t max_depth)
|
search(struct board* b, enum player us, int8_t max_depth, atomic_bool* searching)
|
||||||
{
|
{
|
||||||
#if 1
|
struct move moves[MOVE_MAX];
|
||||||
if (b->tt.entries == NULL) {
|
size_t move_count = 0;
|
||||||
b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0],
|
all_moves(&b->pos, us, &move_count, moves);
|
||||||
SYS_PROT_READ | SYS_PROT_WRITE,
|
|
||||||
SYS_MADV_RANDOM);
|
|
||||||
if (b->tt.entries == NULL) {
|
|
||||||
__builtin_trap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct move best_move = {0};
|
assert(move_count);
|
||||||
|
|
||||||
|
struct move best_move = moves[0];
|
||||||
|
|
||||||
|
#define SCORE_INF 1e300
|
||||||
double score = 0.0;
|
double score = 0.0;
|
||||||
|
|
||||||
#define SCORE_INF 1e80
|
for (int8_t d = 1;
|
||||||
|
d <= max_depth && atomic_load_explicit(searching, memory_order_relaxed);
|
||||||
for (int8_t d = 1; d <= max_depth; ++d) {
|
++d)
|
||||||
double window = 2.0;
|
{
|
||||||
|
double window = 1000; /* temp debug solution */
|
||||||
double alpha = score - window;
|
double alpha = score - window;
|
||||||
double beta = score + window;
|
double beta = score + window;
|
||||||
|
|
||||||
while (true) {
|
while (atomic_load_explicit(searching, memory_order_relaxed)) {
|
||||||
struct search_option so =
|
struct search_option so =
|
||||||
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, 0, alpha, beta);
|
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta, searching);
|
||||||
|
|
||||||
if (so.score > alpha && so.score < beta) {
|
if (so.score > alpha && so.score < beta) {
|
||||||
score = so.score;
|
score = so.score;
|
||||||
@@ -2196,12 +2085,11 @@ search(struct board* b, enum player us, int8_t max_depth)
|
|||||||
|
|
||||||
window *= 2;
|
window *= 2;
|
||||||
|
|
||||||
alpha = score - window;
|
|
||||||
beta = score + window;
|
|
||||||
|
|
||||||
if (so.score <= alpha) {
|
if (so.score <= alpha) {
|
||||||
alpha = -SCORE_INF;
|
alpha = -SCORE_INF;
|
||||||
|
beta = score + window;
|
||||||
} else {
|
} else {
|
||||||
|
alpha = score - window;
|
||||||
beta = SCORE_INF;
|
beta = SCORE_INF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
337
evaluations.h
337
evaluations.h
@@ -2,6 +2,52 @@
|
|||||||
|
|
||||||
#pragma once
|
#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 (enum player pl = PLAYER_BEGIN; pl < PLAYER_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 > 14) {
|
||||||
|
return GP_MIDDLE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return GP_END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define BITBOARD_WHITE( \
|
#define BITBOARD_WHITE( \
|
||||||
a8,b8,c8,d8,e8,f8,g8,h8, \
|
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||||
a7,b7,c7,d7,e7,f7,g7,h7, \
|
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||||
@@ -44,8 +90,97 @@ h7##g7##f7##e7##d7##c7##b7##a7##\
|
|||||||
h8##g8##f8##e8##d8##c8##b8##a8##\
|
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||||
ULL
|
ULL
|
||||||
|
|
||||||
#define RELATIVE_DIAGONAL_A1_H8 \
|
#define REL_RANK_1 \
|
||||||
RELATIVE_BITBOARD( \
|
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 REL_DIAGONAL_A1_H8 \
|
||||||
|
REL_BITBOARD( \
|
||||||
0,0,0,0,0,0,0,1, \
|
0,0,0,0,0,0,0,1, \
|
||||||
0,0,0,0,0,0,1,0, \
|
0,0,0,0,0,0,1,0, \
|
||||||
0,0,0,0,0,1,0,0, \
|
0,0,0,0,0,1,0,0, \
|
||||||
@@ -55,8 +190,8 @@ ULL
|
|||||||
0,1,0,0,0,0,0,0, \
|
0,1,0,0,0,0,0,0, \
|
||||||
1,0,0,0,0,0,0,0)
|
1,0,0,0,0,0,0,0)
|
||||||
|
|
||||||
#define RELATIVE_DIAGONAL_A8_H1 \
|
#define REL_DIAGONAL_A8_H1 \
|
||||||
RELATIVE_BITBOARD( \
|
REL_BITBOARD( \
|
||||||
1,0,0,0,0,0,0,0, \
|
1,0,0,0,0,0,0,0, \
|
||||||
0,1,0,0,0,0,0,0, \
|
0,1,0,0,0,0,0,0, \
|
||||||
0,0,1,0,0,0,0,0, \
|
0,0,1,0,0,0,0,0, \
|
||||||
@@ -66,8 +201,8 @@ ULL
|
|||||||
0,0,0,0,0,0,1,0, \
|
0,0,0,0,0,0,1,0, \
|
||||||
0,0,0,0,0,0,0,1)
|
0,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
#define RELATIVE_BISHOP_KING_ATTACK \
|
#define REL_BISHOP_KING_ATTACK \
|
||||||
RELATIVE_BITBOARD( \
|
REL_BITBOARD( \
|
||||||
0,0,0,0,0,0,1,1, \
|
0,0,0,0,0,0,1,1, \
|
||||||
0,0,0,0,0,1,1,0, \
|
0,0,0,0,0,1,1,0, \
|
||||||
0,0,0,0,1,1,0,0, \
|
0,0,0,0,1,1,0,0, \
|
||||||
@@ -77,8 +212,8 @@ ULL
|
|||||||
1,1,0,0,0,0,0,0, \
|
1,1,0,0,0,0,0,0, \
|
||||||
1,0,0,0,0,0,0,0)
|
1,0,0,0,0,0,0,0)
|
||||||
|
|
||||||
#define RELATIVE_BISHOP_QUEEN_ATTACK \
|
#define REL_BISHOP_QUEEN_ATTACK \
|
||||||
RELATIVE_BITBOARD( \
|
REL_BITBOARD( \
|
||||||
1,1,0,0,0,0,0,0, \
|
1,1,0,0,0,0,0,0, \
|
||||||
0,1,1,0,0,0,0,0, \
|
0,1,1,0,0,0,0,0, \
|
||||||
0,0,1,1,0,0,0,0, \
|
0,0,1,1,0,0,0,0, \
|
||||||
@@ -88,8 +223,8 @@ ULL
|
|||||||
0,0,0,0,0,0,1,1, \
|
0,0,0,0,0,0,1,1, \
|
||||||
0,0,0,0,0,0,0,1)
|
0,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
#define RELATIVE_KING_CASTLE_KINGSIDE \
|
#define REL_KING_CASTLE_KINGSIDE \
|
||||||
RELATIVE_BITBOARD( \
|
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, \
|
||||||
@@ -99,8 +234,8 @@ ULL
|
|||||||
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,0,1,0)
|
||||||
|
|
||||||
#define RELATIVE_KING_CASTLE_QUEENSIDE \
|
#define REL_KING_CASTLE_QUEENSIDE \
|
||||||
RELATIVE_BITBOARD( \
|
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, \
|
||||||
@@ -121,8 +256,8 @@ ULL
|
|||||||
0,0,0,0,0,0,0,0, \
|
0,0,0,0,0,0,0,0, \
|
||||||
1,0,0,0,0,0,0,1)
|
1,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
#define RELATIVE_PAWN_SAFE_ZONE \
|
#define REL_EARLY_PAWN_STRUCTURE \
|
||||||
BITBOARD( \
|
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,0,0,0,0,0,0,0, \
|
1,0,0,0,0,0,0,0, \
|
||||||
@@ -132,6 +267,10 @@ ULL
|
|||||||
1,1,1,1,1,1,1,1, \
|
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 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 \
|
#define BOARD_CENTER_4X4 \
|
||||||
((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
||||||
& (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6))
|
& (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6))
|
||||||
@@ -140,63 +279,163 @@ ULL
|
|||||||
((FILE_MASK_D | FILE_MASK_E) \
|
((FILE_MASK_D | FILE_MASK_E) \
|
||||||
& (RANK_MASK_4 | RANK_MASK_5))
|
& (RANK_MASK_4 | RANK_MASK_5))
|
||||||
|
|
||||||
|
#define POSITIONAL_MODIFIER_COUNT 4
|
||||||
|
|
||||||
#define POSITIONAL_BONUS_0 \
|
/* ------------------------------ early game ------------------------------- */
|
||||||
|
|
||||||
|
#define EARLY_POSITIONAL_BONUS_0 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \
|
||||||
X(PIECE_KNIGHT, 0.05, 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_BISHOP, 0.05, REL_DIAGONAL_A1_H8 | REL_DIAGONAL_A8_H1) \
|
||||||
X(PIECE_KING, 0.15, RELATIVE_KING_CASTLE_KINGSIDE) \
|
X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \
|
||||||
X(PIECE_QUEEN, -0.10, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
X(PIECE_QUEEN, -0.10, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define POSITIONAL_BONUS_1 \
|
#define EARLY_POSITIONAL_BONUS_1 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \
|
||||||
X(PIECE_BISHOP, 0.05, RELATIVE_BISHOP_KING_ATTACK) \
|
X(PIECE_BISHOP, 0.05, REL_BISHOP_KING_ATTACK) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define POSITIONAL_BONUS_2 \
|
#define EARLY_POSITIONAL_BONUS_2 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, -0.10, ~RELATIVE_PAWN_SAFE_ZONE) \
|
X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \
|
||||||
X(PIECE_BISHOP, 0.05, CORNERS) \
|
X(PIECE_BISHOP, 0.05, CORNERS) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define POSITIONAL_BONUS_3 \
|
#define EARLY_POSITIONAL_BONUS_3 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_BISHOP, 0.02, RELATIVE_BISHOP_QUEEN_ATTACK)
|
X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK)
|
||||||
|
|
||||||
#define POSITIONAL_MODIFIER_COUNT 4
|
|
||||||
static struct {bitboard const area; double const val;} const
|
/* ------------------------------ middle game ------------------------------ */
|
||||||
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = {
|
/* (almost same as opening for now, but queen is not punished for moving) */
|
||||||
#define X(p, b, a) [p] = {.area = (a), .val = b},
|
|
||||||
#define RELATIVE_BITBOARD BITBOARD_WHITE
|
#define MIDDLE_POSITIONAL_BONUS_0 \
|
||||||
[PLAYER_WHITE] = {
|
/* piece bonus area*/ \
|
||||||
{POSITIONAL_BONUS_0},
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \
|
||||||
{POSITIONAL_BONUS_1},
|
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \
|
||||||
{POSITIONAL_BONUS_2},
|
X(PIECE_BISHOP, 0.05, REL_DIAGONAL_A1_H8 | REL_DIAGONAL_A8_H1) \
|
||||||
{POSITIONAL_BONUS_3},
|
X(PIECE_KING, 0.15, REL_KING_CASTLE_KINGSIDE) \
|
||||||
},
|
/**/
|
||||||
[PLAYER_BLACK] = {
|
|
||||||
#undef RELATIVE_BITBOARD
|
#define MIDDLE_POSITIONAL_BONUS_1 \
|
||||||
#define RELATIVE_BITBOARD BITBOARD_BLACK
|
/* piece bonus area*/ \
|
||||||
{POSITIONAL_BONUS_0},
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \
|
||||||
{POSITIONAL_BONUS_1},
|
X(PIECE_BISHOP, 0.05, REL_BISHOP_KING_ATTACK) \
|
||||||
{POSITIONAL_BONUS_2},
|
/**/
|
||||||
{POSITIONAL_BONUS_3},
|
|
||||||
}
|
#define MIDDLE_POSITIONAL_BONUS_2 \
|
||||||
#undef X
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, -0.10, ~REL_EARLY_PAWN_STRUCTURE) \
|
||||||
|
X(PIECE_BISHOP, 0.05, CORNERS) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define MIDDLE_POSITIONAL_BONUS_3 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_BISHOP, 0.02, REL_BISHOP_QUEEN_ATTACK) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/* ------------------------------- end game -------------------------------- */
|
||||||
|
|
||||||
|
#define LATE_POSITIONAL_BONUS_0 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \
|
||||||
|
X(PIECE_KING, 0.10, BOARD_CENTER_6X6) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define LATE_POSITIONAL_BONUS_1 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, 0.30, REL_RANK_7 | REL_RANK_6) \
|
||||||
|
X(PIECE_KING, 0.10, BOARD_CENTER_4X4) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define LATE_POSITIONAL_BONUS_2 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, 0.70, REL_RANK_7) \
|
||||||
|
X(PIECE_KING, 0.10, BOARD_CENTER_2X2) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define LATE_POSITIONAL_BONUS_3 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_KING, -0.50, ~BOARD_CENTER_6X6) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
struct posmod {
|
||||||
|
bitboard const area;
|
||||||
|
double const val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline struct posmod positional_modifier(enum player pl, enum game_progress st, size_t layer, enum piece pz)
|
||||||
|
{
|
||||||
|
static struct posmod const
|
||||||
|
lookup[PLAYER_COUNT][GP_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = {
|
||||||
|
#define X(p, b, a) [p] = {.area = (a), .val = b},
|
||||||
|
#define REL_BITBOARD BITBOARD_WHITE
|
||||||
|
[PLAYER_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
|
||||||
|
[PLAYER_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 CORNERS
|
||||||
#undef BOARD_CENTER_4X4
|
#undef BOARD_CENTER_4X4
|
||||||
#undef BOARD_CENTER_2X2
|
#undef BOARD_CENTER_2X2
|
||||||
#undef BITBOARD_WHITE
|
#undef BITBOARD_WHITE
|
||||||
#undef BITBOARD_BLACK
|
#undef BITBOARD_BLACK
|
||||||
#undef RELATIVE_DIAGONAL_A1_H8
|
#undef REL_DIAGONAL_A1_H8
|
||||||
#undef RELATIVE_DIAGONAL_A8_H1
|
#undef REL_DIAGONAL_A8_H1
|
||||||
#undef RELATIVE_BISHOP_KING_ATTACK
|
#undef REL_BISHOP_KING_ATTACK
|
||||||
#undef RELATIVE_BISHOP_QUEEN_ATTACK
|
#undef REL_BISHOP_QUEEN_ATTACK
|
||||||
#undef RELATIVE_KING_CASTLE_KINGSIDE
|
#undef REL_KING_CASTLE_KINGSIDE
|
||||||
#undef RELATIVE_KING_CASTLE_QUEENSIDE
|
#undef REL_KING_CASTLE_QUEENSIDE
|
||||||
|
|||||||
38
tests.c
38
tests.c
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
#define _XOPEN_SOURCE 500
|
#define _XOPEN_SOURCE 500
|
||||||
#include <unistd.h> /* usleep */
|
#include <unistd.h> /* usleep */
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
#include "board_print.h"
|
#include "board_print.h"
|
||||||
@@ -387,9 +388,21 @@ static void test_bishops(void)
|
|||||||
printf("\nAll bishop_attacks_from_index tests passed.\n");
|
printf("\nAll bishop_attacks_from_index tests passed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct timeout_params {
|
||||||
|
atomic_bool* x;
|
||||||
|
bool v;
|
||||||
|
useconds_t us;
|
||||||
|
};
|
||||||
|
void* set_after_timeout(void* x)
|
||||||
|
{
|
||||||
|
struct timeout_params* p = x;
|
||||||
|
usleep(p->us);
|
||||||
|
*p->x = p->v;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
||||||
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
||||||
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
||||||
|
|
||||||
@@ -403,16 +416,18 @@ int main()
|
|||||||
}
|
}
|
||||||
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
||||||
|
|
||||||
/* board is too big for the stack */
|
/* board could be too big for the stack */
|
||||||
struct board* b = malloc(sizeof *b);
|
struct board* b = malloc(sizeof *b);
|
||||||
if (!b) {
|
if (!b) {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
*b = BOARD_INIT;
|
*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(b, "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_load_fen_unsafe(b, "8/8/2kr4/6R1/4K3/6P1/8/8 b - - 0 1");
|
||||||
board_print_fen(&b->pos, stdout);
|
//board_load_fen_unsafe(b, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57");
|
||||||
|
//board_print_fen(b->pos, stdout);
|
||||||
board_print(&b->pos, NULL, stdout);
|
board_print(&b->pos, NULL, stdout);
|
||||||
|
|
||||||
struct move moves[MOVE_MAX];
|
struct move moves[MOVE_MAX];
|
||||||
@@ -429,8 +444,17 @@ int main()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct move move = moves[0];
|
pthread_t timer;
|
||||||
struct search_result sr = search(b, b->pos.player, 7);
|
atomic_bool searching;
|
||||||
|
atomic_init(&searching, true);
|
||||||
|
struct timeout_params timer_params = {
|
||||||
|
.x = &searching,
|
||||||
|
.v = false,
|
||||||
|
.us = 3*1000*1000,
|
||||||
|
};
|
||||||
|
pthread_create(&timer, NULL, set_after_timeout, &timer_params);
|
||||||
|
|
||||||
|
struct search_result sr = search(b, b->pos.player, 5, &searching);
|
||||||
|
|
||||||
struct move move = sr.move;
|
struct move move = sr.move;
|
||||||
double const score = sr.score;
|
double const score = sr.score;
|
||||||
|
|||||||
Reference in New Issue
Block a user