I've been working on an implementation of the game Othello in Java that I will to use for research purposes, and I need to have a fast implementation to run as many games as I can, so this is what I did:
Board[][] implementation
My first approach was to use a board[][] multidimensional array to represent the board, just to make it work, and to be sure that it was working properly. I finished it, and I was happy. But, as most people know, that's not very fast, since I was able to play only 1,500 games per second (making random moves, just for testing).
Bitboard
Then, I implemented the board as a BitBoard, which speeds up the move calculation a lot, and is blazing fast compared to the previous implementation. With this implementation it can play up to 20k games per second. This is the code that I use for move calculation, which works very nice, but is repeated:
private void calculateMoves() {
legal = 0L;
long potentialMoves;
long currentBoard = getCurrentBoard();
long opponentBoard = getOpponentBoard();
long emptyBoard = emptyBoard();
// UP
potentialMoves = (currentBoard >> SIZE) & DOWN_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves >> SIZE) & DOWN_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// DOWN
potentialMoves = (currentBoard << SIZE) & UP_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves << SIZE) & UP_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// LEFT
potentialMoves = (currentBoard >> 1L) & RIGHT_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves >> 1L) & RIGHT_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// RIGHT
potentialMoves = (currentBoard << 1L) & LEFT_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves << 1L) & LEFT_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// UP LEFT
potentialMoves = (currentBoard >> (SIZE + 1L)) & RIGHT_MASK & DOWN_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves >> (SIZE + 1L)) & RIGHT_MASK & DOWN_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// UP RIGHT
potentialMoves = (currentBoard >> (SIZE - 1L)) & LEFT_MASK & DOWN_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves >> (SIZE - 1L)) & LEFT_MASK & DOWN_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// DOWN LEFT
potentialMoves = (currentBoard << (SIZE - 1L)) & RIGHT_MASK & UP_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves << (SIZE - 1L)) & RIGHT_MASK & UP_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
// DOWN RIGHT
potentialMoves = (currentBoard << (SIZE + 1L)) & LEFT_MASK & UP_MASK & opponentBoard;
while (potentialMoves != 0L) {
long tmp = (potentialMoves << (SIZE + 1L)) & LEFT_MASK & UP_MASK;
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
moves.clear();
}
Classes
Then I tried to clean up the code a bit this way:
private MoveFinder[] finders = new MoveFinder[] {new UpFinder(), new DownFinder(), new LeftFinder(),
new RightFinder(), new UpLeftFinder(), new UpRightFinder(), new DownLeftFinder(), new DownRightFinder()};
private void calculateMoves() {
legal = 0L;
long potentialMoves;
long currentBoard = getCurrentBoard();
long opponentBoard = getOpponentBoard();
long emptyBoard = emptyBoard();
for (MoveFinder finder : finders) {
potentialMoves = finder.next(currentBoard) & opponentBoard;
while (potentialMoves != 0L) {
long tmp = finder.next(potentialMoves);
legal |= tmp & emptyBoard;
potentialMoves = tmp & opponentBoard;
}
}
moves.clear();
}
private interface MoveFinder {
long next(long next);
}
private class UpFinder implements MoveFinder {
@Override
public long next(long n) {
return (n >> SIZE) & DOWN_MASK;
}
}
private class DownFinder implements MoveFinder {
@Override
public long next(long n) {
return (n << SIZE) & UP_MASK;
}
}
// and so on for the rest of the moves (LeftFinder, RightFinder, etc).
For some reason, with this approach I'm able to run only 15k games per second! Why is this code much slower? Are Java method calls expensive? Is it because is calling and object from an array? Is it because I pass copies of the long n for each method?
Any ideas would be great, because I'm not really good at optimising code.
Thanks!