====== Chess - Programming - Magic BitBoards - Magic BitBoard Principle ====== ===== Magic BitBoard Principle ===== The basic principle is: - Isolate the squares that a given piece may reach from a given position, without taking board occupancy into account. This gives us a 64-bit bitmask of potential target squares. Let's call it T (for Target). - Perform a bitwise AND of T with the bitmask of occupied squares on the board. Let's call the latter O (for Occupied). - Multiply the result by a magic value M and shift the result to the right by a magic amount S. This gives us I (for Index). - Use I as an index in a lookup table to retrieve the bitmask of squares that can actually be reached with this configuration. To sum it up: I = ((T & O) * M) >> S **NOTE:** * Reachable_squares = lookup[I] * T, M, S and lookup are all pre-computed and depend on the position of the piece (P = 0 ... 63). * So, a more accurate formula would be: I = ((T[P] & O) * M[P]) >> S[P] * reachable_squares = lookup[P][I] The purpose of step #3 is to transform the 64-bit value T & O into a much smaller one, so that a table of a reasonable size can be used. * What we get by computing **((T & O) * M) >> S** is essentially a random sequence of bits, and we want to map each of these sequences to a unique bitmask of reachable target squares. * The 'magic' part in this algorithm is to determine the M and S values that will produce a collision-free lookup table as small as possible. * This is a **Perfect Hash Function** problem. * However, no perfect hashing has been found for magic bitboards so far, which means that the lookup tables in use typically contain many unused 'holes'. * Most of the time, they are built by running an extensive brute-force search. ---- ===== Example ===== The Rook can move any amount of positions horizontally but cannot jump over the King. ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ R │ │ │ k │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┘ 7 6 5 4 3 2 1 0 How to verify that the following is an illegal move, because the Rook cannot jump over the King? ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ │ │ k │ │ R │ └───┴───┴───┴───┴───┴───┴───┴───┘ **NOTE:** It is __not__ possible to determine valid moves for sliding pieces by using only bitwise operations. * Bitwise operations and pre-computed lookup tables will be needed. Here, the position of the piece is in [0 ... 7] and the occupancy bitmask is in [0x00 ... 0xFF] (as it is 8-bit wide). * Therefore, it is entirely feasible to build a direct lookup table based on the position and the current board without applying the **magic** part. * This would be: reachable_squares = lookup[P][board] * This will result in a lookup table containing: 8 * 2^8 = 2048 entries * Obviously we cannot do that for chess, as it would contain: 64 * 2^64 = 1,180,591,620,717,411,303,424 entries * Hence the need for the magic multiply and shift operations to store the data in a more compact manner. ---- ===== Build a Lookup Table ===== int xPos = 5; // Position of the 'Rook' piece. UINT64 board = 1 << xPos, // Initial board. UINT lookup[]; // Lookup table. void buildLookup() { int i, pos, msk; // Iterate on all possible positions. for(pos=0; pos<8; pos++) { // Iterate on all possible occupancy masks. for(lookup[pos] = [], msk=0; msk<0x100; msk++) { // Initialize to zero. lookup[pos][msk] = 0; // Compute valid moves to the left. for(i=pos+1; i<8 && !(msk & (1<=0 && !(msk & (1< **NOTE:** The table is storing all possible positions of the Rook piece and each possible board (from 00000000 to 11111111).