diff --git a/Makefile b/Makefile index 9fc950e..77119c4 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ CPPFLAGS += -DBUG_ON # brlib bug.h #CPPFLAGS += -DDEBUG_FEN # FEN decoding # hash.c -#CPPFLAGS += -HASH_VERIFY # chk zobrist consistency +CPPFLAGS += -DZOBRIST_VERIFY # chk zobrist consistency # attack.c #CPPFLAGS += -DDEBUG_ATTACK_ATTACKERS # sq_attackers @@ -339,7 +339,7 @@ memcheck: targets .PHONY: testing test TEST := piece-test fen-test bitboard-test movegen-test attack-test -TEST += movedo-test perft-test +TEST += movedo-test perft-test tt-test PIECE_OBJS := piece.o FEN_OBJS := $(PIECE_OBJS) fen.o position.o bitboard.o board.o \ @@ -349,6 +349,7 @@ MOVEGEN_OBJS := $(BB_OBJS) move.o move-gen.o ATTACK_OBJS := $(MOVEGEN_OBJS) MOVEDO_OBJS := $(ATTACK_OBJS) move-do.o misc.o PERFT_OBJS := $(MOVEDO_OBJS) search.o +TT_OBJS := $(MOVEDO_OBJS) TEST := $(addprefix $(BINDIR)/,$(TEST)) @@ -359,6 +360,7 @@ MOVEGEN_OBJS := $(addprefix $(OBJDIR)/,$(MOVEGEN_OBJS)) ATTACK_OBJS := $(addprefix $(OBJDIR)/,$(ATTACK_OBJS)) MOVEDO_OBJS := $(addprefix $(OBJDIR)/,$(MOVEDO_OBJS)) PERFT_OBJS := $(addprefix $(OBJDIR)/,$(PERFT_OBJS)) +TT_OBJS := $(addprefix $(OBJDIR)/,$(TT_OBJS)) test: echo TEST=$(TEST) @@ -394,6 +396,10 @@ bin/perft-test: test/perft-test.c test/common-test.h $(PERFT_OBJS) @echo compiling $@ test executable. @$(CC) $(ALL_CFLAGS) $< $(PERFT_OBJS) $(ALL_LDFLAGS) -o $@ +bin/tt-test: test/tt-test.c test/common-test.h $(TT_OBJS) + @echo compiling $@ test executable. + @$(CC) $(ALL_CFLAGS) $< $(TT_OBJS) $(ALL_LDFLAGS) -o $@ + ##################################### Makefile debug .PHONY: showflags wft diff --git a/src/hash.c b/src/hash.c index e67cb70..6e34c0c 100644 --- a/src/hash.c +++ b/src/hash.c @@ -206,7 +206,7 @@ int tt_create(s32 sizemb) hash_tt.nbits = nbits; hash_tt.nbuckets = BIT(hash_tt.nbits); - hash_tt.nkeys = hash_tt.nbuckets * NBUCKETS; + hash_tt.nkeys = hash_tt.nbuckets * ENTRIES_PER_BUCKET; hash_tt.bytes = hash_tt.nbuckets * sizeof(bucket_t); hash_tt.mb = hash_tt.bytes / 1024 / 1024; @@ -238,9 +238,10 @@ void tt_clear() if (hash_tt.keys) memset(hash_tt.keys, 0, hash_tt.bytes); - hash_tt.used_buckets = 0; hash_tt.used_keys = 0; hash_tt.collisions = 0; + hash_tt.hits = 0; + hash_tt.misses = 0; } /** @@ -270,12 +271,12 @@ hentry_t *tt_probe(hkey_t key) bucket = hash_tt.keys + (key & hash_tt.mask); /* find key in buckets */ - for (i = 0; i < NBUCKETS; ++i) { + for (i = 0; i < ENTRIES_PER_BUCKET; ++i) { entry = bucket->entry + i; if (key == entry->key) break; } - if (i < NBUCKETS) + if (i < ENTRIES_PER_BUCKET) return entry; return NULL; } @@ -299,18 +300,18 @@ hentry_t *tt_probe_perft(const hkey_t key, const u16 depth) bucket = hash_tt.keys + (key & hash_tt.mask); /* find key in buckets */ - for (i = 0; i < NBUCKETS; ++i) { + for (i = 0; i < ENTRIES_PER_BUCKET; ++i) { entry = bucket->entry + i; if (key == entry->key && HASH_PERFT_DEPTH(entry->data) == depth) { - printf("tt hit: key=%lx bucket=%lu entry=%d!\n", - key, bucket - hash_tt.keys, i); - break; + hash_tt.hits++; + //printf("tt hit: key=%lx bucket=%lu entry=%d!\n", + // key, bucket - hash_tt.keys, i); + return entry; } } - if (i < NBUCKETS) - return entry; - printf("tt miss: key=%lx bucket=%lu\n", - key, bucket - hash_tt.keys); + //printf("tt miss: key=%lx bucket=%lu\n", + // key, bucket - hash_tt.keys); + hash_tt.misses++; return TT_MISS; } @@ -325,24 +326,36 @@ hentry_t *tt_store_perft(const hkey_t key, const u16 depth, const u64 nodes) { bucket_t *bucket; hentry_t *entry; - int replace = -1, i; - // uint mindepth = 0; + int replace = -1, newkey = 0; + uint mindepth = 1024; u64 data = HASH_PERFT(depth, nodes); - printf("tt_store: key=%lx data=%lx depth=%d=%d nodes=%lu=%lu\n", - key, data, depth, HASH_PERFT_DEPTH(data), nodes, HASH_PERFT_VAL(data)); + + //printf("tt_store: key=%lx data=%lx depth=%d=%d nodes=%lu=%lu\n", + // key, data, depth, HASH_PERFT_DEPTH(data), nodes, HASH_PERFT_VAL(data)); + printf("tt_store: key=%lx depth=%d nodes=%lu ", + key, depth, nodes); bug_on(!hash_tt.keys); bucket = hash_tt.keys + (key & hash_tt.mask); /* find key in buckets */ - for (i = 0; i < NBUCKETS; ++i) { + for (int i = 0; i < ENTRIES_PER_BUCKET; ++i) { entry = bucket->entry + i; + if (key == entry->key && HASH_PERFT_DEPTH(entry->data)) { + printf("tt_store: sup key/depth, this should not happen!\n"); + return NULL; + } if (!entry->key) { replace = i; break; } + /* we replace hash if we are higher in tree */ + if (HASH_PERFT_DEPTH(entry->data) < mindepth) { + mindepth = HASH_PERFT_DEPTH(entry->data); + replace = i; + } /* * else { - * /\* we replace hash if we are higher in tree *\/ + * * if (key == entry->key && HASH_PERFT_DEPTH(entry->data) > mindepth) { * mindepth = HASH_PERFT_DEPTH(entry->data); * replace = i; @@ -351,12 +364,32 @@ hentry_t *tt_store_perft(const hkey_t key, const u16 depth, const u64 nodes) */ } if (replace >= 0) { - printf("replacing key=%lx=%lx bucket=%lu idx=%d val=%lu\n", - key, entry->key, bucket - hash_tt.keys, replace, nodes); entry = bucket->entry + replace; + if (HASH_PERFT_VAL(entry->data)) { + printf("REPL entry=%lu[%d] key=%lx->%lx val=%lu->%lu\n", + bucket - hash_tt.keys, replace, + entry->key, key, + HASH_PERFT_VAL(entry->data), nodes); + } else { + printf("NEW entry=%lu[%d] key=%lx val=%lu\n", + bucket - hash_tt.keys, replace, + entry->key, nodes); + } entry->key = key; entry->data = data; return entry; + } else { + printf("TT full, skip\n"); } return NULL; } + +void tt_stats() +{ + printf("TT: sz=%u bits=%u bcks=%'lu entries=%'lu mask=%10x" + "used=%lu hits/miss=%'lu/%'lu\n", + hash_tt.mb, hash_tt.nbits, hash_tt.nbuckets, hash_tt.nkeys, hash_tt.mask, + hash_tt.used_keys, hash_tt.hits, hash_tt.misses); + //printf("\tused=%lu hits/miss=%lu/%lu\n", + // hash_tt.used_keys, hash_tt.hits, hash_tt.misses); +} diff --git a/src/hash.h b/src/hash.h index fb5ecaf..2ebb145 100644 --- a/src/hash.h +++ b/src/hash.h @@ -18,7 +18,7 @@ #include "chessdefs.h" -#define NBUCKETS 4 /* buckets per hash table entry */ +#define ENTRIES_PER_BUCKET 4 /* buckets per hash table entry */ #define HASH_SIZE_DEFAULT 32 /* default: 32Mb */ #define HASH_SIZE_MIN 4 @@ -35,7 +35,7 @@ typedef u64 hkey_t; /* cannot use typedef for key_ * 16 bytes in future, it should be updated to be exactly 32 bytes. */ typedef struct { - hkey_t key; /* zobrist */ + hkey_t key; /* zobrist */ union { u64 data; struct { @@ -59,7 +59,7 @@ typedef struct { typedef struct { - hentry_t entry[NBUCKETS]; + hentry_t entry[ENTRIES_PER_BUCKET]; } bucket_t; typedef struct { @@ -78,9 +78,11 @@ typedef struct { u32 mask; /* nbuckets - 1, key mask */ /* stats - unsure about usage */ - size_t used_buckets; + //size_t used_buckets; size_t used_keys; u64 collisions; + u64 hits; + u64 misses; } hasht_t; /* hack: @@ -128,5 +130,6 @@ void tt_delete(void); hentry_t *tt_probe(hkey_t key); hentry_t *tt_probe_perft(const hkey_t key, const u16 depth); hentry_t *tt_store_perft(const hkey_t key, const u16 depth, const u64 nodes); +void tt_stats(void); #endif /* HASH_H */ diff --git a/src/move-do.c b/src/move-do.c index 465c0a8..f8979b8 100644 --- a/src/move-do.c +++ b/src/move-do.c @@ -108,7 +108,8 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) } } else if (is_enpassant(move)) { /* clear grabbed pawn */ square_t grabbed = to - up; - key ^= zobrist_pieces[pos->board[grabbed]][grabbed]; + piece_t pc = pos->board[grabbed]; + key ^= zobrist_pieces[pc][grabbed]; pos_clr_sq(pos, grabbed); } } diff --git a/src/position.c b/src/position.c index 53fd591..bb737ac 100644 --- a/src/position.c +++ b/src/position.c @@ -429,9 +429,10 @@ void pos_print(const pos_t *pos) char str[128]; board_print(pos->board); - printf("fen %s\n", pos2fen(pos, str)); - printf("checkers: %s\n", pos_checkers2str(pos, str, sizeof(str))); - printf("pinners : %s\n", pos_pinners2str(pos, str, sizeof(str))); + printf("key:%lx ", pos->key); + printf("fen: %s\n", pos2fen(pos, str)); + printf("checkers:%s ", pos_checkers2str(pos, str, sizeof(str))); + printf("pinners: %s ", pos_pinners2str(pos, str, sizeof(str))); printf("blockers: %s\n", pos_blockers2str(pos, str, sizeof(str))); } diff --git a/src/search.c b/src/search.c index 9cdd0f7..4718927 100644 --- a/src/search.c +++ b/src/search.c @@ -43,13 +43,16 @@ */ u64 perft(pos_t *pos, int depth, int ply, bool output) { - int subnodes; - u64 nodes = 0; + static movelist_t stack; + //int subnodes; + u64 subnodes, nodes = 0; movelist_t movelist; move_t *move, *last; state_t state; - movelist.nmoves = 0; + if (ply == 1) + stack.nmoves = 0; + pos_set_checkers_pinners_blockers(pos); pos_legal(pos, pos_gen_pseudo(pos, &movelist)); @@ -59,19 +62,46 @@ u64 perft(pos_t *pos, int depth, int ply, bool output) nodes++; } else { move_do(pos, *move, &state); + stack.move[stack.nmoves++] = *move; + if (ply == 2 && + //move_from(*move) == F7 && + //move_to(*move) == F5 && + move_from(stack.move[stack.nmoves-2]) == B2 && + move_to(stack.move[stack.nmoves-2]) == B4 && + move_from(stack.move[stack.nmoves-1]) == F7 && + move_to(stack.move[stack.nmoves-1]) == F5 + ) { + //&& pos->board[F5] == B_PAWN) { + moves_print(&stack, 0); + pos_print(pos); + } if (depth == 2) { movelist_t movelist2; pos_set_checkers_pinners_blockers(pos); subnodes = pos_legal(pos, pos_gen_pseudo(pos, &movelist2))->nmoves; } else { - subnodes = perft(pos, depth - 1, ply + 1, output); + hentry_t *entry; + //if (ply >= 4 && ply <= 8) { + if (ply == 4) { + if ((entry = tt_probe_perft(pos->key, depth))) { + subnodes = HASH_PERFT_VAL(entry->data); + printf("tt hit key=%lx ply=%d depth=%d nodes=%lu\n", + pos->key, ply, depth, subnodes); + } else { + subnodes = perft(pos, depth - 1, ply + 1, output); + tt_store_perft(pos->key, depth, subnodes); + } + } else { + subnodes = perft(pos, depth - 1, ply + 1, output); + } } if (output && ply == 1) { char movestr[8]; - printf("%s: %d\n", move_str(movestr, *move, 0), subnodes); + printf("%s: %lu\n", move_to_str(movestr, *move, 0), subnodes); } nodes += subnodes; move_undo(pos, *move, &state); + stack.nmoves--; } } @@ -99,7 +129,7 @@ u64 perft_alt(pos_t *pos, int depth, int ply, bool output) move_t *move, *last; state_t state; - movelist.nmoves = 0; + //movelist.nmoves = 0; pos_set_checkers_pinners_blockers(pos); state = pos->state; @@ -119,7 +149,7 @@ u64 perft_alt(pos_t *pos, int depth, int ply, bool output) } if (output && ply == 1) { char movestr[8]; - printf("%s: %d\n", move_str(movestr, *move, 0), subnodes); + printf("%s: %d\n", move_to_str(movestr, *move, 0), subnodes); } nodes += subnodes; move_undo_alt(pos, *move); diff --git a/test/common-test.h b/test/common-test.h index 8973c8f..ba01990 100644 --- a/test/common-test.h +++ b/test/common-test.h @@ -28,14 +28,30 @@ struct fentest { char *comment; char *fen; } fentest[] = { - /* - { __LINE__, 1, - "", - "" - }, - */ + /******************* TEMP TESTS BELOW *******************/ -/* ***************** TEMP TESTS ABOVE ************************** */ + /* + * { __LINE__, MOVEGEN | MOVEDO | PERFT, + * "bug perft TT après 1.b4 f5", + * "1nbqkbn1/ppp1p1pp/8/r1rpPpK1/1P6/8/P1PP1PPP/RNBQ1BNR w - f6 0 2" + * }, + */ + + /* + * { __LINE__, MOVEGEN | MOVEDO | PERFT, + * "bug perft TT après 1.b4", + * "1nbqkbn1/ppp1pppp/8/r1rpP1K1/1P6/8/P1PP1PPP/RNBQ1BNR b - - 0 1", + * }, + */ + + { __LINE__, MOVEGEN | MOVEDO | PERFT, + "bug perft TT", + "1nbqkbn1/ppp1pppp/8/r1rpP1K1/8/8/PPPP1PPP/RNBQ1BNR w - d6 0 1", + }, + + /* ***************** END of TEMP TESTS ******************/ + /* below line ignored if first test */ + { __LINE__, 0, NULL, NULL }, { __LINE__, MOVEGEN | MOVEDO | PERFT, "illegal white e.p.", @@ -138,7 +154,7 @@ struct fentest { "checker: h4", "4k3/8/8/8/7b/8/8/4K3 w - - 0 1" }, -// First game moves + // First game moves { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "startpos", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" @@ -425,6 +441,10 @@ static int fentest_cur = -1; static char *next_fen(uint module) { fentest_cur++; + + /* skip first entry if NULL - for special testing, see */ + if (fentest_cur == 0 && fentest[fentest_cur].fen == NULL) + fentest_cur++; while (fentest[fentest_cur].fen && !(fentest[fentest_cur].modules & module)) fentest_cur++; return fentest[fentest_cur].fen; diff --git a/test/perft-test.c b/test/perft-test.c index 87cfbe9..ef476f6 100644 --- a/test/perft-test.c +++ b/test/perft-test.c @@ -255,8 +255,8 @@ static __unused void compare_moves(movelist_t *fish, movelist_t *me) static int usage(char *prg) { - fprintf(stderr, "Usage: %s [-d depth] [-p pertf-modules] [-n][-v]\n", prg); - fprintf(stderr, "\t-d: depth, -p: 1-3, -n: no SF res check, -v: output moves\n"); + fprintf(stderr, "Usage: %s [-cmv][-d depth] [-p perft-version] \n", prg); + fprintf(stderr, "\t-c/m: print comments/moves, -n: no SF check, -d: depth, -p: 1-3, \n"); return 1; } @@ -264,6 +264,7 @@ int main(int ac, char**av) { int test_line; u64 sf_count = 0, my_count; + bool comment = false; char *fen; pos_t *pos = NULL, *fenpos; pos_t *fishpos = pos_new(); @@ -283,8 +284,11 @@ int main(int ac, char**av) int opt, depth = 6, run = 3; bool sf_run = true, perft_output = false; - while ((opt = getopt(ac, av, "vnd:p:")) != -1) { + while ((opt = getopt(ac, av, "cmnd:p:")) != -1) { switch (opt) { + case 'c': + comment = true; + break; case 'd': depth = atoi(optarg); break; @@ -294,7 +298,7 @@ int main(int ac, char**av) case 'n': sf_run = false; break; - case 'v': + case 'm': perft_output = true; break; default: @@ -315,7 +319,10 @@ int main(int ac, char**av) CLOCK_DEFINE(clock, CLOCK_MONOTONIC); while ((fen = next_fen(PERFT | MOVEDO))) { + if (comment) + printf("%s\n", *cur_comment()? cur_comment(): ""); test_line = cur_line(); + tt_clear(); if (!(fenpos = fen2pos(pos, fen))) { printf("wrong fen line = %d: [%s]\n", test_line, fen); continue; @@ -370,6 +377,7 @@ int main(int ac, char**av) printf("pt1 ERR: line=%3d sf=%'lu me=%'lu \"%s\"\n", test_line, sf_count, my_count, fen); } + tt_stats(); } if (run & 2) { diff --git a/test/tt-test.c b/test/tt-test.c new file mode 100644 index 0000000..2d1f112 --- /dev/null +++ b/test/tt-test.c @@ -0,0 +1,159 @@ +/* tt-test.c - transposition table test. + * + * Copyright (C) 2024 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 +#include +#include +#include +#include + +#include + +#include "chessdefs.h" +#include "fen.h" +#include "position.h" +#include "move.h" +#include "move-do.h" +#include "move-gen.h" +#include "search.h" + +// #include "common-test.h" + +static move_t move_in_movelist(movelist_t *ml, square_t from, square_t to, piece_type_t pt) +{ + const int nmoves = ml->nmoves; + const move_t *moves = ml->move; + int movenum = 0; + move_t move; + for (movenum = 0; movenum < nmoves; ++movenum) { + move = moves[movenum]; + printf("compare %s%s to %s%s pt=%d ", + sq_to_string(from), sq_to_string(to), + sq_to_string(move_from(move)), + sq_to_string(move_to(move)), + pt + ); + if (move_from(move) == from && move_to(move) == to) { + printf("HIT!\n"); + if (pt != NO_PIECE_TYPE && move_promoted(move) != pt) + continue; + printf("move_in_movelist(%s%s) found from=%s to=%s\n", + sq_to_string(from), sq_to_string(to), + sq_to_string(move_from(move)), + sq_to_string(move_to(move))); + return move; + } else + puts(""); + } + return MOVE_NONE; +} + +static move_t move_from_str(pos_t *pos, const char *move) +{ + movelist_t movelist; + square_t from = sq_from_string(move); + square_t to = sq_from_string(move + 2); + piece_type_t promoted = piece_t_from_char(*(move + 4)); + printf("from=%o to=%o promoted=%d\n", from, to, promoted); + + pos_set_checkers_pinners_blockers(pos); + pos_legal(pos, pos_gen_pseudo(pos, &movelist)); + return move_in_movelist(&movelist, from, to, promoted); +} + +static void pr_entry(hentry_t *entry) +{ + if (!entry) + printf("entry: NULL\n"); + else { + printf("entry: key=%lx depth=%d n=%lu\n", + entry->key, HASH_PERFT_DEPTH(entry->data), + HASH_PERFT_VAL(entry->data)); + } +} + +int main() +{ + pos_t *pos = NULL; + char *token, *str, buf[128]; + hentry_t *entry; + move_t move; + state_t state; + //movelist_t movelist; + + const char *moves_array[] = { + "e2e4 e7e5 g1f3 b8c6", + "e2e4 b8c6 g1f3 e7e5" + }; + + init_all(); + + for (uint i = 0; i < ARRAY_SIZE(moves_array); ++i) { + int depth = 0; + str = strdup(moves_array[i]); + printf("%2d: ", i + 1); + + pos = startpos(pos); + entry = tt_store_perft(pos->key, 0, 123 + depth); + pr_entry(entry); + token = strtok(str, " \t"); + while (token) { + depth++; + printf("%s ", token); + + //pos_set_checkers_pinners_blockers(pos); + //pos_legal(pos, pos_gen_pseudo(pos, &movelist)); + move = move_from_str(pos, token); + printf("move: %s\n", move_to_str(buf, move, 0)); + move_do(pos, move, &state); + if ((entry = tt_probe_perft(pos->key, depth))) { + printf("tt hit: depth=%d val=%lu", + HASH_PERFT_DEPTH(entry->data), + HASH_PERFT_VAL(entry->data)); + } else { + tt_store_perft(pos->key, i + 1, depth); + printf("tt store: depth=%d val=%lu", depth, (u64)i * 123); + }; + + token = strtok(NULL, " \t"); + } + printf("\n"); + free(str); + } + return 0; +} + +/* ccls bug report: https://github.com/emacs-lsp/emacs-ccls/issues/126 + */ +/* + * int called(int), caller(); + * + * /\** + * * called() - test ccls. + * * @x: int, the test value + * * + * * @called() description. + * * + * * @return: int, a very interesting value. + * *\/ + * int called(int x) { return x; } + * + * int caller() + * { + * int i = 0; + * called(int x) + * return i; + * } + */