diff --git a/Makefile b/Makefile index 9fc950e..3effa97 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ # SHELL := /bin/bash -#CC := gcc -CC := gcc-13 +CC := gcc +#CC := gcc-13 #CC := clang BEAR := bear TOUCH := touch @@ -69,8 +69,9 @@ CPPFLAGS += -DBUG_ON # brlib bug.h # fen.c #CPPFLAGS += -DDEBUG_FEN # FEN decoding -# hash.c -#CPPFLAGS += -HASH_VERIFY # chk zobrist consistency +# hash / TT +#CPPFLAGS += -DZOBRIST_VERIFY # double chk zobrist +#CPPFLAGS += -DPERFT_MOVE_HISTORY # perft, keep prev moves # attack.c #CPPFLAGS += -DDEBUG_ATTACK_ATTACKERS # sq_attackers @@ -339,7 +340,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 +350,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 +361,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 +397,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/board.c b/src/board.c index 8acfc4d..96b5c8a 100644 --- a/src/board.c +++ b/src/board.c @@ -14,7 +14,8 @@ #include #include -#include "brlib.h" +#include + #include "board.h" #include "bitboard.h" diff --git a/src/chessdefs.h b/src/chessdefs.h index c531ed4..03658d5 100644 --- a/src/chessdefs.h +++ b/src/chessdefs.h @@ -16,8 +16,8 @@ #include "brlib.h" /* brlib types */ -#define ONE 1ull -#define U64(const_u64) const_u64##ULL +#define ONE 1ul +#define U64(const_u64) const_u64##UL #define BIT(i) ( (u64) (ONE << (i)) ) #define BOARDSIZE (8*8) @@ -176,7 +176,7 @@ s64 clock_elapsed_μs(mclock_t *clock); s64 clock_elapsed_ms(mclock_t *clock); double clock_elapsed_sec(mclock_t *clock); -#define RAND_SEED_DEFAULT U64(1) +#define RAND_SEED_DEFAULT U64(0xb0d1ccea) void rand_init(u64 seed); u64 rand64(void); diff --git a/src/hash.c b/src/hash.c index 5f3756e..6615ba4 100644 --- a/src/hash.c +++ b/src/hash.c @@ -12,6 +12,7 @@ */ #include +#include #include #include @@ -99,10 +100,17 @@ hkey_t zobrist_calc(pos_t *pos) * @return: True if Zobrist key is OK. */ #ifdef ZOBRIST_VERIFY + +#pragma push_macro("BUG_ON") /* force BUG_ON and WARN_ON */ +#pragma push_macro("WARN_ON") +#undef BUG_ON +#define BUG_ON +#undef WARN_ON +#define WARN_ON + bool zobrist_verify(pos_t *pos) { - key_t diff, key = zobrist_calc(pos); - + hkey_t diff, key = zobrist_calc(pos); if (pos->key == key) return true; @@ -120,17 +128,19 @@ bool zobrist_verify(pos_t *pos) goto end; } } - for (castle_rights_t c = CASTLE_NONE; c <= CASTLE_ALL; ++c) + for (castle_rights_t c = CASTLE_NONE; c <= CASTLE_ALL; ++c) { if (diff == zobrist_castling[c]) { warn(true, "zobrist difference is castling:[%d]\n", c); goto end; } + } - for (file_t f = FILE_A; f <= FILE_H; ++f) + for (file_t f = FILE_A; f <= FILE_H; ++f) { if (diff == zobrist_ep[f]) { warn(true, "zobrist difference is ep:[%d]\n", f); goto end; } + } if (diff == zobrist_turn) { warn(true, "zobrist difference is turn\n"); goto end; @@ -138,11 +148,16 @@ bool zobrist_verify(pos_t *pos) warn(true, "zobrist diff %lx is unknown\n", diff); end: bug_on(false); + /* not reached */ + return true; } +#pragma pop_macro("WARN_ON") +#pragma pop_macro("BUG_ON") + #endif /** - * hash_create() - hashtable creation. + * tt_create() - create transposition table * @sizemb: s32 size of hash table in Mb * * Create a hash table of max @sizemb (or HASH_SIZE_MBif @sizemb <= 0) Mb size. @@ -164,7 +179,7 @@ end: * @return: hash table size in Mb. If memory allocation fails, the function does * not return. */ -int hash_create(s32 sizemb) +int tt_create(s32 sizemb) { size_t bytes, target_nbuckets; u32 nbits; @@ -185,12 +200,12 @@ int hash_create(s32 sizemb) if (hash_tt.nbits != nbits) { if (hash_tt.nbits) - hash_delete(); + tt_delete(); 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; @@ -207,34 +222,202 @@ int hash_create(s32 sizemb) // printf("unchanged (cleared)\n"); //} /* attention - may fail ! */ - hash_clear(); + tt_clear(); return hash_tt.nbits; } /** - * hash_clear() - clear hashtable data. + * tt_clear() - clear transposition table * * Reset hashtable entries (if available) and statistic information. */ -void hash_clear() +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; } /** - * hash_delete() - delete hashtable data. + * tt_delete() - delete transposition table * * free hashtable data. */ -void hash_delete() +void tt_delete() { - if (hash_tt.keys) + if (hash_tt.keys) { safe_free(hash_tt.keys); - memset(&hash_tt, 0, sizeof(hash_tt)); + hash_tt.keys = NULL; + } + tt_clear(); +} + +/** + * tt_probe() - probe tt for an entry + * + * + */ +hentry_t *tt_probe(hkey_t key) +{ + bucket_t *bucket; + hentry_t *entry; + int i; + + bug_on(!hash_tt.keys); + bucket = hash_tt.keys + (key & hash_tt.mask); + + /* find key in buckets */ + for (i = 0; i < ENTRIES_PER_BUCKET; ++i) { + entry = bucket->entry + i; + if (key == entry->key) + break; + } + if (i < ENTRIES_PER_BUCKET) + return entry; + return NULL; +} + +/** + * tt_probe_perft() - probe tt for an entry (perft version) + * @key: Zobrist (hkey_t) key + * @depth: depth from search root + * + * Search transposition for @key entry with @depth depth. + * + * @return: @hentry_t address is found, TT_MISS otherwise. + */ +hentry_t *tt_probe_perft(const hkey_t key, const u16 depth) +{ + bucket_t *bucket; + hentry_t *entry; + int i; + + bug_on(!hash_tt.keys); + bucket = hash_tt.keys + (key & hash_tt.mask); + + /* find key in buckets */ + for (i = 0; i < ENTRIES_PER_BUCKET; ++i) { + entry = bucket->entry + i; + if (key == entry->key && HASH_PERFT_DEPTH(entry->data) == depth) { + hash_tt.hits++; + /* + * printf("tt hit: key=%lx depth=%d bucket=%lu entry=%d!\n", + * key, depth, bucket - hash_tt.keys, i); + */ + return entry; + } + } + /* + * printf("tt miss: key=%lx depth=%d ucket=%lu\n", + * key, depth, bucket - hash_tt.keys); + */ + hash_tt.misses++; + return TT_MISS; +} + +/** + * tt_store_perft() - store a transposition table entry (perft version) + * @key: Zobrist (hkey_t) key + * @depth: depth from search root + * @nodes: value to store + * + */ +hentry_t *tt_store_perft(const hkey_t key, const u16 depth, const u64 nodes) +{ + bucket_t *bucket; + hentry_t *entry; + int replace = -1; + 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 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 (int i = 0; i < ENTRIES_PER_BUCKET; ++i) { + entry = bucket->entry + i; + //if (!entry->key) { + // replace = i; + //hash_tt.used_keys++; + // break; + //} + if (key == entry->key) { + if (depth == HASH_PERFT_DEPTH(entry->data)) { + printf("tt_store: dup key=%lx depth=%d, this should not happen!\n", + key, depth); + return NULL; + } + } + /* always keep higher nodes */ + if (HASH_PERFT_DEPTH(entry->data) < mindepth) { + mindepth = HASH_PERFT_DEPTH(entry->data); + replace = i; + } + } + + if (replace >= 0) { + entry = bucket->entry + replace; + + hash_tt.used_keys += entry->key == 0; + hash_tt.collisions += entry->key && (key != entry->key); + /* + * 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; +} + +/** + * tt_info() - print hash-table information. + */ +void tt_info() +{ + if (hash_tt.keys) { + printf("TT: Mb:%d buckets:%'lu (bits:%u mask:%#x) entries:%'lu\n", + hash_tt.mb, hash_tt.nbuckets, hash_tt.nbits, + hash_tt.mask, hash_tt.nkeys); + } else { + printf("TT: not set.\n"); + } +} + +/** + * tt_stats() - print hash-table usage. + */ +void tt_stats() +{ + if (hash_tt.keys) { + float percent = 100.0 * hash_tt.used_keys / hash_tt.nkeys; + printf("hash: used:%'lu/%'lu (%.2f%%) hit:%'lu miss:%'lu coll:%'lu\n", + hash_tt.used_keys, hash_tt.nkeys, percent, + hash_tt.hits, hash_tt.misses, + hash_tt.collisions); + } else { + printf("hash: not set.\n"); + } } diff --git a/src/hash.h b/src/hash.h index a270c47..c57eed8 100644 --- a/src/hash.h +++ b/src/hash.h @@ -14,16 +14,20 @@ #ifndef HASH_H #define HASH_H -#include +#include #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 #define HASH_SIZE_MAX 32768 /* 32Gb */ +#define TT_MISS NULL +#define TT_DUP (void *) U64(0x01) +#define TT_OK(p) ((p) > (void *)U64(0xF)) + typedef u64 hkey_t; /* cannot use typedef for key_t */ /** @@ -33,7 +37,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 { @@ -46,8 +50,18 @@ typedef struct { }; } hentry_t; +/* hentry perft data: + * 0-47: perft value + * 48-63: depth + */ +#define HASH_PERFT_MASK U64(0xffffffffffff) +#define HASH_PERFT(depth, val) ((((u64) depth) << 48) | ((val) & HASH_PERFT_MASK)) +#define HASH_PERFT_VAL(data) ((data) & HASH_PERFT_MASK) +#define HASH_PERFT_DEPTH(data) ((u16)((data) >> 48)) + + typedef struct { - hentry_t buckets[NBUCKETS]; + hentry_t entry[ENTRIES_PER_BUCKET]; } bucket_t; typedef struct { @@ -66,9 +80,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: @@ -79,7 +95,7 @@ typedef struct { * we use the formula: * idx = ( ( ep & SQUARE_NONE ) >> 3 ) | sq_file(ep); */ -#define EP_ZOBRIST_IDX(ep) ( ( (ep) >> 3 ) | sq_file(ep) ) +#define EP_ZOBRIST_IDX(ep) ( ( ( ep & SQUARE_NONE ) >> 3 ) | sq_file(ep) ) extern hkey_t zobrist_pieces[16][64]; extern hkey_t zobrist_castling[4 * 4 + 1]; @@ -97,8 +113,26 @@ bool zobrist_verify(pos_t *pos); #define zobrist_verify(p) true #endif -int hash_create(int Mb); -void hash_clear(void); -void hash_delete(void); +/** + * tt_prefetch() - prefetch hash table entry + * @hash: u64 key + * + * Prefetch memory for @key. + */ +static inline void tt_prefetch(hkey_t key) +{ + bug_on(!hash_tt.keys); + __builtin_prefetch(hash_tt.keys + (key & hash_tt.mask)); +} + +int tt_create(int Mb); +void tt_clear(void); +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_info(void); +void tt_stats(void); #endif /* HASH_H */ diff --git a/src/init.c b/src/init.c index 175594e..f62df6b 100644 --- a/src/init.c +++ b/src/init.c @@ -37,6 +37,6 @@ void init_all(void) /* zobrist tables & default tt hashtable */ zobrist_init(); - hash_create(HASH_SIZE_DEFAULT); + tt_create(HASH_SIZE_DEFAULT); } diff --git a/src/move-do.c b/src/move-do.c index cc4c333..7f36bf7 100644 --- a/src/move-do.c +++ b/src/move-do.c @@ -47,120 +47,8 @@ * * @return: updated pos. */ -pos_t *move_do(pos_t *pos, const move_t move) //, state_t *state) +pos_t *move_do(pos_t *pos, const move_t move, state_t *state) { -//# ifdef DEBUG_MOVE_DO -// move_print(move, M_PR_NL | M_PR_LONG); -//# endif - //*state = pos->state; /* save irreversible changes */ - - color_t us = pos->turn, them = OPPONENT(us); - square_t from = move_from(move), to = move_to(move); - piece_t piece = pos->board[from]; - piece_t captured = pos->board[to]; - piece_type_t ptype = PIECE(piece); - piece_t new_piece = piece; - int up = sq_up(us); - hkey_t key = pos->key; - - /* 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->plycount; - pos->en_passant = SQUARE_NONE; - pos->turn = them; - pos->captured = captured; - - bug_on(COLOR(piece) != us); - - if (is_promotion(move)) { - bug_on(sq_rank(to) != sq_rel_rank(RANK_8, us)); - new_piece = MAKE_PIECE(move_promoted(move), us); - } - - if (captured != EMPTY) { - pos->clock_50 = 0; - //pos->captured = pos->board[to]; /* save capture info */ - bug_on(pos->board[to] == EMPTY || COLOR(pos->captured) != them); - key ^= zobrist_pieces[captured][to]; - pos_clr_sq(pos, to); /* clear square */ - } else if (is_castle(move)) { /* handle rook move */ - square_t rookfrom, rookto; - if (is_castle_K(move)) { - rookfrom = sq_rel(H1, us); - rookto = sq_rel(F1, us); - } else { - rookfrom = sq_rel(A1, us); - rookto = sq_rel(D1, us); - } - key ^= zobrist_pieces[pos->board[rookfrom]][rookto] ^ - zobrist_pieces[pos->board[rookfrom]][rookfrom]; - pos_set_sq(pos, rookto, pos->board[rookfrom]); - 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; - if (is_dpush(move)) { /* if pawn double push, set e.p. */ - pos->en_passant = from + up; - /* update ep key */ - key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; - } else if (is_enpassant(move)) { /* clear grabbed pawn */ - square_t grabbed = to - up; - key ^= zobrist_pieces[pos->board[grabbed]][grabbed]; - pos_clr_sq(pos, grabbed); - } - } - - key ^= zobrist_pieces[piece][from] ^ zobrist_pieces[new_piece][to]; - pos_clr_sq(pos, from); /* clear "from" and set "to" */ - pos_set_sq(pos, to, new_piece); - - if (ptype == KING) - pos->king[us] = to; - - /* update castling flags - * As we always consider flags are valid, we : - * - adjust our flags if relative from is "E1", "A1", H1" - * - adjust opp flags if relative to if "A8", H8" - */ - if (can_castle(pos->castle, us)) { /* do we save time with this test ? */ - square_t rel_e1 = sq_rel(E1, us); - square_t rel_a1 = sq_rel(A1, us); - square_t rel_h1 = sq_rel(H1, us); - if (from == rel_e1) - pos->castle = clr_castle(pos->castle, us); - else if (from == rel_a1) - pos->castle = clr_ooo(pos->castle, us); - else if (from == rel_h1) - pos->castle = clr_oo(pos->castle, us); - } - if (can_castle(pos->castle, them)) { - square_t rel_a8 = sq_rel(A8, us); - square_t rel_h8 = sq_rel(H8, us); - if (to == rel_a8) - pos->castle = clr_ooo(pos->castle, them); - else if (to == rel_h8) - pos->castle = clr_oo(pos->castle, them); - } - - /* update castling rights key */ - key ^= zobrist_castling[pos->castle]; - - pos->key = key; - - bug_on(zobrist_verify(pos) == false); - - return pos; -} - -pos_t *move_do2(pos_t *pos, const move_t move, state_t *state) -{ -//# ifdef DEBUG_MOVE_DO -// move_print(move, M_PR_NL | M_PR_LONG); -//# endif color_t us = pos->turn, them = OPPONENT(us); square_t from = move_from(move), to = move_to(move); piece_t piece = pos->board[from]; @@ -205,26 +93,28 @@ pos_t *move_do2(pos_t *pos, const move_t move, state_t *state) rookfrom = sq_rel(A1, us); rookto = sq_rel(D1, us); } - key ^= zobrist_pieces[pos->board[rookfrom]][rookto]; - key ^= zobrist_pieces[pos->board[rookfrom]][rookfrom]; + key ^= zobrist_pieces[pos->board[rookfrom]][rookto] ^ + zobrist_pieces[pos->board[rookfrom]][rookfrom]; pos_set_sq(pos, rookto, pos->board[rookfrom]); 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; - if (is_dpush(move)) { /* if pawn double push, set e.p. */ - pos->en_passant = from + up; - /* update key */ - key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; + 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]) { + pos->en_passant = ep; + key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; + } } 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); } } - key ^= zobrist_pieces[piece][from]; - key ^= zobrist_pieces[new_piece][to]; + key ^= zobrist_pieces[piece][from] ^ zobrist_pieces[new_piece][to]; pos_clr_sq(pos, from); /* clear "from" and set "to" */ pos_set_sq(pos, to, new_piece); @@ -256,12 +146,12 @@ pos_t *move_do2(pos_t *pos, const move_t move, state_t *state) pos->castle = clr_oo(pos->castle, them); } - /* update castle key */ + /* update castling rights key */ key ^= zobrist_castling[pos->castle]; pos->key = key; - bug_on(zobrist_verify(pos) == false); + zobrist_verify(pos); return pos; } @@ -283,46 +173,158 @@ pos_t *move_do2(pos_t *pos, const move_t move, state_t *state) * * @return: pos. */ -pos_t *move_undo(pos_t *pos, const move_t move) -{ - color_t them = pos->turn, us = OPPONENT(them); - square_t from = move_from(move), to = move_to(move); - piece_t piece = pos->board[to]; - int up = sq_up(them); - - if (is_promotion(move)) - piece = MAKE_PIECE(PAWN, us); - - pos_clr_sq(pos, to); /* always clear "to" ... */ - pos_set_sq(pos, from, piece); /* ... and set "from" */ - - if (PIECE(piece) == KING) - pos->king[us] = from; - - if (pos->captured != EMPTY) { - pos_set_sq(pos, to, pos->captured); /* restore captured piece */ - } else if (is_castle(move)) { /* make reverse rook move */ - square_t rookfrom, rookto; - if (is_castle_K(move)) { - rookfrom = sq_rel(F1, us); - rookto = sq_rel(H1, us); - } else { - rookfrom = sq_rel(D1, us); - rookto = sq_rel(A1, us); - } - pos_set_sq(pos, rookto, pos->board[rookfrom]); - pos_clr_sq(pos, rookfrom); - } else if (is_enpassant(move)) { /* restore grabbed pawn */ - square_t grabbed = to + up; - pos_set_sq(pos, grabbed, MAKE_PIECE(PAWN, them)); - } - - //pos->state = *state; /* restore irreversible changes */ - pos->turn = us; - return pos; -} - -pos_t *move_undo2(pos_t *pos, const move_t move, const state_t *state) +pos_t *move_undo(pos_t *pos, const move_t move, const state_t *state) +{ + color_t them = pos->turn, us = OPPONENT(them); + square_t from = move_from(move), to = move_to(move); + piece_t piece = pos->board[to]; + int up = sq_up(them); + + if (is_promotion(move)) + piece = MAKE_PIECE(PAWN, us); + + pos_clr_sq(pos, to); /* always clear "to" ... */ + pos_set_sq(pos, from, piece); /* ... and set "from" */ + + if (PIECE(piece) == KING) + pos->king[us] = from; + + if (pos->captured != EMPTY) { + pos_set_sq(pos, to, pos->captured); /* restore captured piece */ + } else if (is_castle(move)) { /* make reverse rook move */ + square_t rookfrom, rookto; + if (is_castle_K(move)) { + rookfrom = sq_rel(F1, us); + rookto = sq_rel(H1, us); + } else { + rookfrom = sq_rel(D1, us); + rookto = sq_rel(A1, us); + } + pos_set_sq(pos, rookto, pos->board[rookfrom]); + pos_clr_sq(pos, rookfrom); + } else if (is_enpassant(move)) { /* restore grabbed pawn */ + square_t grabbed = to + up; + pos_set_sq(pos, grabbed, MAKE_PIECE(PAWN, them)); + } + + pos->state = *state; /* restore irreversible changes */ + pos->turn = us; + return pos; +} + +/** + * move_{do,undo}_alt - alternative move_do/move_undo (to experiment) + */ +pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state) +{ + color_t us = pos->turn, them = OPPONENT(us); + square_t from = move_from(move), to = move_to(move); + piece_t piece = pos->board[from]; + piece_t captured = pos->board[to]; + piece_type_t ptype = PIECE(piece); + piece_t new_piece = piece; + int up = sq_up(us); + hkey_t key = pos->key; + + *state = pos->state; /* save irreversible changes */ + + /* 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->plycount; + pos->en_passant = SQUARE_NONE; + pos->turn = them; + pos->captured = captured; + + bug_on(COLOR(piece) != us); + + if (is_promotion(move)) { + bug_on(sq_rank(to) != sq_rel_rank(RANK_8, us)); + new_piece = MAKE_PIECE(move_promoted(move), us); + } + + if (captured != EMPTY) { + pos->clock_50 = 0; + //pos->captured = pos->board[to]; /* save capture info */ + bug_on(pos->board[to] == EMPTY || COLOR(pos->captured) != them); + key ^= zobrist_pieces[captured][to]; + pos_clr_sq(pos, to); /* clear square */ + } else if (is_castle(move)) { /* handle rook move */ + square_t rookfrom, rookto; + if (is_castle_K(move)) { + rookfrom = sq_rel(H1, us); + rookto = sq_rel(F1, us); + } else { + rookfrom = sq_rel(A1, us); + rookto = sq_rel(D1, us); + } + key ^= zobrist_pieces[pos->board[rookfrom]][rookto] ^ + zobrist_pieces[pos->board[rookfrom]][rookfrom]; + pos_set_sq(pos, rookto, pos->board[rookfrom]); + 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; + 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]) { + pos->en_passant = ep; + key ^= zobrist_ep[EP_ZOBRIST_IDX(pos->en_passant)]; + } + } else if (is_enpassant(move)) { /* clear grabbed pawn */ + square_t grabbed = to - up; + piece_t pc = pos->board[grabbed]; + key ^= zobrist_pieces[pc][grabbed]; + pos_clr_sq(pos, grabbed); + } + } + + key ^= zobrist_pieces[piece][from] ^ zobrist_pieces[new_piece][to]; + pos_clr_sq(pos, from); /* clear "from" and set "to" */ + pos_set_sq(pos, to, new_piece); + + if (ptype == KING) + pos->king[us] = to; + + /* update castling flags + * As we always consider flags are valid, we : + * - adjust our flags if relative from is "E1", "A1", H1" + * - adjust opp flags if relative to if "A8", H8" + */ + if (can_castle(pos->castle, us)) { /* do we save time with this test ? */ + square_t rel_e1 = sq_rel(E1, us); + square_t rel_a1 = sq_rel(A1, us); + square_t rel_h1 = sq_rel(H1, us); + if (from == rel_e1) + pos->castle = clr_castle(pos->castle, us); + else if (from == rel_a1) + pos->castle = clr_ooo(pos->castle, us); + else if (from == rel_h1) + pos->castle = clr_oo(pos->castle, us); + } + if (can_castle(pos->castle, them)) { + square_t rel_a8 = sq_rel(A8, us); + square_t rel_h8 = sq_rel(H8, us); + if (to == rel_a8) + pos->castle = clr_ooo(pos->castle, them); + else if (to == rel_h8) + pos->castle = clr_oo(pos->castle, them); + } + + /* update castling rights key */ + key ^= zobrist_castling[pos->castle]; + + pos->key = key; + + zobrist_verify(pos); + + return pos; +} + +pos_t *move_undo_alt(pos_t *pos, const move_t move, const state_t *state) { color_t them = pos->turn, us = OPPONENT(them); square_t from = move_from(move), to = move_to(move); diff --git a/src/move-do.h b/src/move-do.h index fb2dbce..17ca7dd 100644 --- a/src/move-do.h +++ b/src/move-do.h @@ -16,10 +16,11 @@ #include "position.h" -pos_t *move_do(pos_t *pos, const move_t move);//, state_t *state); -pos_t *move_undo(pos_t *pos, const move_t move);//, const state_t *state); +pos_t *move_do(pos_t *pos, const move_t move, state_t *state); +pos_t *move_undo(pos_t *pos, const move_t move, const state_t *state); -pos_t *move_do2(pos_t *pos, const move_t move, state_t *state); -pos_t *move_undo2(pos_t *pos, const move_t move, const state_t *state); +/* new version testing */ +pos_t *move_do_alt(pos_t *pos, const move_t move, state_t *state); +pos_t *move_undo_alt(pos_t *pos, const move_t move, const state_t *state); #endif /* MOVE_DO_H */ diff --git a/src/move-gen.c b/src/move-gen.c index e6bef55..0a76655 100644 --- a/src/move-gen.c +++ b/src/move-gen.c @@ -353,19 +353,12 @@ movelist_t *pos_gen_pseudo(pos_t *pos, movelist_t *movelist) bitboard_t from_bb, to_bb; bitboard_t tmp_bb; move_t *moves = movelist->move; - //int *nmoves = &movelist->nmoves; square_t from, to; square_t king = pos->king[us]; - //*nmoves = 0; - /* king - MUST BE FIRST */ to_bb = bb_king_moves(dest_squares, king); moves = moves_gen(moves, king, to_bb); - //while(to_bb) { - // to = bb_next(&to_bb); - // *moves++ = move_make(king, to); - //} if (bb_multiple(pos->checkers)) /* double check, we stop here */ goto finish; @@ -382,8 +375,6 @@ movelist_t *pos_gen_pseudo(pos_t *pos, movelist_t *movelist) * Attention ! Castling flags are assumed correct */ bitboard_t rel_rank1 = bb_rel_rank(RANK_1, us); - //square_t from_square[2] = { E1, E8 }; /* verify king is on E1/E8 */ - //bug_on(can_castle(pos->castle, us) && from != from_square[us]); /* For castle, we check the opponent attacks on squares between from and to. * To square attack check will be done in gen_is_legal. */ @@ -400,26 +391,19 @@ movelist_t *pos_gen_pseudo(pos_t *pos, movelist_t *movelist) } } } + /* sliding pieces */ from_bb = pos->bb[us][BISHOP] | pos->bb[us][QUEEN]; while (from_bb) { from = bb_next(&from_bb); to_bb = hyperbola_bishop_moves(occ, from) & dest_squares; moves = moves_gen(moves, from, to_bb); - //while(to_bb) { - // to = bb_next(&to_bb); - // *moves++ = move_make(from, to); - //} } from_bb = pos->bb[us][ROOK] | pos->bb[us][QUEEN]; while (from_bb) { from = bb_next(&from_bb); to_bb = hyperbola_rook_moves(occ, from) & dest_squares; moves = moves_gen(moves, from, to_bb); - //while(to_bb) { - // to = bb_next(&to_bb); - // *moves++ = move_make(from, to); - //} } /* knight */ @@ -428,10 +412,6 @@ movelist_t *pos_gen_pseudo(pos_t *pos, movelist_t *movelist) from = bb_next(&from_bb); to_bb = bb_knight_moves(dest_squares, from); moves = moves_gen(moves, from, to_bb); - //while(to_bb) { - // to = bb_next(&to_bb); - // *moves++ = move_make(from, to); - //} } /* pawn: relative rank and files */ @@ -460,12 +440,11 @@ movelist_t *pos_gen_pseudo(pos_t *pos, movelist_t *movelist) while(to_bb) { to = bb_next(&to_bb); from = to - shift - shift; - *moves++ = move_make_flags(from, to, M_DPUSH); + *moves++ = move_make(from, to); } /* pawn: captures */ tmp_bb = bb_pawns_attacks(pos->bb[us][PAWN], shift) & enemy_pieces; - //bb_print("FAIL", tmp_bb); to_bb = tmp_bb & ~rel_rank8; while (to_bb) { to = bb_next(&to_bb); diff --git a/src/move.c b/src/move.c index bad6375..a8a094f 100644 --- a/src/move.c +++ b/src/move.c @@ -89,7 +89,7 @@ */ /** - * move_str() - get a move string + * move_to_str() - get a move string * @dst: destination memory * @move: move * @flags: moves selection and display options. @@ -102,7 +102,7 @@ * M_PR_NL: print a newline after move * M_PR_EVAL: print move eval */ -char *move_str(char *dst, const move_t move, __unused const int flags) +char *move_to_str(char *dst, const move_t move, __unused const int flags) { square_t from = move_from(move); square_t to = move_to(move); @@ -116,7 +116,6 @@ char *move_str(char *dst, const move_t move, __unused const int flags) return dst; } - /** * moves_print() - print movelist moves. * @moves: &movelist_t moves list @@ -135,7 +134,7 @@ void moves_print(movelist_t *moves, __unused int flags) char str[16]; //printf("%2d:", moves->nmoves); for (int m = 0; m < moves->nmoves; ++m) - printf("%s ", move_str(str, moves->move[m], flags)); + printf("%s ", move_to_str(str, moves->move[m], flags)); printf("\n"); } diff --git a/src/move.h b/src/move.h index 368fdbb..0d7f33e 100644 --- a/src/move.h +++ b/src/move.h @@ -42,30 +42,30 @@ enum { M_OFF_FROM = 0, M_OFF_TO = 6, M_OFF_PROMOTED = 12, - M_OFF_CAPTURED = 15, - M_OFF_FLAGS = 18 +// M_OFF_CAPTURED = 15, + M_OFF_FLAGS = 15 }; typedef enum { - M_CAPTURE = BIT(M_OFF_FLAGS + 0), - M_ENPASSANT = BIT(M_OFF_FLAGS + 1), - M_PROMOTION = BIT(M_OFF_FLAGS + 2), - M_CASTLE_K = BIT(M_OFF_FLAGS + 3), /* maybe only one ? */ - M_CASTLE_Q = BIT(M_OFF_FLAGS + 5), /* maybe only one ? */ - M_CHECK = BIT(M_OFF_FLAGS + 6), /* maybe unknown/useless ? */ - M_DPUSH = BIT(M_OFF_FLAGS + 7) /* pawn double push */ + M_PROMOTION = 070000, +// M_CAPTURE = BIT(M_OFF_FLAGS + 0), + M_ENPASSANT = BIT(M_OFF_FLAGS + 0), + M_CASTLE_K = BIT(M_OFF_FLAGS + 1), /* maybe only one ? */ + M_CASTLE_Q = BIT(M_OFF_FLAGS + 2), /* maybe only one ? */ + M_CHECK = BIT(M_OFF_FLAGS + 3), /* maybe unknown/useless ? */ +// M_DPUSH = BIT(M_OFF_FLAGS + 7) /* pawn double push */ } move_flags_t; #define move_set_flags(move, flags) ((move) | (flags)) -#define is_capture(m) ((m) & M_CAPTURE) +//#define is_capture(m) ((m) & M_CAPTURE) #define is_enpassant(m) ((m) & M_ENPASSANT) #define is_promotion(m) ((m) & M_PROMOTION) #define is_castle(m) ((m) & (M_CASTLE_K | M_CASTLE_Q)) #define is_castle_K(m) ((m) & M_CASTLE_K) #define is_castle_Q(m) ((m) & M_CASTLE_Q) #define is_check(m) ((m) & M_CHECK) -#define is_dpush(m) ((m) & M_DPUSH) +//#define is_dpush(m) ((m) & M_DPUSH) #define MOVES_MAX 256 @@ -89,10 +89,12 @@ static inline piece_type_t move_promoted(move_t move) return (move >> M_OFF_PROMOTED) & 07; } -static inline piece_type_t move_captured(move_t move) -{ - return (move >> M_OFF_CAPTURED) & 07; -} +/* + * static inline piece_type_t move_captured(move_t move) + * { + * return (move >> M_OFF_CAPTURED) & 07; + * } + */ static inline move_t move_make(square_t from, square_t to) { @@ -105,10 +107,12 @@ static inline move_t move_make_flags(square_t from, square_t to, move_flags_t fl //move_set_flags(move_make(from, to), flags); } -static inline move_t move_make_capture(square_t from, square_t to) -{ - return move_make_flags(from, to, M_CAPTURE); -} +/* + * static inline move_t move_make_capture(square_t from, square_t to) + * { + * return move_make_flags(from, to, M_CAPTURE); + * } + */ static inline move_t move_make_enpassant(square_t from, square_t to) { @@ -118,19 +122,23 @@ static inline move_t move_make_enpassant(square_t from, square_t to) static inline move_t move_make_promote(square_t from, square_t to, piece_type_t promoted) { - return move_make_flags(from, to, M_PROMOTION) | (promoted << M_OFF_PROMOTED); + return move_make(from, to) | (promoted << M_OFF_PROMOTED); } -static inline move_t move_make_promote_capture(square_t from, square_t to, - piece_type_t promoted) -{ - return move_make_promote(from, to, promoted) | M_CAPTURE; -} +/* + * static inline move_t move_make_promote_capture(square_t from, square_t to, + * piece_type_t promoted) + * { + * return move_make_promote(from, to, promoted) | M_CAPTURE; + * } + */ -static inline move_t move_set_captured(move_t move, piece_type_t captured) -{ - return move | (captured << M_OFF_CAPTURED); -} +/* + * static inline move_t move_set_captured(move_t move, piece_type_t captured) + * { + * return move | (captured << M_OFF_CAPTURED); + * } + */ /* moves_print flags */ @@ -144,7 +152,7 @@ static inline move_t move_set_captured(move_t move, piece_type_t captured) #define M_PR_LONG 0x80 //int move_print(int movenum, move_t *move, move_flags_t flags); -char *move_str(char *dst, const move_t move, __unused const int flags); +char *move_to_str(char *dst, const move_t move, __unused const int flags); void moves_print(movelist_t *moves, int flags); void move_sort_by_sq(movelist_t *moves); diff --git a/src/piece.c b/src/piece.c index e00f9d5..96c3994 100644 --- a/src/piece.c +++ b/src/piece.c @@ -78,8 +78,12 @@ char *piece_to_name(piece_t p) piece_type_t piece_t_from_char(char c) { - char *p = strchr(pieces_str, c); - return p? (p - pieces_str) % 6 + 1: NO_PIECE_TYPE; + char *p; + piece_type_t pt = NO_PIECE_TYPE; + if (c && (p = strchr(pieces_str, c))) { + pt = (p - pieces_str) % 6 + 1; + } + return pt; } //piece_type_t piece_from_promotion(char c, color_t color) 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 9fd90cf..411272b 100644 --- a/src/search.c +++ b/src/search.c @@ -23,9 +23,10 @@ /** * perft() - Perform perft on position - * @pos: &position to search - * @depth: Wanted depth. - * @ply: perft depth level. + * @pos: &position to search + * @depth: Wanted depth. + * @ply: current perft depth level (root = 1) + * @output: 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. @@ -43,15 +44,17 @@ */ u64 perft(pos_t *pos, int depth, int ply, bool output) { - int subnodes; - u64 nodes = 0; + u64 subnodes, nodes = 0; movelist_t movelist; move_t *move, *last; state_t state; +# ifdef PERFT_MOVE_HISTORY + static movelist_t stack; + if (ply == 1) + stack.nmoves = 0; +# endif - movelist.nmoves = 0; pos_set_checkers_pinners_blockers(pos); - state = pos->state; pos_legal(pos, pos_gen_pseudo(pos, &movelist)); last = movelist.move + movelist.nmoves; @@ -59,21 +62,34 @@ u64 perft(pos_t *pos, int depth, int ply, bool output) if (depth == 1) { nodes++; } else { - move_do(pos, *move); + move_do(pos, *move, &state); +# ifdef PERFT_MOVE_HISTORY + stack.move[stack.nmoves++] = *move; +# endif 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 >= 4) { + 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, 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); - pos->state = state; + move_undo(pos, *move, &state); +# ifdef PERFT_MOVE_HISTORY + stack.nmoves--; +# endif } } @@ -83,25 +99,24 @@ u64 perft(pos_t *pos, int depth, int ply, bool output) } /** - * perft_test() - Perform perft on position, experiment version. - * @pos: &position to search - * @depth: Wanted depth. - * @ply: perft depth level. + * perft_alt() - Perform perft on position, experimental version. + * @pos: &position to search + * @depth: Wanted depth. + * @ply: current perft depth level (root = 1) + * @output: 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. * * @return: total moves found at @depth level. */ -u64 perft_test(pos_t *pos, int depth, int ply, bool output) +u64 perft_alt(pos_t *pos, int depth, int ply, bool output) { - int subnodes; - u64 nodes = 0; + u64 subnodes, nodes = 0; movelist_t movelist; move_t *move, *last; state_t state; - movelist.nmoves = 0; pos_set_checkers_pinners_blockers(pos); pos_legal(pos, pos_gen_pseudo(pos, &movelist)); @@ -110,20 +125,20 @@ u64 perft_test(pos_t *pos, int depth, int ply, bool output) if (depth == 1) { nodes++; } else { - move_do2(pos, *move, &state); + move_do_alt(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 { - subnodes = perft_test(pos, depth - 1, ply + 1, output); + subnodes = perft_alt(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_undo2(pos, *move, &state); + move_undo_alt(pos, *move, &state); } } diff --git a/src/search.h b/src/search.h index a2d1531..e6019e4 100644 --- a/src/search.h +++ b/src/search.h @@ -20,6 +20,6 @@ //eval_t pvs(pos_t *pos, int depth, int alpha, int beta, int color); u64 perft(pos_t *pos, int depth, int ply, bool output); -u64 perft_test(pos_t *pos, int depth, int ply, bool output); +u64 perft_alt(pos_t *pos, int depth, int ply, bool output); #endif /* SEARCH_H */ diff --git a/test/common-test.h b/test/common-test.h index 8973c8f..6328899 100644 --- a/test/common-test.h +++ b/test/common-test.h @@ -28,14 +28,26 @@ struct fentest { char *comment; char *fen; } fentest[] = { - /* - { __LINE__, 1, - "", - "" - }, - */ + /************************************************************ + * TEMP TESTS BELOW - only run them (till sentinel below) * + ************************************************************/ -/* ***************** TEMP TESTS ABOVE ************************** */ + /* + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + * "startpos + 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4", + * "r1bqkbnr/1ppp1ppp/p1n5/4p3/B3P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 1 4" + * }, + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + * "", + * "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1" + * }, + */ + + /****************************************************************** + * DO NOT DELETE NEXT LINE - sentinel entry for temp tests above. * + * ignored if first array entry. * + ******************************************************************/ + { __LINE__, 0, NULL, NULL }, { __LINE__, MOVEGEN | MOVEDO | PERFT, "illegal white e.p.", @@ -138,40 +150,45 @@ struct fentest { "checker: h4", "4k3/8/8/8/7b/8/8/4K3 w - - 0 1" }, -// First game moves + /* + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + * "1.e3 - perft bug", + * "rnbqkbnr/pppppppp/8/8/8/4P3/PPPP1PPP/RNBQKBNR b KQkq - 0 1" + * }, + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT | PERFT, + * "1.e3 Nc6 - perft bug", + * "r1bqkbnr/pppppppp/2n5/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq - 1 2" + * }, + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT | PERFT, + * "1.e3 Nc6 2.Ke2 - perft bug", + * "r1bqkbnr/pppppppp/2n5/8/8/4P3/PPPPKPPP/RNBQ1BNR b kq - 2 2" + * }, + * { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + * "1.e3 Nc6 2.Ke2 Nd4+ - perft bug", + * "r1bqkbnr/pppppppp/8/8/3n4/4P3/PPPPKPPP/RNBQ1BNR w kq - 3 3" + * }, + */ + // First game moves { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "startpos", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" }, - //{ __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, - // "1.e3 - perft bug", - // "rnbqkbnr/pppppppp/8/8/8/4P3/PPPP1PPP/RNBQKBNR b KQkq - 0 1" - //}, - //{ __LINE__, FEN | MOVEGEN | MOVEDO | PERFT | PERFT, - // "1.e3 Nc6 - perft bug", - // "r1bqkbnr/pppppppp/2n5/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq - 1 2" - //}, - //{ __LINE__, FEN | MOVEGEN | MOVEDO | PERFT | PERFT, - // "1.e3 Nc6 2.Ke2 - perft bug", - // "r1bqkbnr/pppppppp/2n5/8/8/4P3/PPPPKPPP/RNBQ1BNR b kq - 2 2" - //}, { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, - "1.e3 Nc6 2.Ke2 Nd4+ - perft bug", - "r1bqkbnr/pppppppp/8/8/3n4/4P3/PPPPKPPP/RNBQ1BNR w kq - 3 3" - }, - { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, - "1.e4", + "startpos + 1.e4", "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1" }, { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, - "1.Nh3", - "rnbqkbnr/pppppppp/8/8/8/7N/PPPPPPPP/RNBQKB1R b KQkq - 1 1" + "startpos + 1.e4 e5 2.Nf3", + "rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2" }, { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, - "1.e4 e5 2.Nf3 Nc6", - "r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1" + "startpos + 1.e4 e5 2.Nf3 Nc6", + "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3" + }, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + "startpos + 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4", + "r1bqkbnr/1ppp1ppp/p1n5/4p3/B3P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 1 4" }, - // castling test // both can castle queen only { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, @@ -425,6 +442,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/movedo-test.c b/test/movedo-test.c index bc534ff..897db58 100644 --- a/test/movedo-test.c +++ b/test/movedo-test.c @@ -29,8 +29,8 @@ int main(int __unused ac, __unused char**av) int i = 0, test_line; char *fen, movebuf[8];; pos_t *pos, *savepos; - movelist_t pseudo; - move_t move; + movelist_t movelist; + move_t *move, *last; init_all(); @@ -41,39 +41,39 @@ int main(int __unused ac, __unused char**av) continue; } - pos->checkers = pos_checkers(pos, pos->turn); - pos_set_pinners_blockers(pos); - - pos_gen_pseudo(pos, &pseudo); + movelist.nmoves = 0; + pos_set_checkers_pinners_blockers(pos); + pos_legal(pos, pos_gen_pseudo(pos, &movelist)); + last = movelist.move + movelist.nmoves; savepos = pos_dup(pos); state_t state = pos->state; - int tmp = 0, j = 0; - while ((move = pos_next_legal(pos, &pseudo, &tmp)) != MOVE_NONE) { + int j = 0; + for (move = movelist.move; move < last; ++move) { //pos_print(pos); //printf("i=%d j=%d turn=%d move=[%s]\n", i, j, pos->turn, // move_str(movebuf, move, 0)); //move_p - move_do(pos, move); + move_do(pos, *move, &state); //pos_print(pos); //fflush(stdout); if (!pos_ok(pos, false)) { - printf("*** fen %d move %d [%s] invalid position after move_do\n", - test_line, j, movebuf); + printf("*** fen %d [%s] move %d [%s] invalid position after move_do\n", + test_line, fen, j, move_to_str(movebuf, *move, 0)); exit(0); } //printf("%d/%d move_do check ok\n", i, j); - move_undo(pos, move); + move_undo(pos, *move, &state); pos->state = state; if (!pos_ok(pos, false)) { - printf("*** fen %d move %d [%s] invalid position after move_undo\n", - test_line, j, movebuf); + printf("*** fen %d [%s] move %d [%s] invalid position after move_undo\n", + test_line, fen, j, movebuf); exit(0); } if (pos_cmp(pos, savepos) != true) { - printf("*** fen %d move %d [%s] position differ after move_{do,undo}\n", - test_line, j, movebuf); + printf("*** fen %d [%s] move %d [%s] position differ after move_{do,undo}\n", + test_line, fen, j, movebuf); exit(0); } //fflush(stdout); diff --git a/test/perft-test.c b/test/perft-test.c index 85334ee..8db151d 100644 --- a/test/perft-test.c +++ b/test/perft-test.c @@ -255,20 +255,29 @@ 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 [-cms][-d depth] [-p version] [-t size:\n", prg); + fprintf(stderr, "\t-c: do *not* print FEN comments\n"); + fprintf(stderr, "\t-d depth: perft depth (default: 6)"); + fprintf(stderr, "\t-m: print moves details\n"); + fprintf(stderr, "\t-s: use Stockfish to validate perft result\n"); + fprintf(stderr, "\t-t size: Transposition Table size (Mb). Default: 32\n"); + fprintf(stderr, + "\t-p flavor: perft flavor, 1:perft, 2:perft_alt 3:both, default:1\n"); return 1; } int main(int ac, char**av) { - int test_line; + int curtest = 0; u64 sf_count = 0, my_count; + bool comment = true, sf_run = false, moves_output = false; char *fen; pos_t *pos = NULL, *fenpos; pos_t *fishpos = pos_new(); movelist_t fishmoves; FILE *outfd = NULL; + s64 ms, lps; + int opt, depth = 6, run = 3, tt = 32, newtt = 32; struct { s64 count, ms; s64 minlps, maxlps; @@ -278,48 +287,73 @@ int main(int ac, char**av) { .minlps=LONG_MAX }, { .minlps=LONG_MAX }, }; - s64 ms, lps; - 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, "cd:mp:st:")) != -1) { switch (opt) { + case 'c': + comment = false; + break; case 'd': depth = atoi(optarg); + if (depth <= 0) + depth = 6; break; - case 'p': /* 1 or 2 or 3 for both */ + case 'm': + moves_output = false; + break; + case 'p': run = atoi(optarg); break; - case 'n': - sf_run = false; + case 's': + sf_run = true; break; - case 'v': - perft_output = true; + case 't': + newtt = atoi(optarg); break; default: return usage(*av); } } - printf("perft: depth = %d run = %x stockfish = %s\n", - depth, run, sf_run? "true": "false"); + if (!run) { + printf("Nothing to do, exiting\n"); + exit(0); + } init_all(); + if (newtt != 32 && newtt > 1) { + printf("changing TT size from %d to %d\n", tt, newtt); + tt_create(newtt); + tt = newtt; + } + printf("%s: depth:%d tt_size:%d run:%x SF:%s\n", + *av, + depth, newtt, run, + sf_run? "yes": "no"); - if (!run) - exit(0); + tt_info(); + printf("\n"); + + printf("move_t size:%lu\n", sizeof(move_t)); if (sf_run) outfd = open_stockfish(); CLOCK_DEFINE(clock, CLOCK_MONOTONIC); while ((fen = next_fen(PERFT | MOVEDO))) { - test_line = cur_line(); if (!(fenpos = fen2pos(pos, fen))) { - printf("wrong fen line = %d: [%s]\n", test_line, fen); + printf("wrong fen line:%d fen:%s\n\n", cur_line(), fen); continue; } + curtest++; + printf("test:%d line:%d", curtest, cur_line()); + if (comment) + printf(" comment:%s\n", + *cur_comment()? cur_comment(): "no test desc"); + printf("\t%s\n", fen); + + tt_clear(); + pos = fenpos; if (sf_run) { stockfish_fen(outfd, fen); @@ -338,15 +372,13 @@ int main(int ac, char**av) if (lps < res[2].minlps) res[2].minlps = lps; } - printf("SF : line=%3d perft=%'lu %'ldms lps=%'lu \"%s\"\n", - test_line, sf_count, ms, - lps, - fen); + printf("Stockfish : perft:%'lu ms:%'ld lps:%'lu\n", + sf_count, ms, lps); } if (run & 1) { clock_start(&clock); - my_count = perft(pos, depth, 1, perft_output); + my_count = perft(pos, depth, 1, moves_output); ms = clock_elapsed_ms(&clock); if (!ms) { res[0].skipped++; @@ -362,19 +394,17 @@ int main(int ac, char**av) } if (!sf_run || sf_count == my_count) { - printf("pt1 OK : line=%3d perft=%'lu %'ldms lps=%'lu \"%s\"\n", - test_line, my_count, ms, - lps, - fen); + printf("perft : perft:%'lu ms:%'ld lps:%'lu ", + my_count, ms, lps); + tt_stats(); } else { - printf("pt1 ERR: line=%3d sf=%'lu me=%'lu \"%s\"\n", - test_line, sf_count, my_count, fen); + printf("perft : perft:%'lu ***ERROR***\n", my_count); } } if (run & 2) { clock_start(&clock); - my_count = perft_test(pos, depth, 1, perft_output); + my_count = perft_alt(pos, depth, 1, moves_output); ms = clock_elapsed_ms(&clock); if (!ms) { res[1].skipped++; @@ -390,13 +420,10 @@ int main(int ac, char**av) } if (!sf_run || sf_count == my_count) { - printf("pt2 OK : line=%3d perft=%'lu %'ldms lps=%'lu \"%s\"\n", - test_line, my_count, ms, - lps, - fen); + printf("perft_alt : perft:%'lu ms:%'ld lps:%'lu\n", + my_count, ms, lps); } else { - printf("pt2 ERR: line=%3d sf=%'lu me=%'lu \"%s\"\n", - test_line, sf_count, my_count, fen); + printf("perft_alt : perft:%'lu ***ERROR***\n", my_count); } } printf("\n"); @@ -405,29 +432,32 @@ int main(int ac, char**av) if (sf_run) { if (!res[2].ms) res[2].ms = 1; - printf("total SF %'lums %'lums lps=%'lu min=%'lu max=%'lu (skipped %d)\n", + printf("total Stockfish : perft:%'lums ms:%'lums lps:%'lu min:%'lu max:%'lu " + "(skipped %d/%d)\n", res[2].count, res[2].ms, res[2].count * 1000l / res[2].ms, res[2].minlps, res[2].maxlps, - res[2].skipped); + res[0].skipped, curtest); } if (run & 1) { if (!res[0].ms) res[0].ms = 1; - printf("total perft %'lums %'lums lps=%'lu min=%'lu max=%'lu (skipped %d)\n", + printf("total perft : perft:%'lums ms:%'lums lps:%'lu min:%'lu max:%'lu " + "(skipped %d/%d)\n", res[0].count, res[0].ms, res[0].count * 1000l / res[0].ms, res[0].minlps, res[0].maxlps, - res[0].skipped); + res[0].skipped, curtest); } if (run & 2) { if (!res[1].ms) res[1].ms = 1; - printf("total perft2 %'lums %'lums lps=%'lu min=%'lu max=%'lu (skipped %d)\n", + printf("total perft_alt : perft:%'lums ms:%'lums lps:%'lu min:%'lu max:%'lu " + "(skipped %d/%d)\n", res[1].count, res[1].ms, res[1].count * 1000l / res[1].ms, res[1].minlps, res[1].maxlps, - res[1].skipped); + res[0].skipped, curtest); } return 0; } 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; + * } + */