Rotated Bitboards in Othello
Mike Maxim

 

Feel free to contact me if you have any more questions about this technique.


Basics:

The largest data type in Java that can fit in a machine register (fast storage on the CPU) is the int, which is 32 bits (Pentium CPUs are 32 bit). However, Java does also define a type, long, which is 64 bits wide. A 64-bit number is never stored in a register, although it can be treated in your Java code as if it were. 64 bit integers are particularly useful for Chess and Othello as described below.


Setup:

There will be 8 bitboards (64 bit long) that you will have to maintain to pull this off. They are as follows (each 1 has 2 bitboards for White and Black):

1.) Rotate 0
2.) Rotate 90 Right
3.) Rotate 45 Right
4.) Rotate 45 Left

These 8 bitboards will be the main board state your program will keep around during game play.

Lets deal with just Rotate 0 for now. The bitboard for White that looks like such:

0000000100000001000000010000000100000001000000010000000100000001

corresponds to White pieces along the H (rightmost) file. Notice now that I am implicitly defining the lower left as square 0. To reference square x in the bitboard, I would do this:

long bboard = <...> //Some position
boolean sqrnset = ((bboard & (1 << n)) != 0);

To set a bit, i.e during the make() operation, I would do:

bboard |= (1 << n);

To remove a bit (remove a piece)

bboard &= ~(1 << n);

***Note: You may want to pre-compute (1 << n), 0 <= n < 64.

To facilitate the rotated boards, you will need to create rotation maps. This may be called something like R90map. You can visualize this if you try hard enough. Think about physically rotating the Othello board to the right by 90 degrees. You will see that square 0 now becomes square 56, square 1 goes to 48, and so forth. During pre-computation, you will want to create this map (or hard code it) so that it performs the R90 mapping. To set a bit in the R90 bitboard, I would do something like this:

bboardR90 |= (1 << R90map[n]);

The other operations are similar.

WARNING: R45R and R45L are similar, except you need to know about the length of the diagonal. You must also pre-compute lengths of diagonals on the square. If you think of square 0 as A1 and square 7 as H1, we can see two principle diagonals at work, A1-H8 and A8-H1. For a given square n, it will have two diagonal lengths, the A1-H8 length and A8-H1 length. This information must be known a priori when doing move generation. More on this in a second.

Here are some links that help with this:

http://supertech.lcs.mit.edu/~heinz/dt/node8.html - For rotation clarification

http://www.galahadnet.com/chess/chessprg/rotated.htm - Explains how to to do it for chess.

Move Generation:

In order to get move generation out of all this, we will need more pre-computation.

Postulate a structure:

byte moves[256][256][2];

The first [256] corresponds to all possible combinations of White's pieces on a rank (row). The following is a sample with White having a piece on the A and E file:

10001000

Note 256 is correct because there are 8 squares and 2 options, empty or White, so 28 = 256. The second [256] correspond to Black. The last 2 are for White and Black, that is, moves for White in this configuration, or moves for Black. The pre-comp loop would look a little something like this:

for (i = 0; i < 256; i++) {
    for (j = 0; j < 256; j++) {
        //Compute all legal white moves given i and j
        moves[i][j][WHITE] = compute_white_moves(i,j);

        //Compute all legal black moves given i and j
        moves[i][j][BLACK] = compute_black_moves(i,j);
    }
}


Keep in mind how rotation is working here. This is all we need because of it. The whole point of rotating everything (by 90 or 45), is so that we can get a file or diagonal configuration by shifting and and-ing. This would be done like so:

val = (bitboardR90Wh << file) & 0xff; //Get vertical configuration
val0 = (bitboardR0 << rank) & 0xff; //Get horizontal configuration


val and val0 are now indices into the moves vector. To get the White moves along that rank and file we could just do this:

val = moves[(bitboardR90Wh << file) & 0xff][(bitboardR90Bk << file) & 0xff][WHITE];
val0 = moves[(bitboardR0Wh << rank) & 0xff][(bitboardR0Bk << rank) & 0xff][WHITE];

WARNING: This code is not exact, there may be subtleties that you will have to figure out for yourselves!

There is an added complication for the R45R and R45L moves. You cannot just & against 0xff in this case since the diagonals are of varying length. You must compute the lengths of diagonals ahead of time so you can know how many bits to mask off during the lookup into the moves table:

To now extract the moves, you must loop through the val variable to find all the set bits. These set bits will then need to be translated back to the correct coordinates of the piece that is moving.

***Note: Most real chess programs will break into assembler for this, there is a special instruction, 'bsr', that can find a set bit in 1 CPU cycle (!) on Pentium Pro and up.

Here are some links to help with this:

http://www.galahadnet.com/chess/chessprg/rotated.htm - Mostly for chess but same ideas apply to Othello.

mmaxim