stellarUCI 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 15:45:34 +0200 |
Razoring, better null pruning, futility pruning
Diffstat:M | src/engine/engine.c | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
M | src/engine/stats.h | | | ++-- |
M | src/engine/transposition.c | | | ++++++--- |
M | src/engine/transposition.h | | | ++++-- |
M | src/score/score.c | | | +- |
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, ©, 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, ©);
score = -negamax(stats, -beta, -beta + 1, depth - 1 - REDUCTION_MOVE);
stats_move_unmake(stats, ©);
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, ©);
score = -negamax(stats, -beta, -beta + 1,
depth - 1 - REDUCTION_MOVE, false);
stats_move_unmake(stats, ©);
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, ©, move, 0)) continue;
legal_moves++;
// futility pruning
if (futility && searched && !move_capture(move) &&
!move_promote(move) && !board_isCheck(stats->board)) {
stats_move_unmake(stats, ©);
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 = {