I was in the process of optimizing my chess engine in python when I was doing some testing on this position (engine as black) with a depth of 4:
board and everything seemed fine until I looked at the evaluations for each position. When moving the black queen from h1 to h6, it gives the position a score of 69.5 for black which is clearly wrong because white can promote on the next move.
I rewrote my alpha beta function to its most basic form, like it’s written on the chess programming wiki,
Mine:
def search(self, depth: int, whiteTurn: bool, alpha: int, beta: int, baseDepth: int) -> float:
if depth == 0:
return self.evaluate(whiteTurn)
moves = []
squares = list(self.board.keys())
# finding legal moves this turn
if whiteTurn:
for square in squares:
# check for a piece
if self.board[square] != '0' and self.board[square].isupper():
squareMoves = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
for move, score in squareMoves:
moves.append([ch.Move.from_uci(square + move), score])
moves = [move[0] for move in sorted(moves, key=lambda x: x[1], reverse=True)]
else:
for square in squares:
if self.board[square] != '0' and self.board[square].islower():
squareMoves = self.orderMoves(findLegalMoves(self.pythonBoard.legal_moves, square), square)
for move, score in squareMoves:
moves.append([ch.Move.from_uci(square + move), score])
moves = [move[0] for move in sorted(moves, key=lambda x: x[1], reverse=True)]
for move in moves:
self.pythonBoard.push(move)
fenboard = self.pythonBoard.board_fen()
self.board = fenConverter(fenboard)
evaluation = -self.search(depth - 1, not whiteTurn, -beta, -alpha, baseDepth)
if depth == DEPTH:
print(evaluation, 'n', self.pythonBoard)
self.pythonBoard.pop()
self.board = fenConverter(self.pythonBoard.board_fen())
if evaluation >= beta:
return beta
if evaluation > alpha:
if depth == DEPTH:
self.move = move
self.materialValue = evaluation
alpha = evaluation
return alpha
Their pseudocode:
int alphaBeta( int alpha, int beta, int depthleft ) {
if( depthleft == 0 ) return quiesce( alpha, beta );
bestValue = -infinity;
for ( all moves) {
score = -alphaBeta( -beta, -alpha, depthleft - 1 );
if( score > bestValue )
{
bestValue = score;
if( score > alpha )
alpha = score; // alpha acts like max in MiniMax
}
if( score >= beta )
return bestValue; // fail soft beta-cutoff, existing the loop here is also fine
}
return bestValue;
}
with this evaluation function:
def evaluate(self, isWhite: bool) -> float:
"""
evaluate evaluates the position
"""
materialValue = 0
squares = list(self.board.keys())
if self.pythonBoard.is_stalemate():
return 0
if self.pythonBoard.outcome() != None:
if self.pythonBoard.is_checkmate():
return float('-inf')
for square in squares:
# if there's a piece on the square
if self.board[square] != '0':
name = self.board[square]
color = findColor(name)
moves = set()
piece = Piece(name, color, 0, moves)
if color == 'black':
if name == 'p':
vMap = pawnMap(square)
elif name == 'n':
vMap = knightMap(square)
elif name == 'b':
vMap = bishopMap(square)
elif name == 'q':
vMap = queenMap(square)
elif name == 'k':
if ((self.whitePieceCount['Q'] == 0 and self.whitePieceCount['R'] <= 1 and self.whitePieceCount['B'] + self.whitePieceCount['N'] <= 2) or
(self.whitePieceCount['B'] + self.whitePieceCount['N'] + self.whitePieceCount['R'] <= 2 and self.whitePieceCount['R'] <= 1)):
vMap = lateKingMap(square)
else:
vMap = earlyKingMap(square)
else:
vMap = rookMap(square)
materialValue += vMap.mapValue()
if color == 'white':
row = int(square[1])
newRow = str(9 - row)
square = square[0] + newRow
if name == 'P':
vMap = pawnMap(square)
elif name == 'N':
vMap = knightMap(square)
elif name == 'B':
vMap = bishopMap(square)
elif name == 'Q':
vMap = queenMap(square)
elif name == 'K':
if ((self.blackPieceCount['q'] == 0 and self.blackPieceCount['r'] <= 1 and self.blackPieceCount['b'] + self.blackPieceCount['n'] <= 2) or
(self.blackPieceCount['b'] + self.blackPieceCount['n'] + self.blackPieceCount['r'] <= 2 and self.blackPieceCount['r'] <= 1)):
vMap = lateKingMap(square)
else:
vMap = earlyKingMap(square)
else:
vMap = rookMap(square)
materialValue += -vMap.mapValue()
materialValue += piece.value * 10
if isWhite:
# materialValue += self.endGameEval(self.blackPieces, isWhite)
return materialValue
else:
# materialValue -= self.endGameEval(self.whitePieces, isWhite)
return -materialValue
Black’s piece values are negative and white’s are positive, so that’s why I return -materialValue
for black. In all positions I tested with depth 1 to see if the evaluate function was wrong, it seemed to be working. All the values it gave were expected. I’m unsure how that could be the problem, but then again, I basically copied the function from the chess programming wiki.
Otherwise, the move ordering is fine (shouldn’t affect the evaluation either way) and it finds all possible moves correctly.
2