4

If I have a pawn bitboard like this, for example,

00000000
00000000
00000000
00000000
00001000
00000001
11110110
00000000

generating all of the, say, legal capture moves to the right will perform a north-east shift on the bitboard, like so.

00000000
00000000
00000000
00000100
00000000
01111011
00000000
00000000

Normally, moves are stored as square that the piece moved from, and square that the piece moved to, with special move considerations. But this approach, at least to me, appears to only create a map of positions that these pieces are attacking or can move to.

How would you be able to tell individual moves like this? Would you have to separate it out, i.e. create a bit-board with only one square? In a similar vein, if there were to be multiple moves, how would one be able to distinguish between them?

1 Answer 1

3

How would you be able to tell individual moves like this? Would you have to separate it out, i.e. create a bit-board with only one square? In a similar vein, if there were to be multiple moves, how would one be able to distinguish between them?

There are some different ways you could handle generating moves from bitboards.

In my engine, what I do is go through the bitboard of pawns for the side to move, and generate the moves each individual pawn has. Here's most of the relevant code:

func genPawnMoves(pos *Position, moves *MoveList) {
    usBB := pos.Sides[pos.SideToMove]
    enemyBB := pos.Sides[pos.SideToMove^1]
    pawnsBB := pos.Pieces[Pawn] & pos.Sides[pos.SideToMove]

    // For each pawn on our side...
    for pawnsBB != 0 {
        from := pawnsBB.PopBit()

        pawnOnePush := PawnPushes[pos.SideToMove][from] & ^(usBB | enemyBB)
        pawnTwoPush := ((pawnOnePush & MaskRank[Rank6]) << 8) & ^(usBB | enemyBB)
        if pos.SideToMove == White {
            pawnTwoPush = ((pawnOnePush & MaskRank[Rank3]) >> 8) & ^(usBB | enemyBB)
        }

        // calculate the push move for the pawn...
        pawnPush := pawnOnePush | pawnTwoPush

        // and the attacks.
        pawnAttacks := PawnAttacks[pos.SideToMove][from]

        // Generate pawn push moves
        for pawnPush != 0 {
            to := pawnPush.PopBit()
            if isPromoting(pos.SideToMove, to) {
                makePromotionMoves(pos, from, to, moves)
                continue
            }
            moves.AddMove(NewMove(from, to, Quiet, NoFlag))
        }

        // Generate pawn attack moves.
        for pawnAttacks != 0 {
            to := pawnAttacks.PopBit()
            toBB := SquareBB[to]

            // Check for en passant moves.
            if to == pos.EPSq {
                moves.AddMove(NewMove(from, to, Attack, AttackEP))
            } else if toBB&enemyBB != 0 {
                if isPromoting(pos.SideToMove, to) {
                    makePromotionMoves(pos, from, to, moves)
                    continue
                }
                moves.AddMove(NewMove(from, to, Attack, NoFlag))
            }
        }
    }
}

Notice how I loop through each bit of the pawn bitboard, and then generate all of the possible moves that pawn might have, verify those moves, and then loop through each bit of those move bitboards to generate each individual move.

Note PawnPushes and PawnAttacks are lookup tables where I precalculate what move has a pawn depending on it's square and color (not including double pushes, as those are calculated on the fly as you can see).

If you want to see more of the detail in my code, see here: https://github.com/algerbrex/blunder/blob/main/engine/

4
  • huh, I never thought of that. If you're looping through all of the bits for both the piece bitboard and the move bitboard, though, wouldn't that take more time than just using a mailbox? Or is there something I'm not seeing?
    – AAce3
    May 30, 2022 at 13:38
  • Hey sorry for the late reply @AAce3. To answer your question, bitboard and mailbox approaches often have many similarities, and this is one of them. There are different approaches people take but it's not at all uncommon to do what I described. But they aren't necessarily the same speed. With the naive mailbox approach, you're going to be looping through all 64 squares everytime to generate moves. With the bitboard approach, you're only ever looping through bits which are set, so at most 32 pieces, and as the game goes on even less. (cont) Jun 1, 2022 at 19:15
  • This is why it's common to often use piece lists for mailbox engines. Another big advantage bitboards offer is how easy it is to quickly get information on piece patterns. This is very useful when it comes to the evaluation, as I can quickly get for example, all of the white pawns on a certain rank or file. Jun 1, 2022 at 19:20
  • See here for more information: chessprogramming.org/Bitboards#Analysis and also here chessprogramming.org/Bitboard_Serialization Jun 1, 2022 at 19:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.