每日算法 — 使用java实现Bitboard:位运算压缩与高效状态枚举

引言

在棋类AI和组合优化问题中,状态空间的表示效率直接决定了算法的搜索深度和速度。传统的二维数组表示棋盘状态虽然直观,但在生成合法走法、评估局面、拷贝状态时会产生大量的内存访问和对象创建开销。Bitboard(位棋盘)技术通过将棋盘状态压缩到一个或多个64位整数中,利用CPU原生支持的位运算指令在单个时钟周期内完成整行的操作,将状态处理速度提升一到两个数量级。

Bitboard最早在国际象棋程序中被广泛应用,Deep Blue和Stockfish等顶级引擎都大量使用了位棋盘技术。本文将深入讲解Bitboard的设计原理,并用Java实现一个完整的Bitboard框架,展示其在状态表示、移动生成和局面评估中的强大能力。

核心原理

位棋盘基本思想

对于一个8×8的棋盘,用一个64位long整数表示:第i位为1表示棋盘第i个格子上有棋子。例如,国际象棋有6种棋子类型×2种颜色=12种Bitboard,加上一些辅助位图,总共约15-20个long变量即可完整描述整个棋局。

棋盘位的编号(小端序,从右下角开始):

56 57 58 59 60 61 62 63
48 49 50 51 52 53 54 55
40 41 42 43 44 45 46 47
32 33 34 35 36 37 38 39
24 25 26 27 28 29 30 31
16 17 18 19 20 21 22 23
 8  9 10 11 12 13 14 15
 0  1  2  3  4  5  6  7

核心位运算操作

操作 Java表达式 应用场景
设置某位 board \| (1L << sq) 在格子sq放置棋子
清除某位 board & ~(1L << sq) 从格子sq移除棋子
测试某位 (board >> sq) & 1 判断格子sq是否有棋子
提取最低位 board & -board 获取最低位的1的位置
清除最低位 board & (board - 1) 移除最低位的1
位计数 Long.bitCount(board) 计算棋子数量
水平移位 board << 1 或 board >>> 1 棋子水平移动
垂直移位 board << 8 或 board >>> 8 棋子垂直移动

预计算攻击位图

Bitboard的强大之处在于可以预计算每个棋子在每个格子的攻击范围。例如,车的攻击范围可以通过沿着四个方向射线扫描预计算,存储在一个64×64的数组中。在运行时,只需一次查表和位与操作即可得到所有合法攻击位置。

Java实现

基础Bitboard工具类

/**
 * Bitboard位棋盘工具类
 * 提供64位棋盘的基础操作、移动生成和局面评估
 */
public class BitboardUtils {

    // 棋盘常量
    public static final long FILE_A = 0x0101010101010101L;
    public static final long FILE_B = 0x0202020202020202L;
    public static final long FILE_H = 0x8080808080808080L;
    public static final long RANK_1 = 0x00000000000000FFL;
    public static final long RANK_8 = 0xFF00000000000000L;
    public static final long DIAGONAL_A1H8 = 0x8040201008040201L;
    public static final long DIAGONAL_A8H1 = 0x0102040810204080L;

    // 所有格子的掩码
    public static final long ALL_SQUARES = ~0L;
    public static final long EMPTY = 0L;

    /**
     * 将行列坐标转换为位索引 (0-63)
     */
    public static int square(int rank, int file) {
        return rank * 8 + file;
    }

    /**
     * 从位索引获取行号 (0-7)
     */
    public static int rankOf(int sq) {
        return sq >>> 3;
    }

    /**
     * 从位索引获取列号 (0-7)
     */
    public static int fileOf(int sq) {
        return sq & 7;
    }

    /**
     * 设置指定位
     */
    public static long setBit(long board, int sq) {
        return board | (1L << sq);
    }

    /**
     * 清除指定位
     */
    public static long clearBit(long board, int sq) {
        return board & ~(1L << sq);
    }

    /**
     * 测试指定位是否为1
     */
    public static boolean testBit(long board, int sq) {
        return (board & (1L << sq)) != 0;
    }

    /**
     * 提取最低位的1的索引(LSB)
     */
    public static int bitScanForward(long board) {
        return Long.numberOfTrailingZeros(board);
    }

    /**
     * 提取最高位的1的索引(MSB)
     */
    public static int bitScanReverse(long board) {
        return 63 - Long.numberOfLeadingZeros(board);
    }

    /**
     * 清除最低位的1,返回新值
     */
    public static long popLSB(long board) {
        return board & (board - 1);
    }

    /**
     * 获取最低位的1的掩码
     */
    public static long getLSB(long board) {
        return board & -board;
    }

    /**
     * 计算1的位数(种群计数)
     */
    public static int popCount(long board) {
        return Long.bitCount(board);
    }

    /**
     * 将位棋盘打印为8x8网格
     */
    public static void printBoard(long board) {
        System.out.println("  a b c d e f g h");
        for (int rank = 7; rank >= 0; rank--) {
            System.out.print((rank + 1) + " ");
            for (int file = 0; file < 8; file++) {
                int sq = square(rank, file);
                System.out.print(testBit(board, sq) ? "1 " : ". ");
            }
            System.out.println();
        }
        System.out.println();
    }

    /**
     * 遍历位棋盘上所有置位的索引
     */
    public static void forEachBit(long board, java.util.function.IntConsumer action) {
        while (board != 0) {
            int sq = bitScanForward(board);
            action.accept(sq);
            board = popLSB(board);
        }
    }

    /**
     * 将位索引转换为代数坐标(如e4)
     */
    public static String toAlgebraic(int sq) {
        char file = (char) ('a' + fileOf(sq));
        char rank = (char) ('1' + rankOf(sq));
        return "" + file + rank;
    }

    /**
     * 翻转整个棋盘(用于交换黑白视角)
     */
    public static long flipVertical(long board) {
        return Long.reverseBytes(board);
    }

    /**
     * 水平翻转棋盘
     */
    public static long flipHorizontal(long board) {
        long k1 = 0x5555555555555555L;
        long k2 = 0x3333333333333333L;
        long k4 = 0x0f0f0f0f0f0f0f0fL;
        board = ((board >>> 1) & k1) | ((board & k1) << 1);
        board = ((board >>> 2) & k2) | ((board & k2) << 2);
        board = ((board >>> 4) & k4) | ((board & k4) << 4);
        return board;
    }

    /**
     * 旋转90度(顺时针)
     */
    public static long rotate90(long board) {
        return flipHorizontal(Long.reverseBytes(board));
    }
}

滑动棋子攻击生成

/**
 * 预计算的滑动棋子攻击表
 * 用于车、象、后的攻击范围生成
 */
public class SlidingAttacks {

    // 车的攻击范围表 [square][occupancy_index]
    private static final long[][] ROOK_ATTACKS = new long[64][4096];
    private static final long[][] BISHOP_ATTACKS = new long[64][512];

    // 每个格子的掩码(排除边缘位,用于生成索引)
    private static final long[] ROOK_MASK = new long[64];
    private static final long[] BISHOP_MASK = new long[64];

    // 每个掩码的位数
    private static final int[] ROOK_BITS = new int[64];
    private static final int[] BISHOP_BITS = new int[64];

    static {
        initAttackTables();
    }

    private static void initAttackTables() {
        for (int sq = 0; sq < 64; sq++) {
            ROOK_MASK[sq] = generateRookMask(sq);
            BISHOP_MASK[sq] = generateBishopMask(sq);
            ROOK_BITS[sq] = BitboardUtils.popCount(ROOK_MASK[sq]);
            BISHOP_BITS[sq] = BitboardUtils.popCount(BISHOP_MASK[sq]);

            // 预计算所有可能的占用情况
            int rookVariations = 1 << ROOK_BITS[sq];
            for (int i = 0; i < rookVariations; i++) {
                long occupancy = indexToOccupancy(i, ROOK_BITS[sq], ROOK_MASK[sq]);
                ROOK_ATTACKS[sq][i] = generateRookAttacks(sq, occupancy);
            }

            int bishopVariations = 1 << BISHOP_BITS[sq];
            for (int i = 0; i < bishopVariations; i++) {
                long occupancy = indexToOccupancy(i, BISHOP_BITS[sq], BISHOP_MASK[sq]);
                BISHOP_ATTACKS[sq][i] = generateBishopAttacks(sq, occupancy);
            }
        }
    }

    /**
     * 生成车的攻击掩码(不含边缘)
     */
    private static long generateRookMask(int sq) {
        long mask = 0L;
        int rank = BitboardUtils.rankOf(sq);
        int file = BitboardUtils.fileOf(sq);

        // 水平方向(不含端点)
        for (int f = file + 1; f < 7; f++) mask = BitboardUtils.setBit(mask, BitboardUtils.square(rank, f));
        for (int f = file - 1; f > 0; f--) mask = BitboardUtils.setBit(mask, BitboardUtils.square(rank, f));
        // 垂直方向(不含端点)
        for (int r = rank + 1; r < 7; r++) mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, file));
        for (int r = rank - 1; r > 0; r--) mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, file));

        return mask;
    }

    /**
     * 生成象的攻击掩码(不含边缘)
     */
    private static long generateBishopMask(int sq) {
        long mask = 0L;
        int rank = BitboardUtils.rankOf(sq);
        int file = BitboardUtils.fileOf(sq);

        // 四个对角方向(不含端点)
        for (int r = rank + 1, f = file + 1; r < 7 && f < 7; r++, f++) 
            mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, f));
        for (int r = rank + 1, f = file - 1; r < 7 && f > 0; r++, f--) 
            mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, f));
        for (int r = rank - 1, f = file + 1; r > 0 && f < 7; r--, f++) 
            mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, f));
        for (int r = rank - 1, f = file - 1; r > 0 && f > 0; r--, f--) 
            mask = BitboardUtils.setBit(mask, BitboardUtils.square(r, f));

        return mask;
    }

    /**
     * 将索引转换为占用位图
     */
    private static long indexToOccupancy(int index, int bits, long mask) {
        long occupancy = 0L;
        for (int i = 0; i < bits; i++) {
            int sq = BitboardUtils.bitScanForward(mask);
            mask = BitboardUtils.popLSB(mask);
            if ((index & (1 << i)) != 0) {
                occupancy = BitboardUtils.setBit(occupancy, sq);
            }
        }
        return occupancy;
    }

    /**
     * 生成车的攻击范围(含阻挡)
     */
    private static long generateRookAttacks(int sq, long occupancy) {
        long attacks = 0L;
        int rank = BitboardUtils.rankOf(sq);
        int file = BitboardUtils.fileOf(sq);

        // 四个方向射线扫描
        int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        for (int[] d : directions) {
            for (int r = rank + d[0], f = file + d[1]; r >= 0 && r < 8 && f >= 0 && f < 8; r += d[0], f += d[1]) {
                int target = BitboardUtils.square(r, f);
                attacks = BitboardUtils.setBit(attacks, target);
                if (BitboardUtils.testBit(occupancy, target)) break;
            }
        }
        return attacks;
    }

    private static long generateBishopAttacks(int sq, long occupancy) {
        long attacks = 0L;
        int rank = BitboardUtils.rankOf(sq);
        int file = BitboardUtils.fileOf(sq);

        int[][] directions = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
        for (int[] d : directions) {
            for (int r = rank + d[0], f = file + d[1]; r >= 0 && r < 8 && f >= 0 && f < 8; r += d[0], f += d[1]) {
                int target = BitboardUtils.square(r, f);
                attacks = BitboardUtils.setBit(attacks, target);
                if (BitboardUtils.testBit(occupancy, target)) break;
            }
        }
        return attacks;
    }

    /**
     * 获取车的攻击范围(使用预计算表)
     */
    public static long getRookAttacks(int sq, long occupancy) {
        long masked = occupancy & ROOK_MASK[sq];
        int index = (int)((masked * ROOK_MAGIC[sq]) >>> (64 - ROOK_BITS[sq]));
        return ROOK_ATTACKS[sq][index];
    }

    /**
     * 获取象的攻击范围(使用预计算表)
     */
    public static long getBishopAttacks(int sq, long occupancy) {
        long masked = occupancy & BISHOP_MASK[sq];
        int index = (int)((masked * BISHOP_MAGIC[sq]) >>> (64 - BISHOP_BITS[sq]));
        return BISHOP_ATTACKS[sq][index];
    }

    // 魔数(简化版,实际应使用优化的魔数)
    private static final long[] ROOK_MAGIC = generateMagics(ROOK_MASK, ROOK_BITS);
    private static final long[] BISHOP_MAGIC = generateMagics(BISHOP_MASK, BISHOP_BITS);

    private static long[] generateMagics(long[] masks, int[] bits) {
        long[] magics = new long[64];
        for (int i = 0; i < 64; i++) {
            // 简化:使用一个固定的好魔数
            magics[i] = 0x1040010400104001L;
        }
        return magics;
    }
}

非滑动棋子攻击表

/**
 * 非滑动棋子的预计算攻击表
 * 马、王的攻击范围只与位置有关,与棋盘占用无关
 */
public class LeaperAttacks {

    // 马的偏移量:(dx, dy)
    private static final int[][] KNIGHT_OFFSETS = {
        {-2, -1}, {-2, 1}, {-1, -2}, {-1, 2},
        {1, -2}, {1, 2}, {2, -1}, {2, 1}
    };

    // 王的偏移量
    private static final int[][] KING_OFFSETS = {
        {-1, -1}, {-1, 0}, {-1, 1}, {0, -1},
        {0, 1}, {1, -1}, {1, 0}, {1, 1}
    };

    public static final long[] KNIGHT_ATTACKS = new long[64];
    public static final long[] KING_ATTACKS = new long[64];

    static {
        for (int sq = 0; sq < 64; sq++) {
            KNIGHT_ATTACKS[sq] = generateAttacks(sq, KNIGHT_OFFSETS);
            KING_ATTACKS[sq] = generateAttacks(sq, KING_OFFSETS);
        }
    }

    private static long generateAttacks(int sq, int[][] offsets) {
        long attacks = 0L;
        int rank = BitboardUtils.rankOf(sq);
        int file = BitboardUtils.fileOf(sq);

        for (int[] off : offsets) {
            int nr = rank + off[0];
            int nf = file + off[1];
            if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
                attacks = BitboardUtils.setBit(attacks, BitboardUtils.square(nr, nf));
            }
        }
        return attacks;
    }

    public static long getKnightAttacks(int sq) {
        return KNIGHT_ATTACKS[sq];
    }

    public static long getKingAttacks(int sq) {
        return KING_ATTACKS[sq];
    }
}

完整的Bitboard棋局表示

/**
 * 使用Bitboard表示的棋局状态
 * 演示完整的棋盘管理和走法生成
 */
public class BitboardPosition {

    // 各棋子类型的位棋盘(白色)
    private long whitePawns, whiteKnights, whiteBishops, whiteRooks, whiteQueens, whiteKing;
    // 各棋子类型的位棋盘(黑色)
    private long blackPawns, blackKnights, blackBishops, blackRooks, blackQueens, blackKing;

    // 所有白色/黑色棋子
    private long whitePieces, blackPieces, allPieces;

    // 当前走棋方:true=白方,false=黑方
    private boolean whiteToMove;

    public BitboardPosition() {
        // 初始化标准开局位置
        initStandardPosition();
    }

    private void initStandardPosition() {
        // 白色棋子在第1-2行
        whitePawns = 0x000000000000FF00L;
        whiteRooks = 0x0000000000000081L;
        whiteKnights = 0x0000000000000042L;
        whiteBishops = 0x0000000000000024L;
        whiteQueen = 0x0000000000000008L;
        whiteKing = 0x0000000000000010L;

        // 黑色棋子在第7-8行
        blackPawns = 0x00FF000000000000L;
        blackRooks = 0x8100000000000000L;
        blackKnights = 0x4200000000000000L;
        blackBishops = 0x2400000000000000L;
        blackQueen = 0x0800000000000000L;
        blackKing = 0x1000000000000000L;

        updateAggregates();
        whiteToMove = true;
    }

    private void updateAggregates() {
        whitePieces = whitePawns | whiteKnights | whiteBishops | whiteRooks | whiteQueens | whiteKing;
        blackPieces = blackPawns | blackKnights | blackBishops | blackRooks | blackQueens | blackKing;
        allPieces = whitePieces | blackPieces;
    }

    /**
     * 生成当前方所有马的合法走法目标位图
     */
    public long generateKnightMoves() {
        long knights = whiteToMove ? whiteKnights : blackKnights;
        long ownPieces = whiteToMove ? whitePieces : blackPieces;
        long moves = 0L;

        while (knights != 0) {
            int sq = BitboardUtils.bitScanForward(knights);
            knights = BitboardUtils.popLSB(knights);
            // 马的攻击范围减去己方棋子占据的格子
            moves |= LeaperAttacks.getKnightAttacks(sq) & ~ownPieces;
        }
        return moves;
    }

    /**
     * 生成当前方所有王的合法走法目标位图
     */
    public long generateKingMoves() {
        long king = whiteToMove ? whiteKing : blackKing;
        long ownPieces = whiteToMove ? whitePieces : blackPieces;
        int sq = BitboardUtils.bitScanForward(king);
        return LeaperAttacks.getKingAttacks(sq) & ~ownPieces;
    }

    /**
     * 计算当前局面中当前方的棋子总价值(简化评估函数)
     */
    public int evaluate() {
        int score = 0;
        // 兵=100,马=320,象=330,车=500,后=900,王=20000
        score += BitboardUtils.popCount(whitePawns) * 100;
        score += BitboardUtils.popCount(whiteKnights) * 320;
        score += BitboardUtils.popCount(whiteBishops) * 330;
        score += BitboardUtils.popCount(whiteRooks) * 500;
        score += BitboardUtils.popCount(whiteQueens) * 900;

        score -= BitboardUtils.popCount(blackPawns) * 100;
        score -= BitboardUtils.popCount(blackKnights) * 320;
        score -= BitboardUtils.popCount(blackBishops) * 330;
        score -= BitboardUtils.popCount(blackRooks) * 500;
        score -= BitboardUtils.popCount(blackQueens) * 900;

        return whiteToMove ? score : -score;
    }

    public void print() {
        System.out.println("  a b c d e f g h");
        for (int rank = 7; rank >= 0; rank--) {
            System.out.print((rank + 1) + " ");
            for (int file = 0; file < 8; file++) {
                int sq = BitboardUtils.square(rank, file);
                char piece = '.';
                if (BitboardUtils.testBit(whitePawns, sq)) piece = 'P';
                else if (BitboardUtils.testBit(whiteKnights, sq)) piece = 'N';
                else if (BitboardUtils.testBit(whiteBishops, sq)) piece = 'B';
                else if (BitboardUtils.testBit(whiteRooks, sq)) piece = 'R';
                else if (BitboardUtils.testBit(whiteQueens, sq)) piece = 'Q';
                else if (BitboardUtils.testBit(whiteKing, sq)) piece = 'K';
                else if (BitboardUtils.testBit(blackPawns, sq)) piece = 'p';
                else if (BitboardUtils.testBit(blackKnights, sq)) piece = 'n';
                else if (BitboardUtils.testBit(blackBishops, sq)) piece = 'b';
                else if (BitboardUtils.testBit(blackRooks, sq)) piece = 'r';
                else if (BitboardUtils.testBit(blackQueens, sq)) piece = 'q';
                else if (BitboardUtils.testBit(blackKing, sq)) piece = 'k';
                System.out.print(piece + " ");
            }
            System.out.println();
        }
        System.out.println((whiteToMove ? "White" : "Black") + " to move");
        System.out.println("Evaluation: " + evaluate());
    }
}

测试用例

public class BitboardDemo {
    public static void main(String[] args) {
        System.out.println("=== Bitboard基础操作 ===");
        long board = 0L;
        board = BitboardUtils.setBit(board, BitboardUtils.square(0, 0)); // a1
        board = BitboardUtils.setBit(board, BitboardUtils.square(7, 7)); // h8
        board = BitboardUtils.setBit(board, BitboardUtils.square(3, 4)); // e4

        System.out.println("设置a1, h8, e4后的棋盘:");
        BitboardUtils.printBoard(board);

        System.out.println("a1有棋子: " + BitboardUtils.testBit(board, BitboardUtils.square(0, 0)));
        System.out.println("b1有棋子: " + BitboardUtils.testBit(board, BitboardUtils.square(0, 1)));
        System.out.println("棋子数量: " + BitboardUtils.popCount(board));

        System.out.println("\n=== 马的攻击范围 ===");
        int knightSq = BitboardUtils.square(4, 4); // e5
        System.out.println("e5位置的马可以攻击:");
        BitboardUtils.printBoard(LeaperAttacks.getKnightAttacks(knightSq));

        System.out.println("=== 王的攻击范围 ===");
        int kingSq = BitboardUtils.square(0, 4); // e1
        System.out.println("e1位置的王可以移动:");
        BitboardUtils.printBoard(LeaperAttacks.getKingAttacks(kingSq));

        System.out.println("=== 棋盘翻转 ===");
        System.out.println("原棋盘:");
        BitboardUtils.printBoard(board);
        System.out.println("垂直翻转:");
        BitboardUtils.printBoard(BitboardUtils.flipVertical(board));

        System.out.println("\n=== 完整棋局 ===");
        BitboardPosition pos = new BitboardPosition();
        pos.print();

        System.out.println("\n白方马的合法移动目标:");
        BitboardUtils.printBoard(pos.generateKnightMoves());
    }
}

复杂度分析

操作 数组实现 Bitboard实现 加速比
生成马走法 O(n) O(1) 10-100x
生成王走法 O(1) O(1) 相当
生成车/象走法 O(n) O(1) 50-200x
局面拷贝 O(n²) O(1-20) 100-1000x
棋子计数 O(n²) O(1) 100x
攻击检测 O(n) O(1) 10-50x

其中n为棋盘维度(通常为8)。Bitboard将棋盘操作从循环遍历转化为位运算,充分发挥了现代CPU的并行位处理能力。

应用场景

  1. 棋类AI引擎:Stockfish、Leela Chess Zero等顶级引擎的核心技术
  2. 棋盘游戏:围棋、五子棋、跳棋等游戏的状态表示与搜索
  3. 组合优化:子集枚举、集合运算的高效实现
  4. 数据库索引:位图索引(Bitmap Index)用于OLAP查询
  5. 图形处理:像素掩码、碰撞检测的位图表示

总结

Bitboard技术将棋盘状态压缩到64位整数中,利用位运算的并行性实现了极高的操作效率。本文实现了完整的Bitboard工具集,包含基础位操作、滑动棋子攻击生成、非滑动棋子预计算表以及完整的棋局管理类。掌握Bitboard的设计思想,不仅能提升棋类AI的性能,也能为各类需要高效集合操作的算法问题提供新的解决思路。