From 0ff41c408b77f0edc3ada4e16f743a8f6b707a7e Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Sun, 4 Aug 2024 09:37:58 +0200 Subject: [PATCH] add repetition detection, root position count - hist.c: add hist_next() - get next entry in history - state_t: remove plycount, clock_50 -> ply50, add phase, repcount - pos_t: add plycount, plyroot - state_t moved to chessdefs.h - perft-test: split perft from do_perft (todo for perft_alt) - position.c: add pos_repcount() - search.c: add is_draw() - uci: set root position (after moves), adjust history moves repcount --- Makefile | 2 +- src/brchess.c | 4 +-- src/chessdefs.h | 2 +- src/eval-defs.h | 2 -- src/eval.c | 2 +- src/eval.h | 2 +- src/fen.c | 6 ++-- src/hist.c | 20 +++++++++--- src/hist.h | 1 + src/move-do.c | 24 ++++++++++---- src/perft.c | 81 ++++++++++++++++++++++++++++++++++++----------- src/perft.h | 2 +- src/position.c | 33 ++++++++++++++++++- src/position.h | 10 ++++-- src/search.c | 15 +++++++++ src/search.h | 1 + src/uci.c | 29 ++++++++++++----- test/perft-test.c | 2 +- 18 files changed, 183 insertions(+), 55 deletions(-) diff --git a/Makefile b/Makefile index cc829a3..181237c 100644 --- a/Makefile +++ b/Makefile @@ -415,7 +415,7 @@ TEST += movedo-test perft-test tt-test PIECE_OBJS := piece.o FEN_OBJS := $(PIECE_OBJS) fen.o position.o bitboard.o board.o \ hq.o attack.o hash.o init.o util.o alloc.o move.o \ - eval.o eval-defs.o eval-simple.o + eval.o eval-defs.o eval-simple.o hist.o BB_OBJS := $(FEN_OBJS) MOVEGEN_OBJS := $(BB_OBJS) move-gen.o ATTACK_OBJS := $(MOVEGEN_OBJS) diff --git a/src/brchess.c b/src/brchess.c index 5b158a6..181519e 100644 --- a/src/brchess.c +++ b/src/brchess.c @@ -37,10 +37,10 @@ int main(int ac, char **av) pos_t *pos; int opt; + printf("brchess " VERSION "\n"); init_all(); pos = pos_new(); - hist_link(pos); - printf("\nWelcome to brchess " VERSION "\nEngine ready.\n"); + printf("Engine ready.\n"); // size_t len = 0; // char *str = NULL; diff --git a/src/chessdefs.h b/src/chessdefs.h index 39b6822..25405de 100644 --- a/src/chessdefs.h +++ b/src/chessdefs.h @@ -85,9 +85,9 @@ typedef struct __movelist_s movelist_t; /* basic types */ - typedef u64 bitboard_t; typedef s16 eval_t; +typedef s8 phase_t; /* forward enum definition is impossible in C11. * To simplify cross-dependancies, all important enum are moved here. diff --git a/src/eval-defs.h b/src/eval-defs.h index ab95263..bd3881c 100644 --- a/src/eval-defs.h +++ b/src/eval-defs.h @@ -14,7 +14,6 @@ #ifndef EVAL_DEFS_H #define EVAL_DEFS_H - #include "chessdefs.h" #include "piece.h" #include "eval.h" @@ -26,7 +25,6 @@ enum { ENDGAME, PHASE_NB }; -typedef s16 phase_t; /* pieces weight in phase calculation. */ diff --git a/src/eval.c b/src/eval.c index c736c7e..9f2fb64 100644 --- a/src/eval.c +++ b/src/eval.c @@ -30,7 +30,7 @@ * * @return: */ -s16 calc_phase(pos_t *pos) +phase_t calc_phase(pos_t *pos) { int phase = ALL_PHASE; phase -= P_PHASE * popcount64(pos->bb[WHITE][PAWN] | pos->bb[BLACK][PAWN]); diff --git a/src/eval.h b/src/eval.h index 4a5eb07..2c2185a 100644 --- a/src/eval.h +++ b/src/eval.h @@ -19,7 +19,7 @@ #include "chessdefs.h" #include "eval-defs.h" -s16 calc_phase(pos_t *pos); +phase_t calc_phase(pos_t *pos); eval_t eval_mobility(pos_t *pos, bool color); eval_t eval_square_control(pos_t *pos, bool color); diff --git a/src/fen.c b/src/fen.c index fc98d22..be7fe72 100644 --- a/src/fen.c +++ b/src/fen.c @@ -230,12 +230,12 @@ pos_t *fen2pos(pos_t *pos, const char *fen) /* 5) half moves since last capture or pawn move (50 moves rule) */ - tmppos.clock_50 = 0; + tmppos.ply50 = 0; tmppos.plycount = 1; if (sscanf(cur, "%hd%n", &tmp, &consumed) != 1) goto end; /* early end, ignore w/o err */ - tmppos.clock_50 = tmp; + tmppos.ply50 = tmp; cur += consumed; SKIP_BLANK(cur); @@ -350,7 +350,7 @@ char *pos2fen(const pos_t *pos, char *fen) /* 5) moves since last capture or pawn move (50 moves rule) * 6) current full move number, starting with 1 */ - sprintf(fen+cur, "%d %d", pos->clock_50, + sprintf(fen+cur, "%d %d", pos->ply50, 1 + (pos->plycount - (pos->turn == BLACK)) / 2); return fen; } diff --git a/src/hist.c b/src/hist.c index 58314d3..1774a23 100644 --- a/src/hist.c +++ b/src/hist.c @@ -37,6 +37,16 @@ void hist_init(void) hist.state[0].prev = &hist.state[0]; } +/** + * hist_next() - get next history slot. + * + * return: @state_t next history slot. + */ +state_t *hist_next(void) +{ + return hist.state + hist.nstates++; +} + /** * hist_push() - add a state to hist list * @st: &state_t to add @@ -134,9 +144,10 @@ void hist_static_print(void) printf("UCI state history: "); while (true) { - printf("%s(#%lx) ", + printf("%s(#%lx r=%d) ", move_to_str(movestr, st->move, 0), - hash_short(st->key)); + hash_short(st->key), + st->repcount); if (st == HIST_START) break; st = hist_prev(st); @@ -154,9 +165,10 @@ void hist_print(pos_t *pos) state_t *st = &pos->state; printf("position states history: "); while (true) { - printf("%s(#%lx) ", + printf("%s(#%lx r=%d) ", move_to_str(movestr, st->move, 0), - hash_short(st->key)); + hash_short(st->key), + st->repcount); if (st == HIST_START) break; st = hist_prev(st); diff --git a/src/hist.h b/src/hist.h index 01b4e3a..47cfec9 100644 --- a/src/hist.h +++ b/src/hist.h @@ -48,6 +48,7 @@ extern hist_t hist; #define HIST_START (hist.state) void hist_init(void); +state_t *hist_next(void); void hist_push(state_t *st); //, move_t *move); void hist_link(pos_t *pos); state_t *hist_pop(void); diff --git a/src/move-do.c b/src/move-do.c index 16cf79c..c64cd5a 100644 --- a/src/move-do.c +++ b/src/move-do.c @@ -24,6 +24,7 @@ #include "position.h" #include "move-do.h" #include "hash.h" +#include "hist.h" /** * move_do() - do move. @@ -59,14 +60,16 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) hkey_t key = pos->key; *state = pos->state; /* save irreversible changes */ + pos->prev = state; /* update key: switch turn, reset castling and ep */ key ^= zobrist_turn; key ^= zobrist_castling[pos->castle]; key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; - ++pos->clock_50; + ++pos->ply50; ++pos->plycount; + ++pos->plyroot; pos->en_passant = SQUARE_NONE; pos->turn = them; pos->captured = captured; @@ -80,7 +83,7 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) } if (captured != EMPTY) { - pos->clock_50 = 0; + pos->ply50 = 0; bug_on(pos->board[to] == EMPTY || COLOR(pos->captured) != them); key ^= zobrist_pieces[captured][to]; pos_clr_sq(pos, to); /* clear square */ @@ -99,7 +102,7 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) pos_clr_sq(pos, rookfrom); pos->castle = clr_castle(pos->castle, us); } else if (ptype == PAWN) { /* pawn non capture or e.p. */ - pos->clock_50 = 0; + pos->ply50 = 0; if (from + up + up == to) { /* if pawn double push, set e.p. */ square_t ep = from + up; if (bb_pawn_attacks[us][ep] & pos->bb[them][PAWN]) { @@ -151,6 +154,7 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) pos->key = key; + pos->repcount = pos_repcount(pos); zobrist_verify(pos); return pos; @@ -209,6 +213,8 @@ pos_t *move_undo(pos_t *pos, const move_t move, const state_t *state) pos->state = *state; /* restore irreversible changes */ pos->turn = us; + --pos->plycount; + --pos->plyroot; return pos; } @@ -227,14 +233,16 @@ pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state) hkey_t key = pos->key; *state = pos->state; /* save irreversible changes */ + pos->prev = state; /* update key: switch turn, reset castling and ep */ key ^= zobrist_turn; key ^= zobrist_castling[pos->castle]; key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; - ++pos->clock_50; + ++pos->ply50; ++pos->plycount; + ++pos->plyroot; pos->en_passant = SQUARE_NONE; pos->turn = them; pos->captured = captured; @@ -248,8 +256,7 @@ pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state) } if (captured != EMPTY) { - pos->clock_50 = 0; - //pos->captured = pos->board[to]; /* save capture info */ + pos->ply50 = 0; bug_on(pos->board[to] == EMPTY || COLOR(pos->captured) != them); key ^= zobrist_pieces[captured][to]; pos_clr_sq(pos, to); /* clear square */ @@ -268,7 +275,7 @@ pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state) pos_clr_sq(pos, rookfrom); pos->castle = clr_castle(pos->castle, us); } else if (ptype == PAWN) { /* pawn non capture or e.p. */ - pos->clock_50 = 0; + pos->ply50 = 0; if (from + up + up == to) { /* if pawn double push, set e.p. */ square_t ep = from + up; if (bb_pawn_attacks[us][ep] & pos->bb[them][PAWN]) { @@ -320,6 +327,7 @@ pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state) pos->key = key; + pos->repcount = pos_repcount(pos); zobrist_verify(pos); return pos; @@ -361,5 +369,7 @@ pos_t *move_undo_alt(pos_t *pos, const move_t move, const state_t *state) pos->state = *state; /* restore irreversible changes */ pos->turn = us; + --pos->plycount; + --pos->plyroot; return pos; } diff --git a/src/perft.c b/src/perft.c index a05e285..970f1b3 100644 --- a/src/perft.c +++ b/src/perft.c @@ -40,7 +40,65 @@ * * @return: total moves found at @depth level. */ -u64 perft(pos_t *pos, int depth, int ply, bool divide) +static u64 do_perft(pos_t *pos, int depth) +{ + u64 subnodes = 0, nodes = 0; + movelist_t movelist; + move_t *move, *last; + state_t state; + + pos_set_checkers_pinners_blockers(pos); + pos_legal(pos, pos_gen_pseudo(pos, &movelist)); + if (depth == 1) + return movelist.nmoves; + last = movelist.move + movelist.nmoves; + + for (move = movelist.move; move < last; ++move) { + move_do(pos, *move, &state); + if (depth == 2) { + movelist_t movelist2; + pos_set_checkers_pinners_blockers(pos); + subnodes = pos_legal(pos, pos_gen_pseudo(pos, &movelist2))->nmoves; + } else if (pos->plyroot >= 3) { + hentry_t *entry = tt_probe_perft(pos->key, depth); + if (entry != TT_MISS) { + subnodes = HASH_PERFT_VAL(entry->data); + } else { + subnodes = do_perft(pos, depth - 1); + tt_store_perft(pos->key, depth, subnodes); + } + } else { + subnodes = do_perft(pos, depth - 1); + } + move_undo(pos, *move, &state); + nodes += subnodes; + } + + return nodes; +} + +/** + * perft() - Perform perft on position + * @pos: &position to search + * @depth: Wanted depth. + * @ply: current perft depth level (root = 1) + * @divide: output total for 1st level moves. + * + * Run perft on a position. This function displays the available moves at @depth + * level for each possible first move, and the total of moves. + * + * This version uses the algorithm: + * if last depth + * return 1; + * gen legal moves + * loop for legal move + * do-move + * perft (depth -1) + * undo-move + * + * @return: total moves found at @depth level. + */ +u64 perft(pos_t *pos, int depth, bool divide) { u64 subnodes = 0, nodes = 0; movelist_t movelist; @@ -56,30 +114,15 @@ u64 perft(pos_t *pos, int depth, int ply, bool divide) subnodes = 1; } else { move_do(pos, *move, &state); - if (depth == 2) { - movelist_t movelist2; - pos_set_checkers_pinners_blockers(pos); - subnodes = pos_legal(pos, pos_gen_pseudo(pos, &movelist2))->nmoves; - } else if (ply >= 3) { - hentry_t *entry = tt_probe_perft(pos->key, depth); - if (entry != TT_MISS) { - subnodes = HASH_PERFT_VAL(entry->data); - } else { - subnodes = perft(pos, depth - 1, ply + 1, divide); - tt_store_perft(pos->key, depth, subnodes); - } - } else { - subnodes = perft(pos, depth - 1, ply + 1, divide); - } + subnodes = do_perft(pos, depth - 1); move_undo(pos, *move, &state); } - nodes += subnodes; - if (ply == 1 && divide) { + if (divide) { char movestr[8]; printf("%s: %lu\n", move_to_str(movestr, *move, 0), subnodes); } + nodes += subnodes; } - return nodes; } diff --git a/src/perft.h b/src/perft.h index 810475c..a574137 100644 --- a/src/perft.h +++ b/src/perft.h @@ -16,7 +16,7 @@ #include "position.h" -u64 perft(pos_t *pos, int depth, int ply, bool output); +u64 perft(pos_t *pos, int depth,/* int ply,*/ bool divide); u64 perft_alt(pos_t *pos, int depth, int ply, bool output); #endif /* PERFT_H */ diff --git a/src/position.c b/src/position.c index 99b6d6c..40da984 100644 --- a/src/position.c +++ b/src/position.c @@ -108,6 +108,7 @@ pos_t *pos_clear(pos_t *pos) pos->king[WHITE] = SQUARE_NONE; pos->king[BLACK] = SQUARE_NONE; + pos->prev = HIST_START; return pos; } @@ -130,7 +131,7 @@ bool pos_cmp(const pos_t *pos1, const pos_t *pos2) /* move_do/undo position state */ if (_cmpf(key) || _cmpf(en_passant) || _cmpf(castle) || - _cmpf(clock_50) || _cmpf(plycount) || _cmpf(captured)) + _cmpf(ply50) || _cmpf(plycount) || _cmpf(captured)) goto end; if (_cmpf(checkers) || _cmpf(pinners) || _cmpf(blockers)) @@ -180,6 +181,36 @@ bitboard_t pos_checkers(const pos_t *pos, const color_t color) return sq_attackers(pos, occ, pos->king[color], OPPONENT(color)); } +/** + * pos_repcount() - return position repetition count. + * @pos: &position to search + * + * Attention: positions before (and including) root position repcount is + * decreased by one. See do_moves() in uci.c. + * + * @return: The number of repetitions in history, zero otherwise. + */ +u8 pos_repcount(pos_t *pos) +{ + int c50 = pos->ply50; + state_t *st = &pos->state; + hkey_t key = pos->key; + + st = hist_prev4(st); + //printf("is rep: START=%p ", HIST_START); + //printf("state=%p diff=%zd c50=%d k1=%#lx k2=%#lx\n", + // st, st - HIST_START, c50, hash_short(pos->key), hash_short(st->key)); + + /* attention, what about root position ? Isn't it dangerous to compare with + * its key ? like: st->prev != HIST_START + */ + for (; c50 >= 0 && st != HIST_START; st = hist_prev2(st), c50 -= 2) { + if (key == st->key) + return st->repcount + 1; + } + return false; +} + /** * pos_set_checkers_pinners_blockers() - calculate checkers, pinners and blockers. * @pos: &position diff --git a/src/position.h b/src/position.h index 38a0e3c..1296ab4 100644 --- a/src/position.h +++ b/src/position.h @@ -27,6 +27,7 @@ #include "piece.h" #include "move.h" #include "board.h" +#include "eval-defs.h" typedef struct __pos_s { u64 node_count; /* evaluated nodes */ @@ -50,14 +51,14 @@ typedef struct __pos_s { /* 16 bits */ move_t move; - u16 plycount; /* plies so far, start from 1 */ - s16 phase; /* 8 bits */ square_t en_passant; castle_rights_t castle; piece_t captured; /* only used in move_undo */ - u8 clock_50; + phase_t phase; + u8 ply50; + u8 repcount; /* repetition count */ ); eval_t eval; bitboard_t checkers; /* opponent checkers */ @@ -66,6 +67,8 @@ typedef struct __pos_s { piece_t board[BOARDSIZE]; bitboard_t bb[2][PT_NB]; /* bb[0][PAWN], bb[1][ALL_PIECES] */ square_t king[2]; /* dup with bb, faster retrieval */ + u16 plycount; /* plies in game, start from 1 */ + u8 plyroot; /* plies since search root. root=0 */ } pos_t; typedef struct state_s state_t; @@ -183,6 +186,7 @@ void pos_del(pos_t *pos); pos_t *pos_clear(pos_t *pos); bool pos_cmp(const pos_t *pos1, const pos_t *pos2); +u8 pos_repcount(pos_t *pos); void pos_set_checkers_pinners_blockers(pos_t *pos); void pos_set_pinners_blockers(pos_t *pos); bitboard_t pos_checkers(const pos_t *pos, const color_t color); diff --git a/src/search.c b/src/search.c index 816c846..4e558d7 100644 --- a/src/search.c +++ b/src/search.c @@ -20,6 +20,21 @@ #include "move-do.h" #include "search.h" #include "attack.h" +#include "hist.h" + +/** + * is_draw() - check if position is draw by 50 or repetition rule. + * @pos: &position to search + * + * Note that states before (and including) root position state have + * their repcount decreased by one. + * + * @return: The @pos negamax evaluation. + */ +bool is_draw(pos_t *pos) +{ + return (pos->ply50 > 100 || pos->repcount); +} /** * negamax() - search position negamax. diff --git a/src/search.h b/src/search.h index fa5766c..3b73978 100644 --- a/src/search.h +++ b/src/search.h @@ -16,6 +16,7 @@ #include "position.h" +bool is_draw(pos_t *pos); //eval_t negamax(pos_t *pos, int depth, int color); //eval_t pvs(pos_t *pos, int depth, int alpha, int beta, int color); diff --git a/src/uci.c b/src/uci.c index 7faed06..6054d6a 100644 --- a/src/uci.c +++ b/src/uci.c @@ -370,7 +370,7 @@ int do_position(pos_t *pos, char *arg) } /* link last position t history */ //hist_pop(); - hist_link(pos); + //hist_link(pos); return 1; } @@ -378,8 +378,8 @@ int do_moves(__unused pos_t *pos, char *arg) { char *saveptr = NULL, *token, check[8]; move_t move; - state_t state; movelist_t movelist; + saveptr = NULL; token = strtok_r(arg, " ", &saveptr); while (token) { @@ -395,13 +395,26 @@ int do_moves(__unused pos_t *pos, char *arg) return 1; } //printf("move: %s\n", move_to_str(check, move, 0)); - hist_push(&pos->state); /* push previous state */ - move_do(pos, move, &state); - pos_print(pos); + //hist_push(&pos->state); /* push previous state */ + move_do(pos, move, hist_next()); + printf("repet=%d\n", pos->repcount); + //if (is_repetition(pos)) + // printf("rep detected\n"); + //else if(is_draw(pos)) + // printf("draw detected\n"); hist_static_print(); token = strtok_r(NULL, " ", &saveptr); } - //hist_static_print(); + /* reset position root, and decrease history moves repcounts. + * TODO: Maybe use "ucinewgame" to decide when to perform this decrease ? + */ + pos->plyroot = 0; + for (state_t *st = &pos->state; st != HIST_START; st = hist_prev(st)) { + //printf("adjust rep=%d->\n"); + st->repcount = max(0, st->repcount - 1); + } + pos_print(pos); + hist_print(pos); return 1; } @@ -429,7 +442,7 @@ int do_perft(__unused pos_t *pos, __unused char *arg) printf("perft: divide=%d alt=%d depth=%d\n", divide, alt, depth); if (depth > 0) { if (!alt) - perft(pos, depth, 1, divide); + perft(pos, depth, divide); else perft_alt(pos, depth, 1, divide); } @@ -438,7 +451,7 @@ int do_perft(__unused pos_t *pos, __unused char *arg) int do_hist(__unused pos_t *pos, __unused char *arg) { - hist_static_print(); + hist_print(pos); return 0; } diff --git a/test/perft-test.c b/test/perft-test.c index d8cab81..6bb0e9f 100644 --- a/test/perft-test.c +++ b/test/perft-test.c @@ -364,7 +364,7 @@ int main(int ac, char**av) if (run & 1) { clock_start(&clock); - my_count = perft(pos, depth, 1, divide); + my_count = perft(pos, depth, divide); ms = clock_elapsed_ms(&clock); if (!ms) { res[0].skipped++;