Performance improvements
This commit is contained in:
190
engine.h
190
engine.h
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/* WIP switches */
|
||||
#define NEW_SEARCHING
|
||||
|
||||
#include "libc-lite.h"
|
||||
#include "sys.h"
|
||||
|
||||
@@ -20,8 +23,8 @@ static uint64_t g_ab_node_volume = 0;
|
||||
|
||||
/* --------------------------- MOVE SEARCH --------------------------------- */
|
||||
|
||||
#define SCORE_INF 1e30
|
||||
#define SCORE_CHECKMATE 999.0
|
||||
#define SCORE_INF 1e10f
|
||||
#define SCORE_CHECKMATE 999.0f
|
||||
|
||||
/* for initial ordering of moves in alphabeta search */
|
||||
static void move_compute_appeal(struct move* restrict m,
|
||||
@@ -38,16 +41,16 @@ static void move_compute_appeal(struct move* restrict m,
|
||||
n += (uint8_t)piece_value[mailbox[m->to]];
|
||||
}
|
||||
|
||||
uint8_t mmv_lva_bonus = 16*n - (uint8_t)piece_value[atk];
|
||||
uint8_t mmv_lva_bonus = (uint8_t)(16.0f*(float)n - piece_value[atk]);
|
||||
|
||||
m->appeal = mmv_lva_bonus;
|
||||
}
|
||||
|
||||
static double board_score_heuristic(struct pos const* pos)
|
||||
static float board_score_heuristic(struct pos const* pos)
|
||||
{
|
||||
/* this function always evaluates from white's perspective before
|
||||
eventually flipping the sign based on `pos` */
|
||||
double score = 0.0;
|
||||
float score = 0.0f;
|
||||
|
||||
Bb64 const occw = pos->occupied[SIDE_WHITE];
|
||||
Bb64 const occb = pos->occupied[SIDE_BLACK];
|
||||
@@ -55,57 +58,67 @@ static double board_score_heuristic(struct pos const* pos)
|
||||
|
||||
enum game_progress const gp = endgameness(pos);
|
||||
|
||||
if (pos->pieces[SIDE_WHITE][PIECE_KING] == 0) score -= (double)SCORE_CHECKMATE;
|
||||
if (pos->pieces[SIDE_BLACK][PIECE_KING] == 0) score += (double)SCORE_CHECKMATE;
|
||||
if (pos->pieces[SIDE_WHITE][PIECE_KING] == 0) {
|
||||
score -= SCORE_CHECKMATE;
|
||||
goto end;
|
||||
}
|
||||
if (pos->pieces[SIDE_BLACK][PIECE_KING] == 0) {
|
||||
score += SCORE_CHECKMATE;
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
||||
/* raw material value */
|
||||
score += piece_value[p] *
|
||||
((double)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) -
|
||||
(double)bitboard_popcount(pos->pieces[SIDE_BLACK][p]));
|
||||
((float)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) -
|
||||
(float)bitboard_popcount(pos->pieces[SIDE_BLACK][p]));
|
||||
|
||||
/* very minor advantage for threat projection to break tied moves */
|
||||
if (p != PIECE_PAWN) {
|
||||
score += 0.001 * (
|
||||
(double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_WHITE][p], occall))
|
||||
- (double)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_BLACK][p], occall)));
|
||||
}
|
||||
//if (p != PIECE_PAWN) {
|
||||
// score += 0.001f * (
|
||||
// (float)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_WHITE][p], occall))
|
||||
// - (float)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_BLACK][p], occall)));
|
||||
//}
|
||||
|
||||
/* positional bonus, see evaluations.h */
|
||||
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
||||
score += positional_modifier(SIDE_WHITE, gp, i, p).val *
|
||||
(
|
||||
(double)bitboard_popcount(
|
||||
(float)bitboard_popcount(
|
||||
pos->pieces[SIDE_WHITE][p]
|
||||
& positional_modifier(SIDE_WHITE, gp, i, p).area)
|
||||
- (double)bitboard_popcount(
|
||||
- (float)bitboard_popcount(
|
||||
pos->pieces[SIDE_BLACK][p]
|
||||
& positional_modifier(SIDE_BLACK, gp, i, p).area)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
score += 0.10f * (float)bitboard_more_than_one(pos->pieces[SIDE_WHITE][PIECE_BISHOP]);
|
||||
score -= 0.10f * (float)bitboard_more_than_one(pos->pieces[SIDE_BLACK][PIECE_BISHOP]);
|
||||
|
||||
/* pawns defending pieces are desired */
|
||||
score += 0.03 * (
|
||||
(double)bitboard_popcount(
|
||||
score += 0.03f * (
|
||||
(float)bitboard_popcount(
|
||||
pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw
|
||||
)
|
||||
- (double)bitboard_popcount(
|
||||
- (float)bitboard_popcount(
|
||||
pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb
|
||||
)
|
||||
);
|
||||
|
||||
/* stacked pawns are bad */
|
||||
const double k = 0.30;
|
||||
const float k = 0.30f;
|
||||
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||
uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi));
|
||||
uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi));
|
||||
|
||||
score -= k * (double)(wstk - (wstk == 1));
|
||||
score += k * (double)(bstk - (bstk == 1));
|
||||
score -= k * (float)(wstk - (wstk == 1));
|
||||
score += k * (float)(bstk - (bstk == 1));
|
||||
}
|
||||
|
||||
double sign = (pos->moving_side == SIDE_WHITE) ? 1.0 : -1.0;
|
||||
end:
|
||||
float sign = (pos->moving_side == SIDE_WHITE) ? 1.0f : -1.0f;
|
||||
|
||||
return sign*score;
|
||||
}
|
||||
@@ -131,21 +144,21 @@ struct move moves_linear_search(struct move moves[restrict static MOVE_MAX]
|
||||
|
||||
/* quiescence is a deep search that only considers captures */
|
||||
static
|
||||
double quiesce(struct pos const* pos,
|
||||
float quiesce(struct pos const* pos,
|
||||
Piece8 mailbox[restrict static SQ_COUNT],
|
||||
Side8 us,
|
||||
double alpha,
|
||||
double beta,
|
||||
float alpha,
|
||||
float beta,
|
||||
int8_t depth)
|
||||
{
|
||||
if (pos->pieces[us][PIECE_KING] == 0) {
|
||||
return -SCORE_CHECKMATE;
|
||||
return -(SCORE_CHECKMATE + (float)depth);
|
||||
}
|
||||
|
||||
Side8 const them = other_side(us);
|
||||
|
||||
double score = board_score_heuristic(pos);
|
||||
double highscore = score;
|
||||
float score = board_score_heuristic(pos);
|
||||
float highscore = score;
|
||||
|
||||
if (highscore >= beta) {
|
||||
return highscore;
|
||||
@@ -155,13 +168,11 @@ double quiesce(struct pos const* pos,
|
||||
}
|
||||
|
||||
size_t move_count = 0;
|
||||
|
||||
struct move moves[MOVE_MAX];
|
||||
|
||||
all_pseudolegal_moves(pos, MG_CAPTURES, us, &move_count, moves);
|
||||
if (move_count == 0) {
|
||||
/* TODO: detect stalemate */
|
||||
return -(999.0 + (double)depth);
|
||||
return -(SCORE_CHECKMATE + (float)depth);
|
||||
}
|
||||
for (size_t i = 0; i < move_count; ++i) {
|
||||
move_compute_appeal(&moves[i], pos, us, mailbox);
|
||||
@@ -180,7 +191,7 @@ double quiesce(struct pos const* pos,
|
||||
/* history is irrelevant when all moves are captures */
|
||||
static struct history hist;
|
||||
hist.length = 0;
|
||||
(void)move_piece(&poscpy, &hist, mailbox_cpy, m);
|
||||
(void)move_piece(&poscpy, us, &hist, mailbox_cpy, m);
|
||||
|
||||
score = -quiesce(&poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1);
|
||||
|
||||
@@ -208,15 +219,26 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
Piece8 mailbox[restrict static SQ_COUNT],
|
||||
Side8 us,
|
||||
int8_t depth,
|
||||
double alpha,
|
||||
double beta,
|
||||
atomic_bool* searching)
|
||||
float alpha,
|
||||
float beta
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
,
|
||||
struct searching_flag const* restrict searching
|
||||
#endif
|
||||
)
|
||||
{
|
||||
g_ab_node_volume += 1;
|
||||
|
||||
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
if (!searching_still(searching)) {
|
||||
return (struct search_option) { .init = false, .move = MOVE_NULL };
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pos->pieces[us][PIECE_KING] == 0) {
|
||||
return (struct search_option) {
|
||||
.score = -(SCORE_CHECKMATE + (double)depth),
|
||||
.score = -(SCORE_CHECKMATE + (float)depth),
|
||||
.move = (struct move){0},
|
||||
.depth = 0,
|
||||
.hash = pos->hash,
|
||||
@@ -226,7 +248,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
}
|
||||
|
||||
if (depth <= 0) {
|
||||
double sc = quiesce(pos, mailbox, us, alpha, beta, 0);
|
||||
float sc = quiesce(pos, mailbox, us, alpha, beta, 0);
|
||||
return (struct search_option){
|
||||
.score = sc,
|
||||
.move = (struct move){0},
|
||||
@@ -237,15 +259,20 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
};
|
||||
}
|
||||
|
||||
double const alpha_orig = alpha;
|
||||
float const alpha_orig = alpha;
|
||||
|
||||
struct move moves[MOVE_MAX];
|
||||
size_t move_count = 0;
|
||||
|
||||
float best_score = -SCORE_INF;
|
||||
struct move best_move = MOVE_NULL;
|
||||
bool has_principal_move = false;
|
||||
|
||||
struct search_option tte = tt_get(tt, pos->hash);
|
||||
if (tte.init && tte.hash == pos->hash) {
|
||||
if (tte.depth >= depth) {
|
||||
if (tte.flag == TT_EXACT) {
|
||||
assuming(tte.init);
|
||||
return tte;
|
||||
} else if (tte.flag == TT_LOWER) {
|
||||
if (tte.score > alpha) alpha = tte.score;
|
||||
@@ -254,6 +281,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
}
|
||||
|
||||
if (alpha >= beta) {
|
||||
assuming(tte.init);
|
||||
return tte;
|
||||
}
|
||||
}
|
||||
@@ -261,23 +289,23 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
moves[move_count] = tte.move;
|
||||
moves[move_count].appeal = APPEAL_MAX;
|
||||
++move_count;
|
||||
has_principal_move = true;
|
||||
}
|
||||
|
||||
double best_score = -SCORE_INF;
|
||||
struct move best_move = NULL_MOVE;
|
||||
|
||||
enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS};
|
||||
for (size_t i = 0; i < sizeof types / sizeof *types; ++i) {
|
||||
all_pseudolegal_moves(pos, types[i], us, &move_count, moves);
|
||||
|
||||
for (size_t i = 0; i < move_count; ++i) {
|
||||
for (size_t i = (size_t)has_principal_move; i < move_count; ++i) {
|
||||
move_compute_appeal(&moves[i], pos, us, mailbox);
|
||||
}
|
||||
|
||||
while (move_count > 0) {
|
||||
if (!atomic_load_explicit(searching, memory_order_relaxed)) {
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
if (!searching_still(searching)) {
|
||||
return (struct search_option) { .init = false };
|
||||
}
|
||||
#endif
|
||||
struct move m = moves_linear_search(moves, &move_count);
|
||||
|
||||
size_t const old_hist_len = hist->length;
|
||||
@@ -285,9 +313,9 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
Piece8 mailbox_cpy[SQ_COUNT];
|
||||
my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy);
|
||||
|
||||
enum move_result r = move_piece(&pos_cpy, hist, mailbox_cpy, m);
|
||||
enum move_result r = move_piece(&pos_cpy, us, hist, mailbox_cpy, m);
|
||||
|
||||
double score;
|
||||
float score = 0.0;
|
||||
|
||||
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
||||
score = 0.0;
|
||||
@@ -299,15 +327,18 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
other_side(us),
|
||||
depth - 1,
|
||||
-beta,
|
||||
-alpha,
|
||||
searching);
|
||||
-alpha
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
,
|
||||
searching
|
||||
#endif
|
||||
);
|
||||
if (!so.init) {
|
||||
hist->length = old_hist_len;
|
||||
return (struct search_option){ .init = false, .move = NULL_MOVE };
|
||||
return so;
|
||||
}
|
||||
score = -so.score;
|
||||
}
|
||||
|
||||
hist->length = old_hist_len;
|
||||
|
||||
if (score > best_score) {
|
||||
@@ -325,8 +356,8 @@ struct search_option alphabeta_search(struct pos const* pos,
|
||||
|
||||
finish_search:
|
||||
|
||||
if (IS_NULL_MOVE(best_move)) {
|
||||
return (struct search_option){ .init = false, .move = NULL_MOVE };
|
||||
if (IS_MOVE_NULL(best_move)) {
|
||||
return (struct search_option){ .init = true, .move = MOVE_NULL, .score = -(SCORE_CHECKMATE + (float)depth) };
|
||||
}
|
||||
|
||||
enum tt_flag flag = TT_EXACT;
|
||||
@@ -348,15 +379,31 @@ finish_search:
|
||||
|
||||
tt_insert(tt, pos->hash, out);
|
||||
|
||||
assuming(out.init);
|
||||
return out;
|
||||
}
|
||||
|
||||
static
|
||||
struct search_result {struct move move; double score;}
|
||||
search(struct board* b, Side8 us, int8_t max_depth, atomic_bool* searching)
|
||||
struct search_result {struct move move; float score;}
|
||||
search(
|
||||
struct board* restrict b,
|
||||
Side8 us,
|
||||
int8_t max_depth
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
,
|
||||
struct searching_flag const* restrict searching
|
||||
#endif
|
||||
)
|
||||
{
|
||||
struct move moves[MOVE_MAX];
|
||||
size_t move_count = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < MOVE_MAX; ++i) {
|
||||
moves[i] = MOVE_POISONED;
|
||||
}
|
||||
#endif
|
||||
|
||||
all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves);
|
||||
|
||||
assuming(move_count);
|
||||
@@ -365,26 +412,36 @@ struct search_result {struct move move; double score;}
|
||||
|
||||
g_ab_node_volume = 0;
|
||||
|
||||
double score = 0.0;
|
||||
float score = 0.0;
|
||||
|
||||
for (int8_t d = 1;
|
||||
d <= max_depth && atomic_load_explicit(searching, memory_order_relaxed);
|
||||
d <= max_depth
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
&& searching_still(searching)
|
||||
#endif
|
||||
;
|
||||
++d)
|
||||
{
|
||||
double window = SCORE_INF; /* temp debug solution */
|
||||
double alpha = score - window;
|
||||
double beta = score + window;
|
||||
float window = 2*SCORE_INF; /* temp debug solution */
|
||||
float alpha = score - window;
|
||||
float beta = score + window;
|
||||
|
||||
while (1) {
|
||||
struct search_option so =
|
||||
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta, searching);
|
||||
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
, searching
|
||||
#endif
|
||||
);
|
||||
|
||||
if (atomic_load_explicit(searching, memory_order_relaxed) == false) {
|
||||
break;
|
||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||
if (!searching_still(searching)) {
|
||||
goto breakbreak;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IS_NULL_MOVE(so.move)) {
|
||||
break;
|
||||
if (IS_MOVE_NULL(so.move)) {
|
||||
goto breakbreak;
|
||||
}
|
||||
|
||||
if (so.score > alpha && so.score < beta) {
|
||||
@@ -404,14 +461,15 @@ struct search_result {struct move move; double score;}
|
||||
}
|
||||
|
||||
}
|
||||
#ifdef USE_PRINTF
|
||||
#ifdef FEATURE_USE_PRINTF
|
||||
fprintf(stderr, "depth: %hhd\n", d);
|
||||
#endif
|
||||
}
|
||||
breakbreak:
|
||||
|
||||
#undef SCORE_INF
|
||||
|
||||
#ifdef USE_PRINTF
|
||||
#ifdef FEATURE_USE_PRINTF
|
||||
fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume);
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user