【问题标题】:Sliding move generation using magic bitboard使用魔术位板生成滑动移动
【发布时间】:2013-06-04 18:42:58
【问题描述】:

这是一个关于如何使用魔术位板验证国际象棋中的滑动棋子移动的全局问题。澄清一下,我并不是在问 魔术位板是如何在内部工作的。

现在,关于这个问题的更多细节。我正在使用位板编写棋盘表示,并且我想使用魔术位板来验证滑动块的移动。有人可以列出如何实现这一目标的主要步骤吗?例如,考虑以下董事会职位:

假设我们已经初始化并准备好使用所有神奇的位板函数和数据结构。那么只使用魔术位板的函数签名,你能列出步骤(伪代码或任何语言)来验证 g3 上白车的给定移动吗?

【问题讨论】:

    标签: chess bitboard


    【解决方案1】:

    简单地说,魔术位板是一种有效的方式来获取位置并获得滑动块的合法移动。

    首先,您需要找到一些神奇的数字。当您使用幻数时,您为find幻数编写的一些代码也将被重复使用。

    首先,您需要编写 5 个函数。这些不需要特别快,因为您只会在查找幻数时使用它们,并且在使用幻数之前在程序启动时使用一次。您可以在这些函数中使用任何旧技术。

    uint64_t blockermask_rook   (int square);
    uint64_t blockermask_bishop (int square);
    uint64_t moveboard_rook     (int square, uint64_t blockerboard);
    uint64_t moveboard_bishop   (int square, uint64_t blockerboard);
    uint64_t blockerboard       (int index, uint64_t blockermask);
    

    所以你可能会问自己,da f%q 是阻挡面具、移动板和阻挡板吗?好吧,我只是编造了这些条款,但这就是我的意思:

    /* Example, Rook on e4:
     *  
     *    The blocker mask        A blocker board         The move board
     *    0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 
     *    0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0 
     *    0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 
     *    0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0 
     *    0 1 1 1 0 1 1 0         0 1 1 0 0 0 0 0         0 0 1 1 0 1 1 1 
     *    0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 1 0 0 0 
     *    0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0 
     *    0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 
     */
    

    拦截器掩码是所有可以被占据阻止你的棋子进一步移动的方格。边缘方块不需要成为其中的一部分,因为无论如何你的棋子都不能进一步超过那个方块。此位板中 1 的数量决定了您需要多大的查找表来进行此块和正方形组合。在这种情况下,有 10 个,因此有 2^10 (1024) 种可能的棋子排列可以阻挡 e4 车。

    拦截板是这些排列之一。在本例中,b4、c4、e2、e5 和 e7 上有棋子。这些是敌方友方棋子。拦截板始终是拦截器掩码的子集(它不需要在其他方格上显示棋子(例如,blockers = occupancy & blockermask;)。

    对于给定的阻挡板,移动板是您的棋子的可用移动。这包括您的作品的可能捕获。请注意,它还包括捕获您自己的作品(但您可以将其与您自己的作品位置中的 NOT 进行 AND 以删除这些作品)。

    因此,基本上您需要在所有方格上生成拦截器掩码,包括车和主教。您还需要在每个方格上为车和主教生成所有可能的阻挡板。在生成阻挡板时,您还应该生成生成的移动板。将所有这些东西存储在数组中以备后用。

    现在你已经完成了,对于每个方块/块组合,你尝试随机的 64 位数字,看看它们是否神奇。您将通过使用魔术公式return ((blockerboard*magic) >> (64-bits)); 知道它们是否神奇,它将从 0..2^bits(在 e4 车的情况下为 0..1024)创建一个神奇的索引。对于某个棋子/方格,如果两个阻挡板曾经生成相同的魔法指数但是这两个阻挡板有不同的移动板,那么这是一个麻瓜数字,你应该尝试一个新的。

    一旦你把它记下来,你将拥有 64 个魔法车号和 64 个魔法主教号。要使用它们,在程序启动时,您将初始化所有的拦截器掩码、拦截器板和移动板。现在,您的程序可以有效地在任何方格(因此也包括皇后)上查找主教和白嘴鸦的移动棋盘。代码如下所示:

    /* Retrieves the move board for the given square and occupancy board. */
    uint64_t magic_move_rook  (int8_t square, uint64_t occupancy)
    {
        /* Remove occupants that aren't in the blocker mask for this square. */
        occupancy &= Rook.blockmask[square];
        /* Calculate the magic move index. */
        int index = (occupancy*Rook.magic[square]) >> (64-Rook.bits[square]);
        /* Return the pre-calculated move board. */
        return Rook.moveboard[square][index];
    }
    

    【讨论】:

    • 这是一个绝妙的答案,当然让我更清楚了。我现在唯一挣扎的是索引实际上是什么以及为什么它正确指向移动板数组中的正确位置。你有什么地方可以让我更清楚地了解这一点吗?我检查了所有常用的地方,即国际象棋编程维基、各种博客文章等。
    • 这很神奇,伙计。不过说真的,我去年夏天写答案时就已经想通了,现在我正在努力回忆。所以魔术公式返回的索引始终是小于总挡板数(BB)的整数。每个BB都有一个对应的移动板(MB)。搜索魔法时,您使用慢速算法计算每个 MB,并将 MB 存储在由候选魔法数计算的索引处。在引擎启动时,使用慢速算法重新计算 MB,然后将它们存储在由您预编程的好魔法计算的索引中。
    • 另外,虽然您可能有 1024 个 BB,每个 BB 都有一个对应的 MB,但您最终可能存储不到 1024 MB。例如,对于具有相同 MB 的两个不同 BB,魔术可以计算两个不同的索引,或者它可以为两者计算相同的索引。只要MB相同,索引碰撞就可以了。因此,要直接回答您的问题,我们发现一个恰好可以计算唯一 MB 的唯一索引的数字只是运气/概率。然后,我们在引擎启动时使用魔法公式和我们已知的魔法有目的地将 MB 存储在正确的索引处。
    • 我意识到在您发布答案三年后我正在写评论。但我想说谢谢。在将我的国际象棋引擎从邮箱板表示转换为位板时,我参考了您非常清楚的解释。见madchess.net/2018/10/28/madchess-3-0-beta-build-39-bitboards
    • @paulwal222 这个答案帮助了我,并将继续帮助其他查找魔法位板的人!
    【解决方案2】:

    我们可以假设魔法位板函数可用,这很好,但通常位板移动生成函数可以接受任何生成位板的技术,该位板提供可能的移动方块。说RookMoves是这样一个函数,那么你将填充移动列表如下:

    UInt64 pieceBitboard = Bitboard[SideToMove | Piece.Rook];
    UInt64 targetBitboard = ~Bitboard[SideToMove | Piece.All];
    
    while (pieceBitboard != 0) {
       Int32 from = Bit.Pop(ref pieceBitboard);
       UInt64 moveBitboard = targetBitboard & RookMoves(from, OccupiedBitboard);
    
       while (moveBitboard != 0) {
           Int32 to = Bit.Pop(ref moveBitboard);
           moveList[index++] = Move.Create(this, from, to);
       }
    }
    

    其中Bit.Pop(ref x) 返回x 中的最低有效位,同时从x 中“弹出”(删除)它。

    要验证移动(我将其解释为确认移动有效性),您可以检查移动是否在移动列表中,或者执行移动并查看它是否让您处于检查状态。当然,您可能需要检查它是否符合棋子的运动规则,但这很简单。

    if ((RookRays[move.From] & Bit.At[move.To]) == 0)
       return false;
    
    Int32 side = SideToMove;
    position.Make(move);
    Boolean valid = position.InCheck(side);
    position.Unmake(move);
    
    return valid; 
    

    【讨论】:

    • 非常感谢宗政李,回答了我的问题。虽然我目前正在采取一种更简单的方法 (chessprogramming.wikispaces.com/Classical+Approach),但一旦我了解了包括搜索和评估在内的整个过程,我就会回到魔术板。
    • @bytefire 是的,在我自己的引擎中,我也使用经典方法,因为它很容易作为临时组件实现。我发现它足够快(perft(6) 在 2 秒内),切换到魔法生成的收益几乎是微不足道的,所以我就保留了它。
    • 这非常有帮助,因为它为我目前正在实施的内容增加了更多意义。附带说明一下,当我遇到更多障碍时,我将在 SO 上发布带有“国际象棋”标签的问题。期待更多帮助:)
    • @ZongZhengLi Perft (6) 在 2 秒内?你能帮助我吗?在这里查看我的问题:chess.stackexchange.com/questions/15705/…
    【解决方案3】:

    哈哈从未听说过“魔法位板”。谷歌它,这正是我所期望的。虽然我看不到它有什么神奇之处。无论如何要回答您的问题,您需要生成当前所选棋子的可用移动位位置。不知道还需要什么。

    至于伪代码,我猜是这样的:

    Positions KingChessPiece::getMovablePositions(){
       (x,y) = current bit position in the bitboard
       availablePosition = [ (x+1,y),(x-1,y),(x,y+1),(x,y-1) ]
       for each position in availablePosition
          if p_i is occupied then remove it from list
    
       return availablePosition
       }
    

    我的意思是这没有什么难的,您只需要确保以与您使用的内部结构兼容的方式获取和设置位置。

    编辑:

    女王的例子:

    Position QueenChessPiece::getMovablePosition(){
         (x,y) = queens current position
          availablePosition = []; //empty list
         //check diagonal positions
         //move top left diagonal 
         availablePosition.concat( this.generateAvailablePosition(x,y,-1,1);
         //move top right diagonal 
         availablePosition.concat( this.generateAvailablePosition(x,y,1,1);
         //move bottom right diagonal 
         availablePosition.concat( this.generateAvailablePosition(x,y,1,-1);
         //move bottom left diagonal 
         availablePosition.concat( this.generateAvailablePosition(x,y,-1,-1);
    
        //move straight up 
        availablePosition.concat( this.generateAvailablePosition(x,y,0,1) )
        //move straight down 
        availablePosition.concat( this.generateAvailablePosition(x,y,0,-1) )
    
        //move left 
        availablePosition.concat( this.generateAvailablePosition(x,y,-1,0) )
        //move right
        availablePosition.concat( this.generateAvailablePosition(x,y,1,0) )
    
      return availablePosition;
    }
    Position QueenChess::generateAvailablePosition(x,y,dx,dy){
      availPosition = [];
      while( !isSpaceOccupied(x + dx , y + dy)) 
        availPosition.add( position(x + dx ,y + dy) );
        x += dx;
        y += dy;
      endWhile
      return availPosition;
       }
    

    【讨论】:

    • 感谢您的回复。这对国王来说是可以的,但车呢?对于滑动件,即车、主教和王后,这并不简单。阻挡者阻挡它占据的方格以及移动方向上的任何方格。
    • 不,魔法位板在移动生成之前只生成一次攻击图。当涉及到生成时,它只是一个表查找。这是一个幼稚的实现,根本没有利用位板优势。
    • 它预生成攻击地图?你能解释一下吗
    • 基本总结:特定方格上特定棋子的占用位可以乘以一个“幻数”,然后移位,以获得预先初始化的攻击表中的索引。
    • 所以查找表基本上会在 (x,y) 位置为每一块预先生成移动位置?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多