You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Bitboard存储棋子攻击范围:如何实现兵等非骑士棋子的攻击存储?

棋子攻击范围预存方案(针对兵、王、车等)

问题背景

已知可以通过初始化一个包含64个uint64_t类型元素的数组,预存骑士所有位置的攻击范围,使用时直接通过索引获取对应掩码。但骑士的攻击是固定跳步、不受其他棋子影响的,兵、王、车等棋子的规则更复杂,需要不同的预存策略。

骑士攻击范围示例代码(修正补全)

#include <array>

std::array<uint64_t, 64> getKnightAttacks()
{
    const uint64_t notLeft2Mask   = 0x3f3f3f3f3f3f3f3f;
    const uint64_t notLeftMask    = 0x7f7f7f7f7f7f7f7f;

    const uint64_t notRight2Mask  = 0xfcfcfcfcfcfcfcfc;
    const uint64_t notRightMask   = 0xfefefefefefefefe;
    const uint64_t notBottomMask  = 0xffffffffffffff00;
    const uint64_t notBottom2Mask = 0xffffffffffff0000;
    const uint64_t notTopMask     = 0x00ffffffffffffff;
    const uint64_t notTop2Mask    = 0x0000ffffffffffff;

    std::array<uint64_t, 64> knight_attacks{};

    for (uint64_t i = 0; i < 64; i++)
    {
        uint64_t pos = 1ULL << i;
        uint64_t temp_attacks = 0;

        temp_attacks |= (pos << 6) & notBottomMask & notRight2Mask;  // 左下跳
        temp_attacks |= (pos << 15) & notBottom2Mask & notRightMask; // 左上跳
        temp_attacks |= (pos << 17) & notBottom2Mask & notLeftMask;  // 右上跳
        temp_attacks |= (pos << 10) & notBottomMask & notLeft2Mask;  // 右下跳

        temp_attacks |= (pos >> 6) & notTopMask & notLeft2Mask;   // 右上跳(黑方视角)
        temp_attacks |= (pos >> 15) & notTop2Mask & notLeftMask;  // 右下跳
        temp_attacks |= (pos >> 17) & notTop2Mask & notRightMask; // 左下跳
        temp_attacks |= (pos >> 10) & notTopMask & notRight2Mask; // 左上跳

        knight_attacks[i] = temp_attacks;
    }

    return knight_attacks;
}

各棋子的预存实现方案

1. 兵的预存方案

兵的移动分攻击(斜向吃子)和前进(非吃子),且受阵营、初始位置限制,需要分阵营预存多组掩码:

攻击掩码(吃子范围)

  • 白兵:斜向上左、斜向上右两个位置,需过滤第一行(无法向上)、最左/最右列(无对应斜向位置)
  • 黑兵:斜向下左、斜向下右两个位置,需过滤第八行(无法向下)、最左/最右列

示例代码(白兵攻击掩码):

std::array<uint64_t, 64> getWhitePawnAttacks() {
    std::array<uint64_t, 64> pawn_attacks{};
    const uint64_t notLeftMask = 0x7f7f7f7f7f7f7f7f;  // 排除最左列
    const uint64_t notRightMask = 0xfefefefefefefefe; // 排除最右列
    const uint64_t notTopMask = 0x00ffffffffffffff;   // 排除第一行

    for (uint64_t i = 0; i < 64; ++i) {
        uint64_t pos = 1ULL << i;
        uint64_t attacks = 0;
        // 左斜上吃子
        attacks |= (pos << 7) & notLeftMask & notTopMask;
        // 右斜上吃子
        attacks |= (pos << 9) & notRightMask & notTopMask;
        pawn_attacks[i] = attacks;
    }
    return pawn_attacks;
}

前进移动掩码

  • 白兵单步前进:每个位置存正上方的位置(排除第一行)
  • 白兵初始两步:仅第二行的位置存正上方两个位置的掩码
  • 黑兵对应生成向下的掩码即可

2. 王的预存方案

王的移动是周围8格,无阻挡(仅走一步),和骑士类似可以直接预存64元素数组,用边界掩码过滤出界位置:

std::array<uint64_t, 64> getKingAttacks() {
    std::array<uint64_t, 64> king_attacks{};
    const uint64_t notLeftMask = 0x7f7f7f7f7f7f7f7f;
    const uint64_t notRightMask = 0xfefefefefefefefe;
    const uint64_t notTopMask = 0x00ffffffffffffff;
    const uint64_t notBottomMask = 0xffffffffffffff00;

    for (uint64_t i = 0; i < 64; ++i) {
        uint64_t pos = 1ULL << i;
        uint64_t attacks = 0;
        // 上下左右
        attacks |= (pos << 8) & notBottomMask; // 下
        attacks |= (pos >> 8) & notTopMask;    // 上
        attacks |= (pos << 1) & notRightMask;  // 右
        attacks |= (pos >> 1) & notLeftMask;   // 左
        // 斜向
        attacks |= (pos << 7) & notLeftMask & notBottomMask;  // 左下
        attacks |= (pos << 9) & notRightMask & notBottomMask; // 右下
        attacks |= (pos >> 7) & notRightMask & notTopMask;    // 右上
        attacks |= (pos >> 9) & notLeftMask & notTopMask;     // 左上
        king_attacks[i] = attacks;
    }
    return king_attacks;
}

3. 车、象、后的处理方案

这类棋子的移动是直线/斜线,会被其他棋子阻挡,无法预存固定的攻击范围,有两种常用方案:

方案一:动态生成攻击范围

每次需要时,从当前位置出发,沿各个方向遍历,直到遇到边界或棋子,生成掩码:

// 示例:生成车的攻击范围(结合当前棋盘占据情况)
uint64_t getRookAttacks(uint64_t pos, uint64_t occupied) {
    uint64_t attacks = 0;
    int square = __builtin_ctzll(pos); // 获取当前位置索引(0-63)
    int rank = square / 8;
    int file = square % 8;

    // 向上遍历
    for (int r = rank - 1; r >= 0; --r) {
        uint64_t target = 1ULL << (r * 8 + file);
        attacks |= target;
        if (occupied & target) break; // 遇到棋子停止
    }
    // 向下遍历
    for (int r = rank + 1; r < 8; ++r) {
        uint64_t target = 1ULL << (r * 8 + file);
        attacks |= target;
        if (occupied & target) break;
    }
    // 向左遍历
    for (int f = file - 1; f >= 0; --f) {
        uint64_t target = 1ULL << (rank * 8 + f);
        attacks |= target;
        if (occupied & target) break;
    }
    // 向右遍历
    for (int f = file + 1; f < 8; ++f) {
        uint64_t target = 1ULL << (rank * 8 + f);
        attacks |= target;
        if (occupied & target) break;
    }
    return attacks;
}

方案二:预存射线掩码+位运算截断

预存每个位置沿各个方向的所有射线(比如车的四个方向射线,包含该方向所有格子),然后用当前棋盘的占据位快速截断射线,得到实际攻击范围。这是国际象棋引擎中常用的优化技巧:

// 预存车的射线掩码(示例:向上射线)
std::array<uint64_t, 64> rookUpRays;

// 初始化时生成射线掩码
void initRookRays() {
    for (int i = 0; i < 64; ++i) {
        int rank = i / 8;
        int file = i % 8;
        uint64_t ray = 0;
        for (int r = rank - 1; r >= 0; --r) {
            ray |= 1ULL << (r * 8 + file);
        }
        rookUpRays[i] = ray;
    }
}

// 实际计算时截断射线
uint64_t getRookUpAttacks(uint64_t pos, uint64_t occupied) {
    int square = __builtin_ctzll(pos);
    uint64_t ray = rookUpRays[square];
    uint64_t blocker = ray & occupied;
    if (blocker) {
        // 找到第一个阻挡棋子,截断射线
        int firstBlocker = __builtin_ctzll(blocker);
        ray &= ~(rookUpRays[firstBlocker]);
    }
    return ray;
}

总结

  • 骑士、王:攻击范围固定无阻挡,直接预存64元素数组即可
  • 兵:按阵营预存攻击、移动多组掩码,适配其规则限制
  • 车、象、后:因受阻挡影响,要么动态生成,要么预存射线掩码结合当前棋盘状态快速计算

内容的提问来源于stack exchange,提问作者ns8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:14:53