See Mediocre v0.2b
Before starting to work with the transposition table (which will be represented as a hash table, more about that later) we need yet another way of representing the position on the board.
We will be using a routine called Zobrist keys for this.
Why another representation?
With Zobrist keys implemented we will have three ways of representing the board in Mediocre. A Board-object, a FEN-string and a Zobrist key.
Each of these serves a special purpose:
The Board-object is the main structure used by the program. Not only does it hold the position but it generates moves, makes moves, and more. We can not really use this for any sort of indexing or comparing without many slow operations.
FEN-strings are mainly used for user interaction (inserting and outputting positions), although I have used them for draw detection and history storage up until now they are not suited for this since they can not be hashed or updated easily. As pointed out to me the FEN-string method currently takes about 35% of the cpu time, this is due to the repetition detection and it proves how very bad the FEN-strings are for this.
Zobrist keys will be used in the transpositions table and for repetition detection. Since a Zobrist key only is a bunch of numbers we can not use them to describe a position to the user.
But since every position has its own unique bunch of numbers, they can be used to compare and store positions with a very high efficiency.
They are perfectly suited for hashing since every change in the position generates a completely different set of numbers. A FEN-string for example would look almost the same after a move which could cause collisions in the hash table. (more on this when I explain transposition tables).
They are also easy to update so we do not have to generate a whole new key every time we make a move. More about this below.
How to create a Zobrist key
A Zobrist key is a 64 bit number that represents a position on the board. In Java the 'long' type is 64 bits (8 bytes) so that is what we will be using.
64 bits is enough to create a unique enough value for every position. I say unique enough because it is not really unique, there are so many positions in chess that even a 64 bit number can not represent them all. There will be occassions when two different positions get the same value, but this is so rare that we do not have to worry about it.
First we need a structure that we will fill with randomly generated numbers. The structure I am using looks like this:
long[6][2][120] PIECES // [piece type][side to move][square]
long[4] W_CASTLING_RIGHTS // Four different castling setups
long[4] B_CASTLING_RIGHTS // both ways, short, long and none
long[120] EN_PASSANT // En passant
long SIDE // Used for changing sides
Random rnd = new Random();
SIDE = rnd.nextLong();
for(int i = 0; i < 4; i++)
{
W_CASTLING_RIGHTS[i] = rnd.nextLong();
B_CASTLING_RIGHTS[i] = rnd.nextLong();
}
long zobristKey = 0;
for(int index = 0; index < 120; index++)
{
int piece = board.boardArray[index];
if(piece > 0) // White piece
zobristKey ^= PIECES[Math.abs(piece)-1][0][index];
else if(piece < 0) // Black piece
zobristKey ^= PIECES[Math.abs(piece)-1][1][index];
}
zobristKey ^= W_CASTLING_RIGHTS[board.white_castle];
zobristKey ^= B_CASTLING_RIGHTS[board.black_castle];
if(board.enPassant != -1) zobristKey ^= EN_PASSANT[board.enPassant];
if(board.toMove == -1) zobristKey ^= SIDE;
1 & 1 = 1
1 & 0 = 1
0 & 0 = 0
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 0 = 0
a ^ b = c
c ^ b = a
zobristKey ^= Zobrist.PIECES[3][0][16];
zobristKey ^= Zobrist.PIECES[3][0][32];
board.setupStart();
System.out.println(Zobrist.getZobristKey(board));
board.makeMove(move); // Making the move 'a2a4'
System.out.println(Zobrist.getZobristKey(board));
board.unmakeMove(move);
System.out.println(Zobrist.getZobristKey(board));
System.out.println("---");
System.out.println(Zobrist.getZobristKey(board));
long key = Zobrist.getZobristKey(board);
key ^= Zobrist.PIECES[5][0][16];
key ^= Zobrist.PIECES[5][0][48];
System.out.println(key);
key ^= Zobrist.PIECES[5][0][48];
key ^= Zobrist.PIECES[5][0][16];
System.out.println(key);
2809723212531837306 // Inital position
6377746370581267278 // After a2a4
2809723212531837306 // Initial position (should be same)
---
2809723212531837306 // Initial position (should be same)
4051561200414578875 // After manually inputting a2a4
2809723212531837306 // Initial position (should be same)
board.setupStart();
key = Zobrist.getZobristKey(board);
key ^= Zobrist.PIECES[5][0][16]; // Making the move a2a4
key ^= Zobrist.PIECES[5][0][48];
key ^= Zobrist.PIECES[5][0][17]; // Making the move b2b4
key ^= Zobrist.PIECES[5][0][49];
System.out.println(key);
board.setupStart();
key = Zobrist.getZobristKey(board);
key ^= Zobrist.PIECES[5][0][17]; // Making the move b2b4 first
key ^= Zobrist.PIECES[5][0][49];
key ^= Zobrist.PIECES[5][0][16]; // Then a2a4
key ^= Zobrist.PIECES[5][0][48];
System.out.println(key);
4594401896990620738
4594401896990620738