stellar

Stellar - UCI Chess engine written in C++20
git clone git://git.dimitrijedobrota.com/stellar.git
Log | Files | Refs | README | LICENSE

commit bb9fc007ecf818bf2899b0f8426dc087de918273
parent 2274b78c09de2543b93aa6b439ca7440c278e651
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Tue,  8 Aug 2023 17:45:34 +0200

Razoring, better null pruning, futility pruning

Diffstat:
Msrc/engine/engine.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/engine/stats.h | 4++--
Msrc/engine/transposition.c | 9++++++---
Msrc/engine/transposition.h | 6++++--
Msrc/score/score.c | 2+-
5 files changed, 110 insertions(+), 50 deletions(-)

diff --git a/src/engine/engine.c b/src/engine/engine.c @@ -36,13 +36,12 @@ int move_score(Stats *stats, Move move) { piece_piece(move_piece_capture(move))); } - if (move_cmp(stats->killer_moves[0][stats->ply], move)) + if (move_cmp(stats->killer[0][stats->ply], move)) return 9000; - else if (move_cmp(stats->killer_moves[1][stats->ply], move)) + else if (move_cmp(stats->killer[1][stats->ply], move)) return 8000; else - return stats - ->history_moves[piece_index(move_piece(move))][move_target(move)]; + return stats->history[piece_index(move_piece(move))][move_target(move)]; return 0; } @@ -118,22 +117,30 @@ void stats_move_unmake(Stats *self, Board *copy) { self->ply--; } +void stats_pv_store(Stats *self, Move move) { + const int ply = self->ply; + self->pv_table[ply][ply] = move; + for (int i = ply + 1; i < self->pv_length[ply + 1]; i++) { + self->pv_table[ply][i] = self->pv_table[ply + 1][i]; + } + self->pv_length[ply] = self->pv_length[ply + 1]; +} + int quiescence(Stats *stats, int alpha, int beta) { + stats->pv_length[stats->ply] = stats->ply; stats->nodes++; int score = evaluate(stats->board); + if (stats->ply > MAX_PLY - 1) return score; if (score >= beta) return beta; if (score > alpha) alpha = score; - if (stats->ply > MAX_PLY - 1) return score; Board copy; - MoveList moves; move_list_generate(&moves, stats->board); move_list_sort(stats, &moves); for (int i = 0; i < move_list_size(&moves); i++) { - Move move = move_list_move(&moves, i); if (!stats_move_make(stats, &copy, move, 1)) continue; score = -quiescence(stats, -beta, -alpha); @@ -141,6 +148,7 @@ int quiescence(Stats *stats, int alpha, int beta) { if (score > alpha) { alpha = score; + stats_pv_store(stats, move); if (score >= beta) return beta; } } @@ -148,38 +156,74 @@ int quiescence(Stats *stats, int alpha, int beta) { return alpha; } -int negamax(Stats *stats, int alpha, int beta, int depth) { +int negamax(Stats *stats, int alpha, int beta, int depth, bool null) { int pv_node = (beta - alpha) > 1; HasheFlag flag = flagAlpha; Move bestMove = {0}; + int futility = 0; Board copy; stats->pv_length[stats->ply] = stats->ply; - int score = ttable_read(stats, alpha, beta, depth); + int score = ttable_read(stats, &bestMove, alpha, beta, depth); if (stats->ply && score != TTABLE_UNKNOWN && !pv_node) return score; + // && fifty >= 100 if (stats->ply && is_repetition()) return 0; if (depth == 0) { + stats->nodes++; int score = quiescence(stats, alpha, beta); - ttable_write(stats, score, depth, flagExact); + // ttable_write(stats, bestMove, score, depth, flagExact); return score; } if (alpha < -MATE_VALUE) alpha = -MATE_VALUE; if (beta > MATE_VALUE - 1) beta = MATE_VALUE - 1; if (alpha >= beta) return alpha; - if (stats->ply > MAX_PLY - 1) return evaluate(stats->board); + // if (stats->ply > MAX_PLY - 1) return evaluate(stats->board); - stats->nodes++; int isCheck = board_isCheck(stats->board); if (isCheck) depth++; - if (depth >= 3 && !isCheck && stats->ply) { - stats_move_make_pruning(stats, &copy); - score = -negamax(stats, -beta, -beta + 1, depth - 1 - REDUCTION_MOVE); - stats_move_unmake(stats, &copy); - if (score >= beta) return beta; + if (!pv_node && !isCheck) { + int staticEval = evaluate(stats->board); + + // evaluation pruning + if (depth < 3 && abs(beta - 1) > -MATE_VALUE + 100) { + int marginEval = Score_value(PAWN) * depth; + if (staticEval - marginEval >= beta) return staticEval - marginEval; + } + + if (null) { + // null move pruning + if (stats->ply && depth > 2 && staticEval >= beta) { + stats_move_make_pruning(stats, &copy); + score = -negamax(stats, -beta, -beta + 1, + depth - 1 - REDUCTION_MOVE, false); + stats_move_unmake(stats, &copy); + if (score >= beta) return beta; + } + + // razoring + score = staticEval + Score_value(PAWN); + int scoreNew = quiescence(stats, alpha, beta); + + if (score < beta && depth == 1) { + return (scoreNew > score) ? scoreNew : score; + } + + score += Score_value(PAWN); + if (score < beta && depth < 4) { + if (scoreNew < beta) + return (scoreNew > score) ? scoreNew : score; + } + } + + // futility pruning condition + static const int margin[] = {0, 100, 300, 500}; + if (depth < 4 && abs(alpha) < MATE_SCORE && + staticEval + margin[depth] <= alpha) + futility = 1; } MoveList list; @@ -194,23 +238,40 @@ int negamax(Stats *stats, int alpha, int beta, int depth) { if (!stats_move_make(stats, &copy, move, 0)) continue; legal_moves++; + // futility pruning + if (futility && searched && !move_capture(move) && + !move_promote(move) && !board_isCheck(stats->board)) { + stats_move_unmake(stats, &copy); + continue; + } + if (!searched) { - score = -negamax(stats, -beta, -alpha, depth - 1); + score = -negamax(stats, -beta, -alpha, depth - 1, true); } else { // Late Move Reduction - if (searched >= FULL_DEPTH && depth >= REDUCTION_LIMIT && - !isCheck && !move_capture(move) && !move_promote(move)) { - score = -negamax(stats, -alpha - 1, -alpha, depth - 2); + if (!pv_node && searched >= FULL_DEPTH && + depth >= REDUCTION_LIMIT && !isCheck && !move_capture(move) && + !move_promote(move) && + (move_source(move) != + move_source(stats->killer[0][stats->ply]) || + move_target(move) != + move_target(stats->killer[0][stats->ply])) && + (move_source(move) != + move_source(stats->killer[1][stats->ply]) || + move_target(move) != + move_target(stats->killer[1][stats->ply]))) { + score = -negamax(stats, -alpha - 1, -alpha, depth - 2, true); } else score = alpha + 1; // found better move + // Principal Variation Search if (score > alpha) { - score = -negamax(stats, -alpha - 1, -alpha, depth - 1); + score = -negamax(stats, -alpha - 1, -alpha, depth - 1, true); // if fail research - if (score > alpha && score < beta) - score = -negamax(stats, -beta, -alpha, depth - 1); + if ((score > alpha) && (score < beta)) + score = -negamax(stats, -beta, -alpha, depth - 1, true); } } @@ -219,42 +280,36 @@ int negamax(Stats *stats, int alpha, int beta, int depth) { if (score > alpha) { if (!move_capture(move)) { - stats->history_moves[piece_index(move_piece(move))] - [move_target(move)] += depth; + stats->history[piece_index(move_piece(move))] + [move_target(move)] += depth; } - stats->pv_table[stats->ply][stats->ply] = move; - for (int i = stats->ply + 1; i < stats->pv_length[stats->ply + 1]; - i++) { - stats->pv_table[stats->ply][i] = - stats->pv_table[stats->ply + 1][i]; - } - stats->pv_length[stats->ply] = stats->pv_length[stats->ply + 1]; - alpha = score; flag = flagExact; + bestMove = move; + stats_pv_store(stats, move); if (score >= beta) { + ttable_write(stats, bestMove, beta, depth, flagBeta); + if (!move_capture(move)) { - stats->killer_moves[1][stats->ply] = - stats->killer_moves[0][stats->ply]; - stats->killer_moves[0][stats->ply] = move; + stats->killer[1][stats->ply] = stats->killer[0][stats->ply]; + stats->killer[0][stats->ply] = move; } - ttable_write(stats, beta, depth, flagBeta); return beta; } } } if (legal_moves == 0) { - if (isCheck) { + if (isCheck) return -MATE_VALUE + stats->ply; - } else + else return 0; } - ttable_write(stats, alpha, depth, flag); + ttable_write(stats, bestMove, alpha, depth, flag); return alpha; } @@ -272,7 +327,7 @@ void search_position(Board *board, int depth) { for (int crnt = 1; crnt <= depth;) { stats.follow_pv = 1; - int score = negamax(&stats, alpha, beta, crnt); + int score = negamax(&stats, alpha, beta, crnt, true); if ((score <= alpha) || (score >= beta)) { alpha = -INFINITY; beta = INFINITY; diff --git a/src/engine/stats.h b/src/engine/stats.h @@ -9,8 +9,8 @@ typedef struct Stats Stats; struct Stats { Move pv_table[MAX_PLY][MAX_PLY]; - Move killer_moves[2][MAX_PLY]; - U32 history_moves[16][64]; + Move killer[2][MAX_PLY]; + U32 history[16][64]; int pv_length[MAX_PLY]; struct TTable *ttable; Board *board; diff --git a/src/engine/transposition.c b/src/engine/transposition.c @@ -40,7 +40,8 @@ void ttable_clear(T *self) { memset(self->table, 0x0, sizeof(T) + self->size * sizeof(Hashe)); } -int ttable_read(const Stats *stats, int alpha, int beta, int depth) { +int ttable_read(const Stats *stats, Move *best, int alpha, int beta, + int depth) { assert(stats); assert(stats->ttable); @@ -59,11 +60,13 @@ int ttable_read(const Stats *stats, int alpha, int beta, int depth) { if ((phashe->flag == flagAlpha) && (score <= alpha)) return alpha; if ((phashe->flag == flagBeta) && (score >= beta)) return beta; } + *best = phashe->best; } return TTABLE_UNKNOWN; } -void ttable_write(const Stats *stats, int score, int depth, HasheFlag flag) { +void ttable_write(const Stats *stats, Move best, int score, int depth, + HasheFlag flag) { assert(stats); assert(stats->ttable); @@ -77,7 +80,7 @@ void ttable_write(const Stats *stats, int score, int depth, HasheFlag flag) { *phashe = (Hashe){ .key = hash, - .best = 0, + .best = best, .depth = depth, .score = score, .flag = flag, diff --git a/src/engine/transposition.h b/src/engine/transposition.h @@ -1,6 +1,7 @@ #ifndef STELLAR_TRANSPOSITION_H #define STELLAR_TRANSPOSITION_H +#include "moves.h" #include "stats.h" #include "utils.h" @@ -21,8 +22,9 @@ T *ttable_new(U64 size); void ttable_free(T **self); void ttable_clear(T *self); -int ttable_read(const Stats *stats, int alpha, int beta, int depth); -void ttable_write(const Stats *stats, int score, int depth, HasheFlag flag); +int ttable_read(const Stats *stats, Move *best, int alpha, int beta, int depth); +void ttable_write(const Stats *stats, Move best, int score, int depth, + HasheFlag flag); #undef T #endif diff --git a/src/score/score.c b/src/score/score.c @@ -7,7 +7,7 @@ struct Score_T { int capture[6]; }; -const struct Score_T Scores[] = { +static const struct Score_T Scores[] = { [PAWN] = { .value = 100, .position = {