Quite new to C++ and magic bitboards. I am developing a new engine (actually working but quite weak yet), but my move generator is slow, event if it correctly passes all the perft tests. For example, my previous engine in JavaScript with mailbox has a faster move generation … Can someone explains why is my implementation slow ? For the is_square_attacked and the gen_moves functions, I used the BBC tutorial on youtube. Here is the relevant code regarding move generation :
Code: Select all
static inline U64 get_bishop_attacks(int square, U64 occupancy) {
    __uint128_t occ = occupancy & BISHOP_MASKS[square];
    occ *= BISHOP_MAGICS[square];
    occ >>= 64 - BISHOP_RELEVANT_BITS[square];
    return BISHOP_ATTACKS[square][occ % 512];
}
static inline U64 get_rook_attacks(int square, U64 occupancy) {
    __uint128_t occ = occupancy & ROOK_MASKS[square];
    occ *= ROOK_MAGICS[square];
    occ >>= 64 - ROOK_RELEVANT_BITS[square];
    return ROOK_ATTACKS[square][occ % 4096];
}
U64 get_queen_attacks(int square, U64 occupancy) {
    return get_bishop_attacks(square, occupancy) | get_rook_attacks(square, occupancy);
}
class Move {
    public :
        int from_sq;
        int to_sq;
        int captured;
        int promoted;
        bool is_ep;
        Move(int from_sq=no_sq, int to_sq=no_sq, int captured=None, int promoted=None, bool is_ep=false) {
            this->from_sq = from_sq;
            this->to_sq = to_sq;
            this->captured = captured;
            this->promoted = promoted;
            this->is_ep = is_ep;
        }
        string to_string() const {
            string move_str = SQUARES_NAMES[from_sq] + SQUARES_NAMES[to_sq];
            if (promoted != None) {
                move_str += std::tolower(pieces_char[promoted]);
            }
            if (move_str == string("NONENONE")) {
                return string("0000");
            }
            return move_str;
        }
        bool operator==(const Move other) const {
            return from_sq == other.from_sq &&
                   to_sq == other.to_sq &&
                   captured == other.captured &&
                   promoted == other.promoted &&
                   is_ep == other.is_ep;
        }
};
const Move nullmove;
struct State {
    int en_passant;
    int castle;
    int movecount[2];
    State(int en_passant, int castle, int movecount[2]) {
        this->en_passant = en_passant;
        this->castle = castle;
        this->movecount[0] = movecount[0];
        this->movecount[1] = movecount[1];
    }
};
class Board {
    public :
        U64 bitboards[12];
        U64 occupancy[3];
        U64 hash_key;
        int side;
        int en_passant;
        int castle;
        int move_count[2];
        vector<State> state_history;
        vector<Move> move_stack;
        vector<U64> hash_history;
        void update_occupancy() {
            occupancy[WHITE] = bitboards[WKING] | bitboards[WQUEEN] | bitboards[WROOK] | bitboards[WBISHOP] | bitboards[WKNIGHT] | bitboards[WPAWN];
            occupancy[BLACK] = bitboards[BKING] | bitboards[BQUEEN] | bitboards[BROOK] | bitboards[BBISHOP] | bitboards[BKNIGHT] | bitboards[BPAWN];
            occupancy[BOTH]  = occupancy[WHITE] | occupancy[BLACK];
        }
        int get_piece(int square) {
            if (square < 0 || square >= 64) {
                return None;
            }
            for (int piece = WPAWN; piece <= BKING; piece++) {
                if (contains_square(bitboards[piece], square)) {
                    return piece;
                }
            }
            return None;
        }
        bool is_square_attacked(int square, int by_side) {
            if ((by_side == WHITE) && (PAWN_ATTACKS[BLACK][square] & bitboards[WPAWN])) {
                return true;
            } else if ((by_side == BLACK) && (PAWN_ATTACKS[WHITE][square] & bitboards[BPAWN])) {
                return true;
            } else if (KNIGHT_ATTACKS[square] & (by_side == WHITE ? bitboards[WKNIGHT] : bitboards[BKNIGHT])) {
                return true;
            } else if (KING_ATTACKS[square] & (by_side == WHITE ? bitboards[WKING] : bitboards[BKING])) {
                return true;
            } else if (get_bishop_attacks(square, occupancy[BOTH]) & (by_side == WHITE ? bitboards[WBISHOP] | bitboards[WQUEEN] : bitboards[BBISHOP] | bitboards[BQUEEN])) {
                return true;
            } else if (get_rook_attacks(square, occupancy[BOTH]) & (by_side == WHITE ? bitboards[WROOK] | bitboards[WQUEEN] : bitboards[BROOK] | bitboards[BQUEEN])) {
                return true;
            }
            return false;
        }
        vector<Move> generate_moves() {
            vector<Move> movelist;
            U64 bitboard;
            U64 attacks;
            int from_square;
            int to_square;
            for (int piece = WPAWN; piece <= BKING; piece++) {
                bitboard = bitboards[piece] & occupancy[side];
                if (side == WHITE) {
                    
                    if (piece == WPAWN) {
                        while (bitboard) {
                            from_square = ls1b_index(bitboard);
                            // quiet moves
                            to_square = from_square - 8;
                            if (!contains_square(occupancy[BOTH], to_square)) {
                                if ((to_square >= A8) && (to_square <= H8)) { // pawn promotion
                                    movelist.push_back(Move(from_square, to_square, None, WQUEEN));
                                    movelist.push_back(Move(from_square, to_square, None, WROOK));
                                    movelist.push_back(Move(from_square, to_square, None, WBISHOP));
                                    movelist.push_back(Move(from_square, to_square, None, WKNIGHT));
                                } else { // pawn push
                                    movelist.push_back(Move(from_square, to_square)); // single push
                                    if ((from_square >= A2) && (from_square <= H2) && !contains_square(occupancy[BOTH], to_square-8)) {
                                        movelist.push_back(Move(from_square, to_square-8)); // double push
                                    }
                                }
                            }
                            // capture moves
                            attacks = PAWN_ATTACKS[WHITE][from_square] & occupancy[BLACK];
                            while (attacks) {
                                to_square = ls1b_index(attacks);
                                if ((to_square >= A8) && (to_square <= H8)) { // capture promotion
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), WQUEEN));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), WROOK));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), WBISHOP));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), WKNIGHT));
                                } else { // normal capture
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square)));
                                }
                                attacks = remove_square(attacks, to_square);
                            }
                            // en passant
                            if (en_passant != no_sq) {
                                attacks = PAWN_ATTACKS[WHITE][from_square] & (1ULL << en_passant);
                                if (attacks) { 
                                    movelist.push_back(Move(from_square, en_passant, BPAWN, None, true));
                                }
                            }
                            bitboard = remove_square(bitboard, from_square);
                        }
                    } else if (piece == WKING) { // castling move
                        if (castle & WK) { // kingside
                            if (!contains_square(occupancy[BOTH], F1) && !contains_square(occupancy[BOTH], G1)) {
                                if (!is_square_attacked(E1, BLACK) && ! is_square_attacked(F1, BLACK)) {
                                    movelist.push_back(Move(E1, G1));
                                }
                            }
                        }
                        if (castle & WQ) { // queenside
                            if (!contains_square(occupancy[BOTH], B1) && !contains_square(occupancy[BOTH], C1) && !contains_square(occupancy[BOTH], D1)) {
                                if (!is_square_attacked(E1, BLACK) && ! is_square_attacked(D1, BLACK)) {
                                    movelist.push_back(Move(E1, C1));
                                }
                            }
                        }
                    }
                
                } else if (side == BLACK) {
                    if (piece == BPAWN) {
                        while (bitboard) {
                            from_square = ls1b_index(bitboard);
                            // quiet moves
                            to_square = from_square + 8;
                            if (!contains_square(occupancy[BOTH], to_square)) {
                                if ((to_square >= A1) && (to_square <= H1)) { // pawn promotion
                                    movelist.push_back(Move(from_square, to_square, None, BQUEEN));
                                    movelist.push_back(Move(from_square, to_square, None, BROOK));
                                    movelist.push_back(Move(from_square, to_square, None, BBISHOP));
                                    movelist.push_back(Move(from_square, to_square, None, BKNIGHT));
                                } else { // pawn push
                                    movelist.push_back(Move(from_square, to_square)); // single push
                                    if ((from_square >= A7) && (from_square <= H7) && !contains_square(occupancy[BOTH], to_square+8)) {
                                        movelist.push_back(Move(from_square, to_square+8)); // double push
                                    }
                                }
                            }
                            // capture moves
                            attacks = PAWN_ATTACKS[BLACK][from_square] & occupancy[WHITE];
                            while (attacks) {
                                to_square = ls1b_index(attacks);
                                if ((to_square >= A1) && (to_square <= H1)) { // capture promotion
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), BQUEEN));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), BROOK));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), BBISHOP));
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square), BKNIGHT));
                                } else { // normal capture
                                    movelist.push_back(Move(from_square, to_square, get_piece(to_square)));
                                }
                                attacks = remove_square(attacks, to_square);
                            }
                            // en passant
                            if (en_passant != no_sq) {
                                attacks = PAWN_ATTACKS[BLACK][from_square] & (1ULL << en_passant);
                                if (attacks) { 
                                    movelist.push_back(Move(from_square, en_passant, WPAWN, None, true));
                                }
                            }
                            bitboard = remove_square(bitboard, from_square);
                        }
                    } else if (piece == BKING) { // castling move
                        if (castle & BK) { // kingside
                            if (!contains_square(occupancy[BOTH], F8) && !contains_square(occupancy[BOTH], G8)) {
                                if (!is_square_attacked(E8, WHITE) && !is_square_attacked(F8, WHITE)) {
                                    movelist.push_back(Move(E8, G8));
                                }
                            }
                        }
                        if (castle & BQ) { // queenside
                            if (!contains_square(occupancy[BOTH], B8) && !contains_square(occupancy[BOTH], C8) && !contains_square(occupancy[BOTH], D8)) {
                                if (!is_square_attacked(E8, WHITE) && ! is_square_attacked(D8, WHITE)) {
                                    movelist.push_back(Move(E8, C8));
                                }
                            }
                        }
                    }
                }
                // knight
                if (((piece == WKNIGHT) && (side == WHITE)) || ((piece == BKNIGHT) && (side == BLACK))) {
                    while (bitboard) {
                        from_square = ls1b_index(bitboard);
                        attacks = KNIGHT_ATTACKS[from_square] & (~occupancy[side]);
                        while (attacks) {
                            to_square = ls1b_index(attacks);
                            if (contains_square(occupancy[BOTH], to_square)) { // capture
                                movelist.push_back(Move(from_square, to_square, get_piece(to_square)));    
                            } else { // quiet move
                                movelist.push_back(Move(from_square, to_square));
                            }
                            attacks = remove_square(attacks, to_square);
                        }
                        bitboard = remove_square(bitboard, from_square);
                    }
                }
                // bishop
                else if (((piece == WBISHOP) && (side == WHITE)) || ((piece == BBISHOP) && (side == BLACK))) {
                    while (bitboard) {
                        from_square = ls1b_index(bitboard);
                        attacks = get_bishop_attacks(from_square, occupancy[BOTH]) & (~occupancy[side]);
                        while (attacks) {
                            to_square = ls1b_index(attacks);
                            if (contains_square(occupancy[BOTH], to_square)) { // capture
                                movelist.push_back(Move(from_square, to_square, get_piece(to_square)));    
                            } else { // quiet move
                                movelist.push_back(Move(from_square, to_square));
                            }
                            attacks = remove_square(attacks, to_square);
                        }
                        bitboard = remove_square(bitboard, from_square);
                    }
                }
                // rook
                else if (((piece == WROOK) && (side == WHITE)) || ((piece == BROOK) && (side == BLACK))) {
                    while (bitboard) {
                        from_square = ls1b_index(bitboard);
                        attacks = get_rook_attacks(from_square, occupancy[BOTH]) & (~occupancy[side]);
                        while (attacks) {
                            to_square = ls1b_index(attacks);
                            if (contains_square(occupancy[BOTH], to_square)) { // capture
                                movelist.push_back(Move(from_square, to_square, get_piece(to_square)));    
                            } else { // quiet move
                                movelist.push_back(Move(from_square, to_square));
                            }
                            attacks = remove_square(attacks, to_square);
                        }
                        bitboard = remove_square(bitboard, from_square);
                    }
                }
                // rook
                else if (((piece == WQUEEN) && (side == WHITE)) || ((piece == BQUEEN) && (side == BLACK))) {
                    while (bitboard) {
                        from_square = ls1b_index(bitboard);
                        attacks = get_queen_attacks(from_square, occupancy[BOTH]) & (~occupancy[side]);
                        while (attacks) {
                            to_square = ls1b_index(attacks);
                            if (contains_square(occupancy[BOTH], to_square)) { // capture
                                movelist.push_back(Move(from_square, to_square, get_piece(to_square)));    
                            } else { // quiet move
                                movelist.push_back(Move(from_square, to_square));
                            }
                            attacks = remove_square(attacks, to_square);
                        }
                        bitboard = remove_square(bitboard, from_square);
                    }
                }
                // king
                else if (((piece == WKING) && (side == WHITE)) || ((piece == BKING) && (side == BLACK))) {
                    while (bitboard) {
                        from_square = ls1b_index(bitboard);
                        attacks = KING_ATTACKS[from_square] & (~occupancy[side]);
                        while (attacks) {
                            to_square = ls1b_index(attacks);
                            if (contains_square(occupancy[BOTH], to_square)) { // capture
                                movelist.push_back(Move(from_square, to_square, get_piece(to_square)));    
                            } else { // quiet move
                                movelist.push_back(Move(from_square, to_square));
                            }
                            attacks = remove_square(attacks, to_square);
                        }
                        bitboard = remove_square(bitboard, from_square);
                    }
                }
            }
            return movelist;
        }
        bool make(Move move) {
            int piece = get_piece(move.from_sq);
            state_history.push_back(State(en_passant, castle, move_count));
            move_stack.push_back(move);
            hash_history.push_back(hash_key);
            move_count[0] += 1;
            if (side == BLACK) {
                move_count[1] += 1;
            }
            hash_key ^= castlingKey[castle];
            if (en_passant != no_sq) {
                hash_key ^= RANDOM_ARRAY[772 + square_file(en_passant)];
            }
            int from_file = square_file(move.from_sq);
            int from_rank = square_rank(move.from_sq);
            int to_file   = square_file(move.to_sq);
            int to_rank   = square_rank(move.to_sq);
            castle &= castle_mask[move.from_sq] & castle_mask[move.to_sq];
            if (piece == WKING) {
                castle &= BK|BQ;
                if ((move.from_sq == E1) && (move.to_sq == G1)) {
                    bitboards[WROOK] = remove_square(bitboards[WROOK], H1);
                    bitboards[WROOK] = add_square(bitboards[WROOK], F1);
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[WROOK] + 8 * square_rank(H1) + square_file(H1)];
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[WROOK] + 8 * square_rank(F1) + square_file(F1)];
                } else if ((move.from_sq == E1) && (move.to_sq == C1)) {
                    bitboards[WROOK] = remove_square(bitboards[WROOK], A1);
                    bitboards[WROOK] = add_square(bitboards[WROOK], D1);
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[WROOK] + 8 * square_rank(A1) + square_file(A1)];
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[WROOK] + 8 * square_rank(D1) + square_file(D1)];
                }
            } else if (piece == BKING) {
                castle &= WK|WQ;
                if ((move.from_sq == E8) && (move.to_sq == G8)) {
                    bitboards[BROOK] = remove_square(bitboards[BROOK], H8);
                    bitboards[BROOK] = add_square(bitboards[BROOK], F8);
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[BROOK] + 8 * square_rank(H8) + square_file(H8)];
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[BROOK] + 8 * square_rank(F8) + square_file(F8)];
                } else if ((move.from_sq == E8) && (move.to_sq == C8)) {
                    bitboards[BROOK] = remove_square(bitboards[BROOK], A8);
                    bitboards[BROOK] = add_square(bitboards[BROOK], D8);
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[BROOK] + 8 * square_rank(A8) + square_file(A8)];
                    hash_key ^= RANDOM_ARRAY[64 * hash_piece[BROOK] + 8 * square_rank(D8) + square_file(D8)];
                }
            }
            if (move.is_ep) {
                bitboards[move.captured] = remove_square(bitboards[move.captured], side == WHITE ? en_passant+8 : en_passant-8);
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[move.captured] + 8 * square_rank(side == WHITE ? en_passant+8 : en_passant-8) + square_file(side == WHITE ? en_passant+8 : en_passant-8)];
            }
            
            else if (move.captured != None) {
                move_count[0] = 0;
                bitboards[move.captured] = remove_square(bitboards[move.captured], move.to_sq);
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[move.captured] + 8 * to_rank + to_file];
            }
            if (move.promoted != None) {
                bitboards[move.promoted] = add_square(bitboards[move.promoted], move.to_sq);
                bitboards[piece] = remove_square(bitboards[piece], move.from_sq);
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[move.promoted] + 8 * to_rank + to_file];
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[piece] + 8 * from_rank + from_file];
            }
            
            else {
                bitboards[piece] = remove_square(bitboards[piece], move.from_sq);
                bitboards[piece] = add_square(bitboards[piece], move.to_sq);
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[piece] + 8 * from_rank + from_file];
                hash_key ^= RANDOM_ARRAY[64 * hash_piece[piece] + 8 * to_rank + to_file];
            }
            en_passant = no_sq;
            if (piece == WPAWN or piece == BPAWN) {
                move_count[0] = 0;
                if (abs(move.from_sq - move.to_sq) == 16) {
                    en_passant = side == WHITE ? move.from_sq - 8 : move.from_sq + 8;
                    hash_key ^= RANDOM_ARRAY[772 + square_file(en_passant)];
                }
            }
            update_occupancy();
            side ^= 1;
            hash_key ^= castlingKey[castle];
            hash_key ^= RANDOM_ARRAY[780]; // side
            // legality check-up
            bool illegal = is_square_attacked(ls1b_index(bitboards[side == BLACK ? WKING : BKING]), side);
            if (illegal) {
                unmake();
                return false;
            }
            return true;
        }
        void unmake() {
            Move move = move_stack.back();
            move_stack.pop_back();
            State state = state_history.back();
            state_history.pop_back();
            en_passant = state.en_passant;
            castle = state.castle;
            move_count[0] = state.movecount[0];
            move_count[1] = state.movecount[1];
            hash_key = hash_history.back();
            hash_history.pop_back();
            side ^= 1;
            int captured = move.captured;
            int piece = get_piece(move.to_sq);
            if (move.promoted != None) {
                bitboards[piece] = remove_square(bitboards[piece], move.to_sq);
                bitboards[side == WHITE ? WPAWN : BPAWN] = add_square(bitboards[side == WHITE ? WPAWN : BPAWN], move.from_sq);
                if (move.captured != None) {
                    bitboards[captured] = add_square(bitboards[captured], move.to_sq);
                }
                update_occupancy();
                return;
            }
            bitboards[piece] = remove_square(bitboards[piece], move.to_sq);
            bitboards[piece] = add_square(bitboards[piece], move.from_sq);
            if (move.is_ep) {
                if (side == WHITE) {
                    bitboards[BPAWN] = add_square(bitboards[BPAWN], en_passant+8);
                } else {
                    bitboards[WPAWN] = add_square(bitboards[WPAWN], en_passant-8);
                }
            } else if (move.captured != None) {
                bitboards[captured] = add_square(bitboards[captured], move.to_sq);
            }
            if (piece == WKING) {
                if ((move.from_sq == E1) && (move.to_sq == G1)) {
                    bitboards[WROOK] = add_square(bitboards[WROOK], H1);
                    bitboards[WROOK] = remove_square(bitboards[WROOK], F1);
                } else if ((move.from_sq == E1) && (move.to_sq == C1)) {
                    bitboards[WROOK] = add_square(bitboards[WROOK], A1);
                    bitboards[WROOK] = remove_square(bitboards[WROOK], D1);
                }
            } else if (piece == BKING) {
                if ((move.from_sq == E8) && (move.to_sq == G8)) {
                    bitboards[BROOK] = add_square(bitboards[BROOK], H8);
                    bitboards[BROOK] = remove_square(bitboards[BROOK], F8);
                } else if ((move.from_sq == E8) && (move.to_sq == C8)) {
                    bitboards[BROOK] = add_square(bitboards[BROOK], A8);
                    bitboards[BROOK] = remove_square(bitboards[BROOK], D8);
                }
            }
            update_occupancy();
            
        }
        
};
int perft(Board board, int depth) {
    
    int nodes = 0;
    if (depth == 0) {
        return 1;
    }
    for (const Move move : board.generate_moves()) {
        if (!board.make(move)) {
            continue;
        }
        nodes += perft(board, depth - 1);
        board.unmake();
    }
    return nodes;
}
Also, my use of the magic bitboards is the following :
Code: Select all
static inline U64 get_bishop_attacks(int square, U64 occupancy) {
    __uint128_t occ = occupancy & BISHOP_MASKS[square];
    occ *= BISHOP_MAGICS[square];
    occ >>= 64 - BISHOP_RELEVANT_BITS[square];
    return BISHOP_ATTACKS[square][occ % 512];
}
static inline U64 get_rook_attacks(int square, U64 occupancy) {
    __uint128_t occ = occupancy & ROOK_MASKS[square];
    occ *= ROOK_MAGICS[square];
    occ >>= 64 - ROOK_RELEVANT_BITS[square];
    return ROOK_ATTACKS[square][occ % 4096];
}
Code: Select all
static inline U64 get_bishop_attacks(int square, U64 occupancy) {
    occupancy &= bishop_masks[square];
    occupancy *= bishop_magic_numbers[square];
    occupancy >>= 64 - bishop_relevant_bits[square];
    return bishop_attacks[square][occupancy];
}
static inline U64 get_rook_attacks(int square, U64 occupancy) {
    occupancy &= rook_masks[square];
    occupancy *= rook_magic_numbers[square];
    occupancy >>= 64 - rook_relevant_bits[square];
    return rook_attacks[square][occupancy];
}
Thanks in advance for any help,
Paul JF

