To summarize the question, I am looking to enhance my engine with hashtables, but it seems to consistently perform worse when it plays the (otherwise identical) algorithm that does not look up hashtable data. Due to insignificantly higher computational load, and somewhat deeper plies towards the endgame if the TT is utilized, I wonder if the way I store/retrieve/interpret hashtable data is erroneous.
The main idea is as follows: Whenever a call of alpha beta returns, I store: the type of node (upper cutoff/lower cutoff/exact), the value, the hash key, the preferred move, the distance to the leaf node. This is stored at a position as indexed by the current hash key modulo the number of hashtable entries. Whenever alphabeta is called and before all possible moves that that node are investigated, the hash key of the current board is used to access the hashtable, and if the stored key coincides and the distance to the leaf node is at least as high as the algorithm would search without hashtables, the stored data is retrieved and used to either: (a) return the search if it was an "exact" node, or (b) update the alpha/beta window if it was an upper/lower node, and execute the "preferred" move first (in the hopes of getting more cutoffs).
Now, if this is conceptually correct, then the issue could lie within the technical implementation. Some relevant bits are attached here - and I apologize for the somewhat nasty notation, this is all very much "wip" while I am ironing out these issues.
The part that looks up data from the hashtable (where alphaBeta_preferredMove just executes the passed move first and does not look up hashtable data - I should consolidate both functions down the line):
Code: Select all
int Engine::alphaBeta(ChessBoard* board, Colour colour, char depth, int alpha, int beta)
[...]
if (useHashtable) {
HashData data = Engine::transpos_table.getHashData();
if ((data.zobristKey == Engine::transpos_table.hash) && (data.distanceToLeaf >= (depthLimit - depth))) {
Engine::transpos_table.hashHits++;
if ((data.type == NodeType::Exact))
return data.value;
else if ((data.type == NodeType::Lower) && (data.value < beta))
beta = data.value;
else if ((data.type == NodeType::Upper) && (data.value > alpha))
alpha = data.value;
if (alpha >= beta) return data.value;
if (colour == Colour::White)
return Engine::alphaBeta_preferredMove(board, Colour::White, depth, alpha, beta, data.preferredMove);
else
return Engine::alphaBeta_preferredMove(board, Colour::Black, depth, alpha, beta, data.preferredMove);
}
}
[continue with the normal alpha beta search, i.e. generate moveset and recursively call alphaBeta]
Code: Select all
void Hashtable::setHashData(int distanceToLeaf, int value, int alpha, int beta, std::tuple<char, char, char, char> preferredMove) {
unsigned long index = Hashtable::hash % Hashtable::HASH_SIZE;
if (((value > alpha) && (value < beta)) || (alpha == beta)) {
Hashtable::table[index].type = NodeType::Exact;
Hashtable::table[index].value = value;
}
else if (value >= beta) {
Hashtable::table[index].type = NodeType::Upper;
Hashtable::table[index].value = value;
}
else if (value <= alpha) {
Hashtable::table[index].type = NodeType::Lower;
Hashtable::table[index].value = value;
}
Hashtable::table[index].distanceToLeaf = distanceToLeaf;
Hashtable::table[index].zobristKey = Hashtable::hash;
Hashtable::table[index].preferredMove = preferredMove;
}
Code: Select all
void Hashtable::updateHash(ChessBoard* board, MoveData move) {
if (move.pieceMoved->colour == Colour::White) {
hash ^= hk_blackToMove;
if (move.pieceMoved->getPieceType() == PieceType::Pawn)
hash = hash ^ hk_whitePawn[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whitePawn[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Knight)
hash = hash ^ hk_whiteKnight[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whiteKnight[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Bishop)
hash = hash ^ hk_whiteBishop[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whiteBishop[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Rook)
hash = hash ^ hk_whiteRook[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whiteRook[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Queen)
hash = hash ^ hk_whiteQueen[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whiteQueen[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::King)
hash = hash ^ hk_whiteKing[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_whiteKing[std::get<0>(move.dest)][std::get<1>(move.dest)];
if (move.pieceTaken != nullptr) {
if (move.pieceTaken->getPieceType() == PieceType::Pawn)
hash = hash ^ hk_blackPawn[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Knight)
hash = hash ^ hk_blackKnight[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Bishop)
hash = hash ^ hk_blackBishop[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Rook)
hash = hash ^ hk_blackRook[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Queen)
hash = hash ^ hk_blackQueen[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::King)
hash = hash ^ hk_blackKing[std::get<0>(move.dest)][std::get<1>(move.dest)];
}
if (move.isWhiteLRookCastling)
hash = hash ^ hk_whiteLRookCastling;
else if (move.isWhiteRRookCastling)
hash = hash ^ hk_whiteRRookCastling;
}
else {
hash = hash ^ hk_whiteToMove;
if (move.pieceMoved->getPieceType() == PieceType::Pawn)
hash = hash ^ hk_blackPawn[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackPawn[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Knight)
hash = hash ^ hk_blackKnight[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackKnight[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Bishop)
hash = hash ^ hk_blackBishop[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackBishop[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Rook)
hash = hash ^ hk_blackRook[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackRook[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::Queen)
hash = hash ^ hk_blackQueen[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackQueen[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceMoved->getPieceType() == PieceType::King)
hash = hash ^ hk_blackKing[std::get<0>(move.orig)][std::get<1>(move.orig)] ^ hk_blackKing[std::get<0>(move.dest)][std::get<1>(move.dest)];
if (move.pieceTaken != nullptr) {
if (move.pieceTaken->getPieceType() == PieceType::Pawn)
hash = hash ^ hk_whitePawn[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Knight)
hash = hash ^ hk_whiteKnight[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Bishop)
hash = hash ^ hk_whiteBishop[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Rook)
hash = hash ^ hk_whiteRook[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::Queen)
hash = hash ^ hk_whiteQueen[std::get<0>(move.dest)][std::get<1>(move.dest)];
else if (move.pieceTaken->getPieceType() == PieceType::King)
hash = hash ^ hk_whiteKing[std::get<0>(move.dest)][std::get<1>(move.dest)];
}
if (move.isBlackLRookCastling)
hash = hash ^ hk_blackLRookCastling;
else if (move.isBlackRRookCastling)
hash = hash ^ hk_blackRRookCastling;
}
if (move.prevEnPassantPawn != std::tuple<char, char>(127, 127))
hash = hash ^ hk_enPassantSquare[std::get<0>(move.prevEnPassantPawn)][std::get<1>(move.prevEnPassantPawn)];
}