From 9932a64c975ab62bc9d4bff362a2abaf56879fc0 Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Tue, 11 Jul 2023 22:18:41 +0200 Subject: [PATCH] add pvs(), aka Principal Variation Search --- src/eval-simple.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++ src/eval-simple.h | 47 +++++++++++ src/eval.c | 8 +- src/eval.h | 15 +++- src/search.c | 191 ++++++++++++++++++++++++++++++++++++++------ src/search.h | 5 +- 6 files changed, 435 insertions(+), 29 deletions(-) create mode 100644 src/eval-simple.c create mode 100644 src/eval-simple.h diff --git a/src/eval-simple.c b/src/eval-simple.c new file mode 100644 index 0000000..9c24c95 --- /dev/null +++ b/src/eval-simple.c @@ -0,0 +1,198 @@ +/* eval-simple.c - simple position evaluation. + * + * Copyright (C) 2023 Bruno Raoult ("br") + * Licensed under the GNU General Public License v3.0 or later. + * Some rights reserved. See COPYING. + * + * You should have received a copy of the GNU General Public License along with this + * program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include +#include + +#include "piece.h" +#include "eval-simple.h" +#include "position.h" + +/* + * Tables are from https://www.chessprogramming.org/Simplified_Evaluation_Function + * + * Attention! Tables are black point of view (to be visually easier to read). + */ + +static int mg_pawn[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 50, 50, 50, 50, 50, 50, 50, 50, + 10, 10, 20, 30, 30, 20, 10, 10, + 5, 5, 10, 25, 25, 10, 5, 5, + 0, 0, 0, 20, 20, 0, 0, 0, + 5, -5, -10, 0, 0, -10, -5, 5, + 5, 10, 10, -20, -20, 10, 10, 5, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int mg_knight[] = { + -50, -40, -30, -30, -30, -30, -40, -50, + -40, -20, 0, 0, 0, 0, -20, -40, + -30, 0, 10, 15, 15, 10, 0, -30, + -30, 5, 15, 20, 20, 15, 5, -30, + -30, 0, 15, 20, 20, 15, 0, -30, + -30, 5, 10, 15, 15, 10, 5, -30, + -40, -20, 0, 5, 5, 0, -20, -40, + -50, -40, -30, -30, -30, -30, -40, -50 +}; + +static int mg_bishop[] = { + -20, -10, -10, -10, -10, -10, -10, -20, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 5, 5, 10, 10, 5, 5, -10, + -10, 0, 10, 10, 10, 10, 0, -10, + -10, 10, 10, 10, 10, 10, 10, -10, + -10, 5, 0, 0, 0, 0, 5, -10, + -20, -10, -10, -10, -10, -10, -10, -20 +}; + +static int mg_rook[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 10, 10, 10, 10, 10, 10, 5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + 0, 0, 0, 5, 5, 0, 0, 0 +}; + +static int mg_queen[] = { + -20, -10, -10, -5, -5, -10, -10, -20, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -5, 0, 5, 5, 5, 5, 0, -5, + 0, 0, 5, 5, 5, 5, 0, -5, + -10, 5, 5, 5, 5, 5, 0, -10, + -10, 0, 5, 0, 0, 0, 0, -10, + -20, -10, -10, -5, -5, -10, -10, -20 +}; + +static int mg_king[] = { + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -30, -40, -40, -50, -50, -40, -40, -30, + -20, -30, -30, -40, -40, -30, -30, -20, + -10, -20, -20, -20, -20, -20, -20, -10, + 20, 20, 0, 0, 0, 0, 20, 20, + 20, 30, 10, 0, 0, 10, 30, 20 +}; + +static int eg_king[] = { + -50, -40, -30, -20, -20, -30, -40, -50, + -30, -20, -10, 0, 0, -10, -20, -30, + -30, -10, 20, 30, 30, 20, -10, -30, + -30, -10, 30, 40, 40, 30, -10, -30, + -30, -10, 30, 40, 40, 30, -10, -30, + -30, -10, 20, 30, 30, 20, -10, -30, + -30, -30, 0, 0, 0, 0, -30, -30, + -50, -30, -30, -30, -30, -30, -30, -50 +}; + +/* as pieces bitboard tables start at position 2; we make these tables + * bigger. + */ +static int *mg_tables[] = { + NULL, + NULL, + mg_pawn, + mg_knight, + mg_bishop, + mg_rook, + mg_queen, + mg_king +}; + +static int *eg_tables[] = { + NULL, + NULL, + mg_pawn, + mg_knight, + mg_bishop, + mg_rook, + mg_queen, + eg_king +}; + +/* to flip vertically a square, we need to XOR it with 56 + */ +static int mg_table[2][6 + 2][64]; +static int eg_table[2][6 + 2][64]; + +void eval_simple_init(void) +{ +# ifdef DEBUG_EVAL + log_f(1, "initializing piece tables\n"); +# endif + for (int piece = BB_PAWN; piece <= BB_KING; ++piece) { + for (int square = 0; square < 64; ++square) { + mg_table[WHITE][piece][square] = mg_tables[piece][FLIP_V(square)]; + eg_table[WHITE][piece][square] = eg_tables[piece][FLIP_V(square)]; + mg_table[BLACK][piece][square] = mg_tables[piece][square]; + eg_table[BLACK][piece][square] = eg_tables[piece][square]; + } + } +} + +/** + * eval_simple() - simple and fast position evaluation + * @pos: &position to evaluate + * + * This function is normally used only during initialization, + * or when changing phase (middlegame <--> endgame), as the eval + * will be done increntally when doing moves. + * + * @return: the @pos evaluation in centipawns + */ +eval_t eval_simple(pos_t *pos) +{ + eval_t eval[2] = { 0, 0 }; + int eg = simple_is_endgame(pos); + int (*gg)[6 + 2][64]= eg? eg_table: mg_table; + + pos->eval_simple_phase = ENDGAME; +# ifdef DEBUG_EVAL + log_f(1, "phase = %s.\n", eg? "endgame": "midgame"); +# endif + + for (int color = WHITE; color <= BLACK; ++color) { + for (uint piece = PAWN; piece <= KING; piece <<= 1) { + int bb = PIECETOBB(piece), cur; + u64 _t; + +# ifdef DEBUG_EVAL + log_f(1, "p=%u bb=%d %s %s: count=%d val=%ld ", piece, bb, color? "black": "white", + P_SYM(piece), popcount64(pos->bb[color][bb]), + popcount64(pos->bb[color][bb]) * P_VALUE(piece)); +# endif + + eval[color] += popcount64(pos->bb[color][bb]) * P_LETTER(piece); + bit_for_each64_2(cur, _t, pos->bb[color][piece]) { +# ifdef DEBUG_EVAL + log(1, "sq=%d:%d ", cur, gg[color][bb][cur]); +# endif + eval[color] += gg[color][bb][cur]; + } +# ifdef DEBUG_EVAL + log(1, "\n"); +# endif + } + } +# ifdef DEBUG_EVAL + log_f(1, "white: %d black:%d\n", eval[WHITE], eval[BLACK]); +# endif + + return eval[WHITE] - eval[BLACK]; +} diff --git a/src/eval-simple.h b/src/eval-simple.h new file mode 100644 index 0000000..3ca92ab --- /dev/null +++ b/src/eval-simple.h @@ -0,0 +1,47 @@ +/* eval-simple.h - simple position evaluation. + * + * Copyright (C) 2021 Bruno Raoult ("br") + * Licensed under the GNU General Public License v3.0 or later. + * Some rights reserved. See COPYING. + * + * You should have received a copy of the GNU General Public License along with this + * program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef EVAL_SIMPLE_H +#define EVAL_SIMPLE_H + + +#include "chessdefs.h" + +/* no queen on board */ +#define simple_no_queen(p, c) \ + ( !(p)->bb[c][BB_QUEEN] ) + +#define simple_one_queen(p, c) \ + ( popcount64((p)->bb[c][BB_QUEEN]) == 1 ) + +#define simple_no_rook(p, c) \ + (!(p)->bb[c][BB_ROOK]) + +#define simple_one_minor_piece(p, c) \ + (popcount64((p)->bb[c][BB_KNIGHT] | (p)->bb[c][BB_BISHOP]) == 1) + +#define simple_is_endgame(p) \ + ( (simple_no_queen(p, WHITE) || \ + (simple_one_queen(p, WHITE) && \ + simple_no_rook(p, WHITE) && \ + simple_one_minor_piece(p, WHITE))) \ + && \ + (simple_no_queen(p, BLACK) || \ + (simple_one_queen(p, BLACK) && \ + simple_no_rook(p, BLACK) && \ + simple_one_minor_piece(p, BLACK))) ) + +void eval_simple_init(void); +eval_t eval_simple(pos_t *pos); + +#endif /* EVAL_SIMPLE_H */ diff --git a/src/eval.c b/src/eval.c index 8711958..8ff333e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,6 +1,6 @@ /* eval.c - static position evaluation. * - * Copyright (C) 2021 Bruno Raoult ("br") + * Copyright (C) 2021-2023 Bruno Raoult ("br") * Licensed under the GNU General Public License v3.0 or later. * Some rights reserved. See COPYING. * @@ -16,6 +16,7 @@ #include #include +#include "position.h" #include "eval.h" inline eval_t eval_material(pos_t *pos, bool color) @@ -52,6 +53,9 @@ eval_t eval(pos_t *pos) { eval_t material[2] = {0}, control[2] = {0}; + if (pos->eval != EVAL_INVALID) + return pos->eval; + /* 1) pieces value */ material[WHITE] = eval_material(pos, WHITE); material[BLACK] = eval_material(pos, BLACK); @@ -85,7 +89,7 @@ eval_t eval(pos_t *pos) # ifdef DEBUG_EVAL log_f(2, "eval: %d\n", res); # endif - + pos->eval = res; return res; } diff --git a/src/eval.h b/src/eval.h index e3e1239..9b16edf 100644 --- a/src/eval.h +++ b/src/eval.h @@ -14,7 +14,20 @@ #ifndef EVAL_H #define EVAL_H -#include "position.h" +#include + +#include "chessdefs.h" +#include "piece.h" + +#define EVAL_MAX \ + KING_VALUE + \ + QUEEN_VALUE * 9 + \ + ROOK_VALUE * 2 + \ + BISHOP_VALUE * 2 + \ + KNIGHT_VALUE * 2 + +#define EVAL_MIN (-EVAL_MAX) +#define EVAL_INVALID INT_MIN eval_t eval_material(pos_t *pos, bool color); eval_t eval_mobility(pos_t *pos, bool color); diff --git a/src/search.c b/src/search.c index 9c32fae..18a682c 100644 --- a/src/search.c +++ b/src/search.c @@ -21,8 +21,15 @@ #include "search.h" /** - * negamax() - the negamax tree. + * negamax() - search position negamax. + * @pos: &position to search + * @depth: Wanted depth. + * @color: 1 for white, -1 for black. * + * Calculate the negamax value of @pos. This is an extensive search, with + * absolutely no cutoff. + * + * @return: The @pos negamax evaluation. */ eval_t negamax(pos_t *pos, int depth, int color) { @@ -30,40 +37,180 @@ eval_t negamax(pos_t *pos, int depth, int color) pos_t *newpos; eval_t best = EVAL_MIN, score; - //printf("depth=%d\n", depth); - moves_gen_all(pos); - //pos_check(pos); if (depth == 0) { - score = eval(pos); - //printf("evalnega=%d turn=%d color=%d", score, pos->turn, color); - score *= color; - //printf(" --> evalnega=%d\n", score); + moves_gen_all_nomoves(pos); + score = eval(pos) * color; return score; } - //moves_print(pos, 0); + moves_gen_all(pos); list_for_each_entry(move, &pos->moves[pos->turn], list) { log(1, "%.*s", 5 - depth, " "); newpos = move_do(pos, move); - //move_print(0, move, 0); - //printf("color piece = %d\n", COLOR(move->piece)); - //printf("turns=%d/%d\n", pos->turn, newpos->turn); - //fflush(stdout); score = -negamax(newpos, depth - 1, -color); + pos->node_count += newpos->node_count; move->negamax = score; - //printf("move="); - //move_print(0, move, 0); - //printf("score=%d\n", score); - if (score > best) { best = score; pos->bestmove = move; -# ifdef DEBUG_SEARCH - //printf("depth=%d best move=", depth); - //move_print(0, &bestmove, 0); - //printf(" eval=%d\n", best); -# endif } move_undo(newpos, move); } return best; } + + +/** + * pvs() - Principal Variation Search. + * @pos: &position to search + * @depth: wanted depth. + * @alpha: alpha value. + * @beta: beta value. + * @color: 1 for white, -1 for black. + * + * Calculate the PVS value of @pos. + * See https://en.wikipedia.org/wiki/Principal_variation_search + * + * @return: The @pos PVS evaluation. + */ +eval_t pvs(pos_t *pos, int depth, int alpha, int beta, int color) +{ + move_t *move; + pos_t *newpos; + eval_t score; + int n = 0; + + if (depth == 0) { + //return quiesce(p, alpha, beta); /* leaf node */ + moves_gen_all_nomoves(pos); + score = eval(pos) * color; + return score; + } + + moves_gen_eval_sort(pos); + + //moves_print(pos, M_PR_EVAL); + list_for_each_entry(move, &pos->moves[pos->turn], list) { + log(1, "%.*s", 5 - depth, " "); + newpos = move->pos; + log_f(1, "depth=%d eval=%d move=", depth, move->eval); + move_print(0, move, M_PR_EVAL); + log(1, "\n"); + if (!n++) { /* first child */ + score = -pvs(newpos, depth - 1, -beta, -alpha, -color); + } else { + /* search with a null window */ + score = -pvs(newpos, depth - 1, -alpha - 1, -alpha, -color); + if (score > alpha && score < beta) { + /* if failed high, do a full re-search */ + score = -pvs(newpos, depth - 1, -beta, -score, -color); + } + } + if (score > alpha) { + alpha = score; + pos->bestmove = move; + } + move_undo(newpos, move); + move->pos = NULL; + pos->node_count += newpos->node_count; + move->negamax = score; + // alpha = max(alpha, score); + if (alpha > beta) { /* beta cut-off */ + break; + } + } + + return alpha; +} + +/* + * int negascout (pos_t *pos, int depth, int alpha, int beta ) + * { /\* compute minimax value of position p *\/ + * move_t *move; + * pos_t *newpos; + * eval_t best = EVAL_MIN, score; + * + * int a, b, t, i; + * + * if (depth == 0) { + * //return quiesce(p, alpha, beta); /\* leaf node *\/ + * moves_gen_all_nomoves(pos); + * score = eval(pos) * color; + * return score; + * } + * moves_gen_all(pos); + * a = alpha; + * b = beta; + * list_for_each_entry(move, &pos->moves[pos->turn], list) { + * log(1, "%.*s", 5 - depth, " "); + * newpos = move_do(pos, move); + * // for ( i = 1; i <= w; i++ ) { + * t = -negascout (newpos, depth - 1, -b, -alpha); + * if ( (t > a) && (t < beta) && (i > 1) ) + * t = -NegaScout ( p_i, -beta, -alpha ); /\* re-search *\/ + * alpha = max( alpha, t ); + * if ( alpha >= beta ) + * return alpha; /\* cut-off *\/ + * b = alpha + 1; /\* set new null window *\/ + * } + * return alpha; + * } + */ + +/* + * int quiesce(pos_t *pos, int alpha, int beta) + * { + * int stand_pat = eval(pos); + * + * if( stand_pat >= beta ) + * return beta; + * if( alpha < stand_pat ) + * alpha = stand_pat; + * + * /\* + * * until( every_capture_has_been_examined ) { + * * MakeCapture(); + * * score = -Quiesce( -beta, -alpha ); + * * TakeBackMove(); + * * + * * if( score >= beta ) + * * return beta; + * * if( score > alpha ) + * * alpha = score; + * * } + * *\/ + * return alpha; + * } + */ + +/** + * ab_negamax() - search position negamax with alpha-beta cutoff. + * @pos: &position to search + * @depth: Wanted depth. + * @color: 1 for white, -1 for black. + * + * Calculate the negamax value of @pos, with alpha-beta pruning. + * + * @return: The @pos negamax evaluation. + */ +/*int ab_negamax(pos_t *pos, int alpha, int beta, int depth) +{ + move_t *move; + pos_t *newpos; + eval_t best = EVAL_MIN, score; + + if(depth == 0) { + //return quiesce( alpha, beta ); + moves_gen_all_nomoves(pos); + score = eval(pos) * color; + return score; + } + for ( all moves) { + score = -alphaBeta( -beta, -alpha, depthleft - 1 ); + if( score >= beta ) + return beta; // fail hard beta-cutoff + if( score > alpha ) + alpha = score; // alpha acts like max in MiniMax + } + return alpha; +} +*/ diff --git a/src/search.h b/src/search.h index 50b89c9..b76e6c2 100644 --- a/src/search.h +++ b/src/search.h @@ -14,12 +14,9 @@ #ifndef SEARCH_H #define SEARCH_H -#include #include "position.h" -#define EVAL_MIN INT_MIN -#define EVAL_MAX INT_MAX - eval_t negamax(pos_t *pos, int depth, int color); +eval_t pvs(pos_t *pos, int depth, int alpha, int beta, int color); #endif /* SEARCH_H */