diff --git a/Makefile b/Makefile index fa97e43..8550f52 100644 --- a/Makefile +++ b/Makefile @@ -86,23 +86,36 @@ CPPFLAGS := $(strip $(CPPFLAGS)) ##################################### compiler flags CFLAGS := -std=gnu11 -#CFLAGS += -flto + +### dev OR release +# dev CFLAGS += -O1 -CFLAGS += -g +#CFLAGS += -g +# release +#CFLAGS += -Ofast + +CFLAGS += -march=native +CFLAGS += -flto CFLAGS += -Wall CFLAGS += -Wextra -CFLAGS += -march=native CFLAGS += -Wmissing-declarations # for gprof -# CFLAGS += -pg +CFLAGS += -pg # Next one may be useful for valgrind (when invalid instructions) # CFLAGS += -mno-tbm CFLAGS := $(strip $(CFLAGS)) +# development CFLAGS - unused - TODO +#DEV_CFLAGS := -O1 +#DEV_CFLAGS += -g + +# release CFLAGS - unused - TODO +#REL_CFLAGS := -Ofast + ##################################### linker flags LDFLAGS := -L$(BRLIBDIR) -#LDFLAGS += -flto +LDFLAGS += -flto LDFLAGS := $(strip $(LDFLAGS)) @@ -286,19 +299,17 @@ memcheck: targets ##################################### test binaries .PHONY: testing test -TEST := piece-test fen-test bitboard-test movegen-test attack-test movedo-test +TEST := piece-test fen-test bitboard-test movegen-test attack-test +TEST += movedo-test perft-test PIECE_OBJS := piece.o -FEN_OBJS := fen.o position.o piece.o bitboard.o board.o hyperbola-quintessence.o \ - attack.o -BB_OBJS := fen.o position.o piece.o bitboard.o board.o hyperbola-quintessence.o \ - attack.o -MOVEGEN_OBJS := fen.o position.o piece.o bitboard.o board.o hyperbola-quintessence.o \ - attack.o move.o move-gen.o -ATTACK_OBJS := fen.o position.o piece.o bitboard.o board.o hyperbola-quintessence.o \ - attack.o move.o move-gen.o -MOVEDO_OBJS := fen.o position.o piece.o bitboard.o board.o hyperbola-quintessence.o \ - attack.o move.o move-gen.o move-do.o +FEN_OBJS := $(PIECE_OBJS) fen.o position.o bitboard.o board.o \ + hyperbola-quintessence.o attack.o +BB_OBJS := $(FEN_OBJS) +MOVEGEN_OBJS := $(BB_OBJS) move.o move-gen.o +ATTACK_OBJS := $(MOVEGEN_OBJS) +MOVEDO_OBJS := $(ATTACK_OBJS) move-do.o +PERFT_OBJS := $(MOVEDO_OBJS) search.o TEST := $(addprefix $(BINDIR)/,$(TEST)) @@ -308,6 +319,7 @@ BB_OBJS := $(addprefix $(OBJDIR)/,$(BB_OBJS)) MOVEGEN_OBJS := $(addprefix $(OBJDIR)/,$(MOVEGEN_OBJS)) ATTACK_OBJS := $(addprefix $(OBJDIR)/,$(ATTACK_OBJS)) MOVEDO_OBJS := $(addprefix $(OBJDIR)/,$(MOVEDO_OBJS)) +PERFT_OBJS := $(addprefix $(OBJDIR)/,$(PERFT_OBJS)) test: echo TEST=$(TEST) @@ -339,6 +351,10 @@ bin/movedo-test: test/movedo-test.c test/common-test.h $(MOVEDO_OBJS) @echo compiling $@ test executable. @$(CC) $(ALL_CFLAGS) $< $(MOVEDO_OBJS) $(ALL_LDFLAGS) -o $@ +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 $@ + ##################################### Makefile debug .PHONY: showflags wft diff --git a/src/attack.c b/src/attack.c index bc09669..150d14d 100644 --- a/src/attack.c +++ b/src/attack.c @@ -21,7 +21,68 @@ #include "attack.h" -// #define DEBUG_ATTACK_ATTACKERS1 +/** + * sq_is_attacked() - find if a square is attacked + * @pos: position + * @occ: occupation mask used + * @sq: square to test + * @c: attacker color + * + * Find if a @c piece attacks @sq. + * + * Algorithm: We perform a reverse attack, and check if any given + * piece type on @sq can attack a @c piece of same type. + * + * For example, if @c is white, we test for all T in P,N,B,R,Q,K if + * T on @sq could attack a white T piece. + * + * @Return: true if @sq is attacked by a @c piece, false otherwise. + */ +bool sq_is_attacked(const pos_t *pos, const bitboard_t occ, const square_t sq, const color_t c) +{ + bitboard_t sqbb = mask(sq); + color_t opp = OPPONENT(c); + + + //pos_print_raw(pos, 1); + + /* bishop / queen */ + if (hyperbola_bishop_moves(occ, sq) & (pos->bb[c][BISHOP] | pos->bb[c][QUEEN])) + return true; + + /* rook / queen */ + if (hyperbola_rook_moves(occ, sq) & (pos->bb[c][ROOK] | pos->bb[c][QUEEN])) + return true; + + /* pawn */ + if ((pawn_shift_upleft(sqbb, opp) | pawn_shift_upright(sqbb, opp)) & pos->bb[c][PAWN]) + return true; + + /* knight */ + if (bb_knight_moves(pos->bb[c][KNIGHT], sq)) + return true; + + /* king */ + if (bb_king_moves(pos->bb[c][KING], sq)) + return true; + + return false; +} + +/** + * is_in_check() - find if a king is in check. + * @pos: position + * @color: king color + * + * Find if a @c king is in check. This function is a wrapper over @sq_is_attacked(). + * + * @Return: true if @sq is attacked by a @c piece, false otherwise. + */ +bool is_in_check(const pos_t *pos, const color_t color) +{ + bitboard_t occ = pos_occ(pos); + return sq_is_attacked(pos, occ, pos->king[color], OPPONENT(color)); +} /** * sq_attackers() - find attackers on a square diff --git a/src/attack.h b/src/attack.h index 603a965..6c999bc 100644 --- a/src/attack.h +++ b/src/attack.h @@ -17,6 +17,9 @@ #include "chessdefs.h" #include "bitboard.h" +bool sq_is_attacked(const pos_t *pos, const bitboard_t occ, const square_t sq, const color_t c); +bool is_in_check(const pos_t *pos, const color_t color); + bitboard_t sq_attackers(const pos_t *pos, const bitboard_t occ, const square_t sq, const color_t c); bitboard_t sq_attackers_all(const pos_t *pos, const square_t sq); bitboard_t sq_pinners(const pos_t *pos, const square_t sq, const color_t c); diff --git a/src/bitboard.c b/src/bitboard.c index e78e3d6..984ccbb 100644 --- a/src/bitboard.c +++ b/src/bitboard.c @@ -190,8 +190,6 @@ void bitboard_init(void) */ bitboard_t bb_knight_moves(bitboard_t notmine, square_t sq) { - //bitboard_print("bb_knight_move mask", bb_knight[sq]); - //bitboard_print("bb_knight_move res", bb_knight[sq] & notmine); return bb_knight[sq] & notmine; } diff --git a/src/bitboard.h b/src/bitboard.h index c537946..dccf58a 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -272,15 +272,15 @@ static __always_inline bitboard_t shift_nw(const bitboard_t bb) #define pawn_push_upleft(sq, c) ((sq) + ((c) == WHITE ? NORTH_WEST: SOUTH_EAST)) #define pawn_push_upright(sq, c) ((sq) + ((c) == WHITE ? NORTH_EAST: SOUTH_WEST)) -extern bitboard_t bitboard_between_excl(square_t sq1, square_t sq2); -extern void bitboard_init(void); +bitboard_t bitboard_between_excl(square_t sq1, square_t sq2); +void bitboard_init(void); -extern bitboard_t bb_knight_moves(bitboard_t occ, square_t sq); -extern bitboard_t bb_king_moves(bitboard_t occ, square_t sq); +bitboard_t bb_knight_moves(bitboard_t notmine, square_t sq); +bitboard_t bb_king_moves(bitboard_t notmine, square_t sq); -extern void bb_print(const char *title, const bitboard_t bitboard); -extern void bb_print_multi(const char *title, const int n, ...); -extern char *bb_rank_sprint(char *str, const uchar bb8); -extern char *bb_sq2str(const bitboard_t bb, char *str, int len); +void bb_print(const char *title, const bitboard_t bitboard); +void bb_print_multi(const char *title, const int n, ...); +char *bb_rank_sprint(char *str, const uchar bb8); +char *bb_sq2str(const bitboard_t bb, char *str, int len); #endif /* _BITBOARD_H */ diff --git a/src/fen.c b/src/fen.c index 8b34afa..741b2ed 100644 --- a/src/fen.c +++ b/src/fen.c @@ -75,7 +75,7 @@ static const char *castle_str = "KQkq"; static int fen_check(pos_t *pos) { char *colstr[2] = { "white", "black"}; - int error = 0, warning = 0; + int warning = 0; /* en passant, depends on who plays next */ if (pos->en_passant != SQUARE_NONE) { @@ -130,13 +130,7 @@ static int fen_check(pos_t *pos) } } } - if (!(error = pos_check(pos, 0))) { - /* TODO: Should it really be here ? */ - pos->checkers = pos_checkers(pos, pos->turn); - pos->pinners = pos_king_pinners(pos, pos->turn); - pos->blockers = pos_king_blockers(pos, pos->turn, pos->pinners); - } - return error ? -1: warning; + return pos_ok(pos, 0) ? warning: -1; } /** diff --git a/src/hyperbola-quintessence.h b/src/hyperbola-quintessence.h index c7cc8b7..82c867c 100644 --- a/src/hyperbola-quintessence.h +++ b/src/hyperbola-quintessence.h @@ -17,17 +17,17 @@ #include "board.h" #include "bitboard.h" -extern void hyperbola_init(void); +void hyperbola_init(void); -extern bitboard_t hyperbola_rank_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_moves(const bitboard_t pieces, const square_t sq, - const bitboard_t mask); -extern bitboard_t hyperbola_file_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_diag_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_anti_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_rank_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_moves(const bitboard_t pieces, const square_t sq, + const bitboard_t mask); +bitboard_t hyperbola_file_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_diag_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_anti_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_bishop_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_rook_moves(const bitboard_t occ, const square_t sq); -extern bitboard_t hyperbola_queen_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_bishop_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_rook_moves(const bitboard_t occ, const square_t sq); +bitboard_t hyperbola_queen_moves(const bitboard_t occ, const square_t sq); #endif /* _HYPERBOLA_QUINTESSENCE_H */ diff --git a/src/move-do.c b/src/move-do.c index 79f708f..60174db 100644 --- a/src/move-do.c +++ b/src/move-do.c @@ -42,12 +42,12 @@ * * @return: 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 */ + //*state = pos->state; /* save irreversible changes */ color_t us = pos->turn, them = OPPONENT(us); square_t from = move_from(move), to = move_to(move); @@ -146,7 +146,7 @@ pos_t *move_do(pos_t *pos, const move_t move, state_t *state) * * @return: pos. */ -pos_t *move_undo(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) { //# ifdef DEBUG_MOVE //log(1, "new move: "); @@ -184,7 +184,7 @@ pos_t *move_undo(pos_t *pos, const move_t move, const state_t *state) pos_set_sq(pos, grabbed, MAKE_PIECE(PAWN, them)); } - pos->state = *state; /* restore irreversible changes */ + //pos->state = *state; /* restore irreversible changes */ pos->turn = us; return pos; } diff --git a/src/move-do.h b/src/move-do.h index 019a01f..32b276d 100644 --- a/src/move-do.h +++ b/src/move-do.h @@ -16,7 +16,7 @@ #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); #endif /* MOVE_DO_H */ diff --git a/src/move-gen.c b/src/move-gen.c index a453790..2d156af 100644 --- a/src/move-gen.c +++ b/src/move-gen.c @@ -40,6 +40,7 @@ bool pseudo_is_legal(const pos_t *pos, const move_t move) square_t from = move_from(move), to = move_to(move), king = pos->king[us], sq; piece_type_t piece = PIECE(pos->board[from]); bitboard_t kingbb = pos->bb[us][KING], tmp; + u64 pinned = mask(from) & pos->blockers & pos->bb[us][ALL_PIECES]; /* (1) - King * For castling, we already tested intermediate squares attacks @@ -53,14 +54,22 @@ bool pseudo_is_legal(const pos_t *pos, const move_t move) /* (2) - King is in check * Double-check is already handled, as only K moves were generated. - * Here, allowed moves are only on King-attacker line, including - * attacker.We can move a piece on - * in pseudo move generation, so we only care destination square here. + * Here, allowed dest squares are only on King-checker line, or on checker + * square. + * attacker. + * Special cases: + * e.p., legal if the taken pawn is giving check + * pinned piece: always illegal */ if (pos->checkers) { if (popcount64(pos->checkers) == 1) { /* one checker */ square_t checker = ctz64(pos->checkers); - bitboard_t between = bb_between[king][checker]; + if (pinned) + return false; + if (is_enpassant(move)) { + return pawn_push_up(pos->en_passant, them) == checker; + } + bitboard_t between = bb_between[king][checker] | pos->checkers; return mask(to) & between; } return false; /* double check */ @@ -69,7 +78,8 @@ bool pseudo_is_legal(const pos_t *pos, const move_t move) /* (3) - pinned pieces * We verify here that pinned piece P stays on line King-P. */ - if (mask(from) & pos->blockers & pos->bb[us][ALL_PIECES]) { + //if (mask(from) & pos->blockers & pos->bb[us][ALL_PIECES]) { + if (pinned) { bitboard_t line = bb_line[from][king]; return line & mask(to); /* to is not on pin line */ } @@ -159,7 +169,7 @@ movelist_t *pos_all_legal(const pos_t *pos, movelist_t *movelist, movelist_t *de * - castling, if king passes an enemy-controlled square (not final square). * When immediately known, a few move flags are also applied in these cases: * - castling: M_CASTLE_{K,Q} - * - pawn capture (excl. en-passant): M_CAPTURE + * - capture (excl. en-passant): M_CAPTURE * - en-passant: M_EN_PASSANT * - pawn double push: M_DPUSH * - promotion: M_PROMOTION @@ -182,7 +192,6 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) bitboard_t movebits, from_pawns; bitboard_t tmp1, tmp2; - move_t *moves = movelist->move; int *nmoves = &movelist->nmoves; int from, to; @@ -213,7 +222,6 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) } } bit_for_each64(from, tmp1, pos->bb[us][ROOK] | pos->bb[us][QUEEN]) { - // printf("rook=%d/%s\n", from, sq_to_string(from)); movebits = hyperbola_rook_moves(occ, from) & not_my_pieces; bit_for_each64(to, tmp2, movebits & empty) { moves[(*nmoves)++] = move_make(from, to); @@ -235,16 +243,23 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) } /* pawn: relative rank and files */ - bitboard_t rel_rank7 = bb_rel_rank(RANK_7, us); + bitboard_t rel_rank8 = bb_rel_rank(RANK_8, us); bitboard_t rel_rank3 = bb_rel_rank(RANK_3, us); /* pawn: ranks 2-6 push 1 and 2 squares */ - movebits = pawn_shift_up(pos->bb[us][PAWN] & ~rel_rank7, us) & empty; - bit_for_each64(to, tmp1, movebits) { + movebits = pawn_shift_up(pos->bb[us][PAWN], us) & empty; + bit_for_each64(to, tmp1, movebits & ~rel_rank8) { from = pawn_push_up(to, them); /* reverse push */ - //printf("push %d->%d %s->%s", from, to, sq_to_string(from), sq_to_string(to)); moves[(*nmoves)++] = move_make(from, to); } + bit_for_each64(to, tmp1, movebits & rel_rank8) { /* promotions */ + from = pawn_push_up(to, them); /* reverse push */ + moves[(*nmoves)++] = move_make_promote(from, to, QUEEN); + moves[(*nmoves)++] = move_make_promote(from, to, ROOK); + moves[(*nmoves)++] = move_make_promote(from, to, BISHOP); + moves[(*nmoves)++] = move_make_promote(from, to, KNIGHT); + } + /* possible second push */ movebits = pawn_shift_up(movebits & rel_rank3, us) & empty; bit_for_each64(to, tmp1, movebits) { @@ -252,20 +267,33 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) moves[(*nmoves)++] = move_make_flags(from, to, M_DPUSH); } - /* pawn: ranks 2-6 captures left */ - from_pawns = pos->bb[us][PAWN] & ~rel_rank7; // & ~rel_filea; - movebits = pawn_shift_upleft(from_pawns, us) & enemy_pieces; - bit_for_each64(to, tmp1, movebits) { + /* pawn: captures left */ + movebits = pawn_shift_upleft(pos->bb[us][PAWN], us) & enemy_pieces; + bit_for_each64(to, tmp1, movebits & ~rel_rank8) { from = pawn_push_upleft(to, them); /* reverse capture */ moves[(*nmoves)++] = move_make_capture(from, to); } - /* pawn: ranks 2-6 captures right */ - from_pawns = pos->bb[us][PAWN] & ~rel_rank7; // & ~rel_fileh; - movebits = pawn_shift_upright(from_pawns, us) & enemy_pieces; - bit_for_each64(to, tmp1, movebits) { - from = pawn_push_upright(to, them); + bit_for_each64(to, tmp1, movebits & rel_rank8) { + from = pawn_push_upleft(to, them); /* reverse capture */ + moves[(*nmoves)++] = move_make_promote_capture(from, to, QUEEN); + moves[(*nmoves)++] = move_make_promote_capture(from, to, ROOK); + moves[(*nmoves)++] = move_make_promote_capture(from, to, BISHOP); + moves[(*nmoves)++] = move_make_promote_capture(from, to, KNIGHT); + } + + /* pawn: captures right */ + movebits = pawn_shift_upright(pos->bb[us][PAWN], us) & enemy_pieces; + bit_for_each64(to, tmp1, movebits & ~rel_rank8) { + from = pawn_push_upright(to, them); /* reverse capture */ moves[(*nmoves)++] = move_make_capture(from, to); } + bit_for_each64(to, tmp1, movebits & rel_rank8) { + from = pawn_push_upright(to, them); /* reverse capture */ + moves[(*nmoves)++] = move_make_promote_capture(from, to, QUEEN); + moves[(*nmoves)++] = move_make_promote_capture(from, to, ROOK); + moves[(*nmoves)++] = move_make_promote_capture(from, to, BISHOP); + moves[(*nmoves)++] = move_make_promote_capture(from, to, KNIGHT); + } /* pawn: en-passant */ if ((to = pos->en_passant) != SQUARE_NONE) { @@ -277,36 +305,6 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) } } - /* pawn: rank 7 push */ - movebits = pawn_shift_up(pos->bb[us][PAWN] & rel_rank7, us) & empty; - bit_for_each64(to, tmp1, movebits) { - from = pawn_push_up(to, them); /* reverse push */ - moves[(*nmoves)++] = move_make_promote(from, to, QUEEN); - moves[(*nmoves)++] = move_make_promote(from, to, ROOK); - moves[(*nmoves)++] = move_make_promote(from, to, BISHOP); - moves[(*nmoves)++] = move_make_promote(from, to, KNIGHT); - } - /* pawn promotion: rank 7 captures left */ - from_pawns = pos->bb[us][PAWN] & rel_rank7; // & ~rel_filea; - movebits = pawn_shift_upleft(from_pawns, us) & enemy_pieces; - bit_for_each64(to, tmp1, movebits) { - from = pawn_push_upleft(to, them); /* reverse capture */ - moves[(*nmoves)++] = move_make_promote_capture(from, to, QUEEN); - moves[(*nmoves)++] = move_make_promote_capture(from, to, ROOK); - moves[(*nmoves)++] = move_make_promote_capture(from, to, BISHOP); - moves[(*nmoves)++] = move_make_promote_capture(from, to, KNIGHT); - } - /* pawn: rank 7 captures right */ - from_pawns = pos->bb[us][PAWN] & rel_rank7; // & ~rel_fileh; - movebits = pawn_shift_upright(from_pawns, us) & enemy_pieces; - bit_for_each64(to, tmp1, movebits) { - from = pawn_push_upright(to, them); /* reverse capture */ - moves[(*nmoves)++] = move_make_promote_capture(from, to, QUEEN); - moves[(*nmoves)++] = move_make_promote_capture(from, to, ROOK); - moves[(*nmoves)++] = move_make_promote_capture(from, to, BISHOP); - moves[(*nmoves)++] = move_make_promote_capture(from, to, KNIGHT); - } - /* castle - Attention ! Castling flags are assumed correct */ if (!pos->checkers) { @@ -332,16 +330,7 @@ int pos_gen_pseudomoves(pos_t *pos, movelist_t *movelist) } } } - /* TODO - * DONE. pawn ranks 2-6 advance (1 push, + 2 squares for rank 2) - * DONE. pawns rank 7 advance + promotions - * DONE. pawns ranks 2-6 captures, left and right - * DONE. pawns en-passant (with capture) - * DONE. pawns rank 7 capture + promotion - * DONE. castle - * - * add function per piece, and type, for easier debug - * + /* TODO: add function per piece, and type, for easier debug */ return *nmoves; } diff --git a/src/position.c b/src/position.c index aa9d150..969e1ae 100644 --- a/src/position.c +++ b/src/position.c @@ -186,6 +186,49 @@ bitboard_t pos_checkers(const pos_t *pos, const color_t color) return sq_attackers(pos, occ, pos->king[color], OPPONENT(color)); } +/** + * pos_set_checkers_pinners_blockers() - calculate checkers, pinners and blockers. + * @pos: &position + * + * Set position checkers, pinners and blockers on player-to-play king. + * It should be faster than @pos_checkers + @pos_set_pinners_blockers, as + * some calculation will be done once. + */ +/* + * void pos_set_checkers_pinners_blockers(pos_t *pos) + * { + * bitboard_t b_bb = pos->bb[WHITE][BISHOP] | pos->bb[BLACK][BISHOP]; + * bitboard_t r_bb = pos->bb[WHITE][ROOK] | pos->bb[BLACK][ROOK]; + * bitboard_t q_bb = pos->bb[WHITE][QUEEN] | pos->bb[BLACK][QUEEN]; + * + * /\* find out first piece on every diagonal *\/ + * + * } + */ + +/** + * pos_set_pinners_blockers() - set position pinners and blockers. + * @pos: &position + * + * set position pinners and blockers on player-to-play king. + */ +void pos_set_pinners_blockers(pos_t *pos) +{ + color_t color = pos->turn; + square_t king = pos->king[color]; + bitboard_t tmp, occ = pos_occ(pos), blockers = 0; + int pinner; + + pos->pinners = sq_pinners(pos, king, OPPONENT(pos->turn)); + bit_for_each64(pinner, tmp, pos->pinners) { + //bitboard_t blocker = + // warn_on(popcount64(blocker) != 1); + blockers |= bb_between_excl[pinner][king] & occ; + } + pos->blockers = blockers; + return; +} + /** * pos_king_pinners() - get the "pinners" on a king "pinners". * @pos: &position @@ -218,19 +261,18 @@ bitboard_t pos_king_blockers(const pos_t *pos, const color_t color, const bitboa square_t pinner, king = pos->king[color]; bit_for_each64(pinner, tmp, pinners) { - bitboard_t blocker = bb_between_excl[pinner][king] & occ; - warn_on(popcount64(blocker) != 1); - if (popcount64(blocker) != 1) { - printf("n blockers = %d\n", popcount64(blocker)); - bb_print("blockers", blocker); - } - blockers |= blocker; + //warn_on(popcount64(blocker) != 1); + //if (popcount64(blocker) != 1) { + // printf("n blockers = %d\n", popcount64(blocker)); + // bb_print("blockers", blocker); + //} + blockers |= bb_between_excl[pinner][king] & occ; } return blockers; } /** - * pos_check() - extensive position consistency check. + * pos_ok() - extensive position consistency check. * @pos: &position * @strict: if true, call bug_on() on any error. * @@ -255,9 +297,9 @@ bitboard_t pos_king_blockers(const pos_t *pos, const color_t color, const bitboa * TODO: add more checks: * - kings attacking each other * - * @Return: Number of detected error (only if @strict is false). + * @return: (if @strict is false) return true if check is ok, false otherwise. */ -int pos_check(const pos_t *pos, const bool strict) +bool pos_ok(const pos_t *pos, const bool strict) { int n, count = 0, bbcount = 0, error = 0; bitboard_t tmp; @@ -301,7 +343,7 @@ int pos_check(const pos_t *pos, const bool strict) error += warn_on(sq_dist(pos->king[WHITE], pos->king[BLACK]) < 2); bug_on(strict && error); - return error; + return error? false: true; } /** @@ -333,7 +375,7 @@ void pos_print_mask(const pos_t *pos, const bitboard_t mask) } /** - * pos_print_board_raw - print simple position board (octal/FEN symbol values) + * pos_print_raw - print simple position board (octal/FEN symbol values) * @bb: the bitboard * @type: int, 0 for octal, 1 for fen symbol */ diff --git a/src/position.h b/src/position.h index 8dd9935..0e76a06 100644 --- a/src/position.h +++ b/src/position.h @@ -30,7 +30,8 @@ typedef struct __pos_s { u64 node_count; /* evaluated nodes */ int turn; /* WHITE or BLACK */ - /* data which cannot be recovered by move_undo + /* data which cannot be recovered by move_undo (like castle_rights, ...), + * or would be expensive to recover (checkers, ...) * following data can be accessed either directly, either via "movesave" * structure name. * For example, pos->en_passant and pos->state.en_passant are the same. @@ -48,7 +49,6 @@ typedef struct __pos_s { ); piece_t board[BOARDSIZE]; - bitboard_t bb[2][PIECE_TYPE_MAX]; /* bb[0][PAWN], bb[1][ALL_PIECES] */ //bitboard_t controlled[2]; /* unsure */ square_t king[2]; /* dup with bb, faster retrieval */ @@ -159,14 +159,13 @@ void pos_del(pos_t *pos); pos_t *pos_clear(pos_t *pos); bool pos_cmp(const pos_t *pos1, const pos_t *pos2); +//bitboard_t set_king_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); bitboard_t pos_king_pinners(const pos_t *pos, const color_t color); bitboard_t pos_king_blockers(const pos_t *pos, const color_t color, const bitboard_t ); -//bitboard_t set_king_pinners_blockers(pos_t *pos); -//char *pos_checkers2str(const pos_t *pos, char *str); -//char *pos_pinners2str(const pos_t *pos, char *str); -int pos_check(const pos_t *pos, const bool strict); +bool pos_ok(const pos_t *pos, const bool strict); void pos_print(const pos_t *pos); void pos_print_mask(const pos_t *pos, const bitboard_t mask); diff --git a/src/search.c b/src/search.c index 983370d..30d8cd8 100644 --- a/src/search.c +++ b/src/search.c @@ -18,10 +18,11 @@ #include "position.h" #include "move-gen.h" #include "move-do.h" +#include "search.h" +#include "attack.h" //#include "move.h" //#include "eval.h" -//#include "search.h" /** * perft() - Perform perft on position @@ -36,35 +37,70 @@ */ u64 perft(pos_t *pos, int depth, int ply) { - int movetmp = 0, nodes = 0, subnodes, nmove = 0; - movelist_t pseudo = { .nmoves = 0 }, legal = { .nmoves = 0 }; + int subnodes, movetmp = 0; + u64 nodes = 0; + movelist_t pseudo = { .nmoves = 0 }; move_t move; + state_t state; if (depth == 0) return 1; pos->checkers = pos_checkers(pos, pos->turn); - pos->pinners = pos_king_pinners(pos, pos->turn); - pos->blockers = pos_king_blockers(pos, pos->turn, pos->pinners); + pos_set_pinners_blockers(pos); + state = pos->state; pos_gen_pseudomoves(pos, &pseudo); - pos_all_legal(pos, &pseudo, &legal); - - //for (nmove = 0; nmove < legal.nmoves; ++nmove ) { while ((move = pos_next_legal(pos, &pseudo, &movetmp)) != MOVE_NONE) { - - state_t state; - move = legal.move[nmove]; - move_do(pos, move, &state); + move_do(pos, move); subnodes = perft(pos, depth - 1, ply + 1); if (ply == 1) { char movestr[8]; printf("%s: %d\n", move_str(movestr, move, 0), subnodes); } nodes += subnodes; - move_undo(pos, move, &state); + move_undo(pos, move); + pos->state = state; + } + + if (ply == 1) + printf("\nTotal: %lu\n", nodes); + return nodes; +} + +u64 perft2(pos_t *pos, int depth, int ply) +{ + int subnodes, nmove = 0; + u64 nodes = 0; + movelist_t pseudo = { .nmoves = 0 }; + move_t move; + state_t state; + + if (is_in_check(pos, OPPONENT(pos->turn))) + return 0; + if (depth == 0) + return 1; + pos->checkers = pos_checkers(pos, pos->turn); + pos_set_pinners_blockers(pos); + state = pos->state; + + pos_gen_pseudomoves(pos, &pseudo); + + for (nmove = 0; nmove < pseudo.nmoves; ++nmove ) { + move = pseudo.move[nmove]; + move_do(pos, move); + //if (!is_in_check(pos, OPPONENT(pos->turn))) { + subnodes = perft2(pos, depth - 1, ply + 1); + nodes += subnodes; + if (ply == 1) { + char movestr[8]; + printf("%s: %d\n", move_str(movestr, move, 0), subnodes); + } + //} + move_undo(pos, move); + pos->state = state; } if (ply == 1) - printf("Total: %d\n", nodes); + printf("\nTotal: %lu\n", nodes); return nodes; } diff --git a/src/search.h b/src/search.h index d4f8f12..9fbf9e7 100644 --- a/src/search.h +++ b/src/search.h @@ -20,5 +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); +u64 perft2(pos_t *pos, int depth, int ply); #endif /* SEARCH_H */ diff --git a/test/common-test.h b/test/common-test.h index 800671e..d433c32 100644 --- a/test/common-test.h +++ b/test/common-test.h @@ -20,6 +20,7 @@ #define MOVEGEN 4 #define ATTACK 8 #define MOVEDO 16 +#define PERFT 32 struct fentest { int line; @@ -38,41 +39,58 @@ struct fentest { */ //"4k3/pppppppp/8/8/8/8/PPPPPPPP/2BRK3 w - - 0 1", //"4k3/pppppppp/8/8/8/8/PPPPPPPP/1B1R1K2 w - - 0 1", - { __LINE__, MOVEGEN, - "illegal white e.p.", - "3k4/8/8/2qpPK2/8/8/8/8 w - d6 0 1", - }, - { __LINE__, MOVEGEN, - "illegal black e.p.", - "8/8/8/8/2QPpk2/8/8/3K4 b - d3 0 1", - }, - - { __LINE__, MOVEGEN, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "illegal white e.p.", "3k4/8/5K2/3pP3/8/2b5/8/8 w - d6 0 1", }, - { __LINE__, MOVEGEN, + { __LINE__, PERFT, + "only K, pawn push and ep moves", + "3k4/8/8/3pPK2/8/8/8/8 w - d6 0 1" + }, + + { __LINE__, MOVEGEN | MOVEDO | PERFT, + "illegal white e.p.", + "3k4/8/8/2qpPK2/8/8/8/8 w - d6 0 1", + }, + { __LINE__, MOVEGEN | MOVEDO | PERFT, + "illegal black e.p.", + "8/8/8/8/2QPpk2/8/8/3K4 b - d3 0 1", + }, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "illegal black e.p.", "8/8/2B5/8/3Pp3/5k2/8/3K4 b - d3 0 1", }, - { __LINE__, MOVEGEN, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "legal white e.p.", "1K1k4/8/8/3pP3/8/6b1/8/8 w - d6 0 1", }, - { __LINE__, MOVEGEN, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "legal black e.p.", "8/8/6B1/8/3Pp3/8/8/1k1K4 b - d3 0 1", }, - - { __LINE__, MOVEGEN, - "white mate.", - "1nbqkbn1/ppp1pppp/8/r1rpP1K1/8/8/PPPP1PPP/RNBQ1BNR w - d6 0 1", - }, - { __LINE__, MOVEGEN, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "illegal e.p.", "1nbqkbn1/ppp1pppp/8/r1rpP1K1/8/8/PPPP1PPP/RNBQ1BNR w - d6 0 1", }, + { __LINE__, MOVEGEN | MOVEDO | PERFT, + "illegal e.p. bug perft at depth 4", + "1nbqkbn1/ppp2ppp/4p3/r1rpP3/6K1/P7/1PPP1PPP/RNBQ1BNR b - - 1 2" + }, + /* + * { __LINE__, MOVEGEN | MOVEDO | PERFT, + * "illegal e.p. bug perft depth 3", + * "1nbqkbn1/ppp2ppp/4p3/2rpP3/r5K1/P7/1PPP1PPP/RNBQ1BNR w - - 2 3" + * }, + * { __LINE__, MOVEGEN | MOVEDO | PERFT, + * "illegal e.p. bug perft depth 2", + * "1nbqkbn1/ppp2ppp/4p3/2rpP3/r4PK1/P7/1PPP2PP/RNBQ1BNR b - - 0 3" + * }, + * { __LINE__, MOVEGEN | MOVEDO | PERFT | PERFT, + * "illegal e.p. bug perft depth 1 - fixed", + * "1nb1kbn1/ppp2ppp/4p3/2rpP1q1/r4PK1/P7/1PPP2PP/RNBQ1BNR w - - 1 4" + * }, + */ { __LINE__, ATTACK, "checkers: a1 h1", "1k6/8/8/8/8/8/8/r2K3r w - - 1 1" @@ -113,7 +131,7 @@ struct fentest { "3k4/8/8/3r3b/b7/1N2n3/4B3/rN1K1R1r w - - 1 0" }, - { __LINE__, MOVEGEN, + { __LINE__, MOVEGEN | MOVEDO | PERFT, "only pawn captures", "4k3/8/2p1p3/2PpP3/8/pp4pp/PP4PP/4K3 w - d6 0 1" }, @@ -123,59 +141,75 @@ struct fentest { "4k3/8/8/8/7b/8/8/4K3 w - - 0 1" }, // First game moves - { __LINE__, FEN | MOVEGEN, - "initial pos", + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + "startpos", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + //{ __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", "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "1.Nh3", "rnbqkbnr/pppppppp/8/8/8/7N/PPPPPPPP/RNBQKB1R b KQkq - 1 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "1.e4 e5 2.Nf3 Nc6", "r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1" }, // castling test // both can castle queen only - { __LINE__, FEN | MOVEGEN, - "", + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + "no Q castling", "r3k2r/8/3B4/8/8/3b4/8/R3K2R w KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, - "", + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, + "no castling, 2 mates in 1", "r3k2r/8/3BB3/8/8/3bb3/8/R3K2R w KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r2bkb1r/8/8/8/8/3bb3/8/R2BKB1R w KQkq - 0 1" }, // - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "4 castle possible, only K+R", "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1" }, // - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "only kings on A1/A8, white to play", "k7/8/8/8/8/8/8/K7 w - - 0 1" }, // - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "only one move possible (Pf2xBg3)", "k7/8/8/1p1p4/pPpPp3/P1PpPpb1/NBNP1P2/KBB1B3 w - - 0 1" }, // - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "only 2 moves possible (Ph5xg6 e.p., Ph5-h6)", "k7/8/8/1p1p2pP/pPpPp3/P1PpPp2/NBNP1P2/KBB1B3 w - g6 0 1" }, // - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "2 Kings, W/B/ pawns on 7th for promotion", "k4n2/4P3/8/8/8/8/4p3/K4N2 w - - 0 1" }, @@ -183,20 +217,20 @@ struct fentest { // white is a pawn down // white has 36 moves: P=11 + 1 e.p. N=6+3 B=5+5 R=1 Q=3 K=1 + 1 e.p. // black has 33 moves: P=11 N=2+7 B=5 R=3 Q=3 K=1 + castle - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "rnbqk2r/pp1pbpp1/7p/2pPp3/4n3/3B1N2/PPP2PPP/RNBQ1RK1 w kq c6 0 7" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "4k3/4p3/8/b7/1BR1p2p/1Q3P2/5N2/4K3 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1bq1rk1/pppp1ppp/2n2n2/4p3/2B1P3/3PPN2/PPP3PP/RN1QK2R b KQ - 1 7" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "6k1/6pp/R2p4/p1p5/8/1P1r3P/6P1/6K1 b - - 3 3" }, @@ -204,156 +238,167 @@ struct fentest { // some of tests below are from: // - Rodent IV // - https://www.chessprogramming.net/perfect-perft/ - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "8/2p5/3p4/Kp5r/1R3p1k/8/4P1P1/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "1rbqk1nr/p3ppbp/2np2p1/2p5/1p2PP2/3PB1P1/PPPQ2BP/R2NK1NR b KQk - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1bqk2r/pp1p1ppp/2n1pn2/2p5/1bPP4/2NBP3/PP2NPPP/R1BQK2R b KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "rnb1kb1r/ppp2ppp/1q2p3/4P3/2P1Q3/5N2/PP1P1PPP/R1B1KB1R b KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1b2rk1/pp2nppp/1b2p3/3p4/3N1P2/2P2NP1/PP3PBP/R3R1K1 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "n1q1r1k1/3b3n/p2p1bp1/P1pPp2p/2P1P3/2NBB2P/3Q1PK1/1R4N1 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "2r5/8/1n6/1P1p1pkp/p2P4/R1P1PKP1/8/1R6 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r2q1rk1/1b1nbppp/4p3/3pP3/p1pP4/PpP2N1P/1P3PP1/R1BQRNK1 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "6k1/5pp1/7p/p1p2n1P/P4N2/6P1/1P3P1K/8 w - - 0 35" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r4rk1/1pp1q1pp/p2p4/3Pn3/1PP1Pp2/P7/3QB1PP/2R2RK1 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "", "1k6/1b6/8/8/7R/8/8/4K2R b K - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Illegal ep move #1", "3k4/3p4/8/K1P4r/8/8/8/8 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Illegal ep move #2", "8/8/4k3/8/2p5/8/B2P2K1/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "EP Capture Checks Opponent", "8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Short Castling Gives Check", "5k2/8/8/8/8/8/8/4K2R w K - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Long Castling Gives Check", "3k4/8/8/8/8/8/8/R3K3 w Q - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Castle Rights", "r3k2r/1b4bq/8/8/8/8/7B/R3K2R w KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Castling Prevented", "r3k2r/8/3Q4/8/8/5q2/8/R3K2R b KQkq - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Promote out of Check", "2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Discovered Check", "8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Promote to give check", "4k3/1P6/8/8/8/8/K7/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Under Promote to give check", "8/P1k5/K7/8/8/8/8/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Self Stalemate", "K1k5/8/P7/8/8/8/8/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Stalemate & Checkmate", "8/k1P5/8/1K6/8/8/8/8 w - - 0 1" }, - { __LINE__, FEN | MOVEGEN, + { __LINE__, FEN | MOVEGEN | MOVEDO | PERFT, "Stalemate & Checkmate", "8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1" }, - + { __LINE__, MOVEDO | PERFT, + "simple movedo/undo: only 2 W knights", + "8/1k6/8/8/8/8/6K1/1NN5 w - - 0 1" + }, + { __LINE__, MOVEDO | PERFT, + "simple movedo/undo: only 2 W knights", + "8/1k6/8/8/8/8/6K1/1NN5 w - - 0 1" + }, + { __LINE__, MOVEDO | PERFT, + "simple movedo/undo: only 2 W knights", + "5n2/1k6/8/8/5K2/8/P7/1N6 w - - 0 1" + }, { __LINE__, FEN, "illegal EP and castle flags, fix-able by fen parser, SF crash", "4k3/8/8/8/7B/8/8/4K3 w KQkq e6 0 1" @@ -370,18 +415,6 @@ struct fentest { "illegal, SF crash", "2r1k3/3P4/8/8/8/8/8/4K3 w - - 0 1" }, - { __LINE__, MOVEDO, - "simple movedo/undo: only 2 W knights", - "8/1k6/8/8/8/8/6K1/1NN5 w - - 0 1" - }, - { __LINE__, MOVEDO, - "simple movedo/undo: only 2 W knights", - "8/1k6/8/8/8/8/6K1/1NN5 w - - 0 1" - }, - { __LINE__, MOVEDO, - "simple movedo/undo: only 2 W knights", - "5n2/1k6/8/8/5K2/8/P7/1N6 w - - 0 1" - }, { __LINE__, 0, NULL, NULL } }; diff --git a/test/movedo-test.c b/test/movedo-test.c index 62a6179..ac24e44 100644 --- a/test/movedo-test.c +++ b/test/movedo-test.c @@ -37,34 +37,39 @@ int main(int __unused ac, __unused char**av) bitboard_init(); hyperbola_init(); - while ((fen = next_fen(MOVEGEN | MOVEDO))) { + while ((fen = next_fen(MOVEDO))) { test_line = cur_line(); if (!(pos = fen2pos(NULL, fen))) { printf("wrong fen %d: [%s]\n", i, fen); continue; } + + pos->checkers = pos_checkers(pos, pos->turn); + pos_set_pinners_blockers(pos); + pos_gen_pseudomoves(pos, &pseudo); savepos = pos_dup(pos); + + state_t state = pos->state; int tmp = 0, j = 0; while ((move = pos_next_legal(pos, &pseudo, &tmp)) != MOVE_NONE) { - state_t state; - move_str(movebuf, move, 0); //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, &state); + move_do(pos, move); //pos_print(pos); //fflush(stdout); - if (pos_check(pos, false)) { + if (!pos_ok(pos, false)) { printf("*** fen %d move %d [%s] invalid position after move_do\n", test_line, j, movebuf); exit(0); } //printf("%d/%d move_do check ok\n", i, j); - move_undo(pos, move, &state); - if (pos_check(pos, false)) { + move_undo(pos, move); + pos->state = state; + if (!pos_ok(pos, false)) { printf("*** fen %d move %d [%s] invalid position after move_undo\n", test_line, j, movebuf); exit(0); diff --git a/test/movegen-test.c b/test/movegen-test.c index 4d71a0e..9cfbfc6 100644 --- a/test/movegen-test.c +++ b/test/movegen-test.c @@ -238,7 +238,10 @@ int main(int __unused ac, __unused char**av) /* print movelists */ send_stockfish_fen(outfd, fishpos, &fishmoves, fen); pos_gen_pseudomoves(pos, &pseudo); + //moves_print(&pseudo, 0); pos_all_legal(pos, &pseudo, &legal); + + //moves_print(&legal, 0); //printf("Fu "); //moves_print(fishpos, 0); //fflush(stdout); diff --git a/test/perft-test.c b/test/perft-test.c new file mode 100644 index 0000000..e93847f --- /dev/null +++ b/test/perft-test.c @@ -0,0 +1,303 @@ +/* perft-test.c - perft 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 "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" + +#define RD 0 +#define WR 1 + +typedef struct { + move_t move; + int count; +} ddperft; + +/** + * return write pipd fd + */ +static FILE *open_stockfish() +{ + int rpipe[2], wpipe[2]; + FILE *out_desc; + pid_t pid; + + if ((pipe(rpipe) < 0) || (pipe(wpipe) < 0)) { + perror("pipe"); + return NULL; + } + if ((pid = fork()) < 0) { + perror("fork"); + return NULL; + } + + if (!pid) { /* stockfish */ + setvbuf(stdin, NULL, _IOLBF, 0); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + + dup2(wpipe[RD], STDIN_FILENO); + dup2(rpipe[WR], STDOUT_FILENO); + dup2(rpipe[WR], STDERR_FILENO); + + close(wpipe[RD]); + close(wpipe[WR]); + close(rpipe[RD]); + close(rpipe[WR]); + if (execlp("stockfish", "stockfish", NULL) == -1) { + perror("execlp"); + return NULL; + } + return 0; /* not reached */ + } + /* us */ + dup2(rpipe[RD], STDIN_FILENO); + setvbuf(stdin, NULL, _IOLBF, 0); + + close(wpipe[RD]); + close(rpipe[RD]); + close(rpipe[WR]); + + out_desc = fdopen(wpipe[WR], "w"); + setvbuf(out_desc, NULL, _IOLBF, 0); + + return out_desc; +} + +static u64 send_stockfish_fen(FILE *desc, pos_t *pos, movelist_t *movelist, + char *fen, int depth) +{ + char *buf = NULL; + u64 count, mycount = 0, fishcount; + size_t alloc = 0; + ssize_t buflen; + + pos_clear(pos); + + move_t *moves = movelist->move; + int *nmoves = &movelist->nmoves; + *nmoves = 0; + //char nodescount[] = "Nodes searched"; + //printf("nmoves = %d\n", nmoves); + fflush(stdout); + //sprintf(str, "stockfish \"position fen %s\ngo perft depth\n\"", fen); + fprintf(desc, "position fen %s\ngo perft %d\n", fen, depth); + //fflush(desc); + while ((buflen = getline(&buf, &alloc, stdin)) > 0) { + buf[--buflen] = 0; + if (buflen == 0) + continue; + if (sscanf(buf, "Nodes searched: %lu", &fishcount) == 1) { + break; + } + //printf("%d: %s\n", line++, buf); + if (sscanf(buf, "%*4s: %lu", &count) == 1) { + square_t from = sq_from_string(buf); + square_t to = sq_from_string(buf + 2); + mycount += count; + //printf("move found: %c%c->%c%c %s->%s count=%d\n", + // buf[0], buf[1], buf[2], buf[3], + // sq_to_string(from), sq_to_string(to), + // count); + moves[(*nmoves)++] = move_make(from, to); + + } else if (sscanf(buf, "%*5s: %lu", &count) == 1) { + square_t from = sq_from_string(buf); + square_t to = sq_from_string(buf + 2); + piece_type_t promoted = piece_t_from_char(*(buf + 4)); + mycount += count; + //printf("move found: %c%c->%c%c %s->%s count=%d\n", + // buf[0], buf[1], buf[2], buf[3], + // sq_to_string(from), sq_to_string(to), + // count); + moves[(*nmoves)++] = move_make_promote(from, to, promoted); + } + } + //pos->moves.nmoves = nmoves; + // printf("fishcount=%d mycount=%d\n", fishcount, mycount); + free(buf); + return mycount; +} + +static __unused bool movelists_equal(movelist_t *fish, movelist_t *me) +{ + move_t *m1 = fish->move, *m2 = me->move; + int n1 = fish->nmoves, n2 = me->nmoves; + int mask = 077777; + + if (n1 != n2) + return false; + for (int cur = 0; cur < n1; ++cur) { + if ((m1[cur] & mask) != (m2[cur] & mask)) + return false; + } + return true; +} + +static __unused void compare_moves(movelist_t *fish, movelist_t *me) +{ + char str1[1024] = {0}, str2[1024] = {0}, tmpstr[1024]; + char *skip = " "; + move_t *m1 = fish->move; + move_t *m2 = me->move; + int n1 = fish->nmoves; + int n2 = me->nmoves; + +#define f(c) move_from(c) +#define t(c) move_to(c) + + for (move_t *c1 = m1, *c2 = m2; (c1 - m1 < n1) || (c2 - m2 < n2);) { + // square_t f1 = move_from(*c1); square_t t1 = move_to(*c1); + // square_t f2 = move_from(*c2); square_t t2 = move_to(*c2); + + /* no more move in c2 */ + if (c2 - m2 >= n2) { + while (c1 - m1 < n1) { + sprintf(tmpstr, " %s-%s", sq_to_string(f(*c1)), sq_to_string(t(*c1))); + strcat (str1, tmpstr); + c1++; + } + break; + } + if (c1 - m1 >= n1) { + while (c2 - m2 < n2) { + sprintf(tmpstr, " %s-%s", sq_to_string(f(*c2)), sq_to_string(t(*c2))); + strcat (str2, tmpstr); + c2++; + } + break; + } + + /* missing move in c2 */ + if (f(*c1) < f(*c2) || + (f(*c1) == f(*c2) && t(*c1) < t(*c2))) { + strcat(str2, skip); + sprintf(tmpstr, " %s-%s", sq_to_string(f(*c1)), sq_to_string(t(*c1))); + strcat (str1, tmpstr); + while ((c1 - m1 < n1) && (f(*c1) < f(*c2) || + (f(*c1) == f(*c2) && t(*c1) < t(*c2)))) { + c1++; + } + continue; + } + /* missing move in c1 */ + if (f(*c1) > f(*c2) || + (f(*c1) == f(*c2) && t(*c1) > t(*c2))) { + strcat(str1, skip); + sprintf(tmpstr, " %s-%s", sq_to_string(f(*c2)), sq_to_string(t(*c2))); + strcat (str2, tmpstr); + while ((c2 - m2 < n2) && (f(*c1) > f(*c2) || + (f(*c1) == f(*c2) && t(*c1) > t(*c2)))) { + c2++; + } + continue; + } + sprintf(tmpstr, " %s-%s", sq_to_string(f(*c1)), sq_to_string(t(*c1))); + strcat(str1, tmpstr); + strcat(str2, tmpstr); + c1++, c2++; + } + printf("F(%2d): %s\nM(%2d): %s\n", n1, str1, n2, str2); +} + + +int main(int __unused ac, __unused char**av) +{ + int i = 0, test_line; + u64 sf_count, my_count; + char *fen; + pos_t *pos, *savepos, *fishpos = pos_new(); + movelist_t fishmoves; + //move_t move; + FILE *outfd; + int depth = 6; + + if (ac > 1) + depth = atoi(av[1]); + printf("depth = %d\n", depth); + + setlocale(LC_NUMERIC, ""); + setlinebuf(stdout); /* line-buffered stdout */ + outfd = open_stockfish(); + + bitboard_init(); + hyperbola_init(); + + while ((fen = next_fen(PERFT | MOVEDO))) { + test_line = cur_line(); + if (!(pos = fen2pos(NULL, fen))) { + printf("wrong fen %d: [%s]\n", i, fen); + continue; + } + sf_count = send_stockfish_fen(outfd, fishpos, &fishmoves, fen, depth); + //pos_gen_pseudomoves(pos, &pseudo); + savepos = pos_dup(pos); + //int j = 0; + +#define NANOSEC 1000000000 /* nano sec in sec */ +#define MILLISEC 1000000 /* milli sec in sec */ + + // 1 sec = 1000 millisec + // 1 millisec = 1000 microsec + // 1 microsec = 1000 nanosec + // milli = sec * 1000 + nanosec / 1000000 + struct timespec ts1, ts2; + s64 microsecs; + + clock_gettime(CLOCK_MONOTONIC_RAW, &ts1); + my_count = perft(pos, depth, 1); + clock_gettime(CLOCK_MONOTONIC_RAW, &ts2); + + microsecs = ((s64)ts2.tv_sec - (s64)ts1.tv_sec) * 1000000l + + ((s64)ts2.tv_nsec - (s64)ts1.tv_nsec) / 1000l ; + if (sf_count == my_count) { + printf("pt1 OK : line=%03d perft=%lu ms=%'ldms lps=%'lu \"%s\"\n", + test_line, my_count, microsecs/1000l, + my_count*1000000l/microsecs, fen); + } else { + printf("pt1 ERR: line=%03d sf=%lu me=%lu \"%s\"\n", + test_line, sf_count, my_count, fen); + } + + clock_gettime(CLOCK_MONOTONIC_RAW, &ts1); + my_count = perft2(pos, depth, 1); + clock_gettime(CLOCK_MONOTONIC_RAW, &ts2); + + microsecs = ((s64)ts2.tv_sec - (s64)ts1.tv_sec) * 1000000l + + ((s64)ts2.tv_nsec - (s64)ts1.tv_nsec) / 1000l ; + if (sf_count == my_count) { + printf("pt2 OK : line=%03d perft=%lu ms=%'ldms lps=%'lu \"%s\"\n\n", + test_line, my_count, microsecs/1000l, + my_count*1000000l/microsecs, fen); + } else { + printf("pt2 ERR: line=%03d sf=%lu me=%lu \"%s\"\n\n", + test_line, sf_count, my_count, fen); + } + pos_del(savepos); + pos_del(pos); + i++; + } + return 0; +}