niel5946 wrote: ↑Fri Jun 11, 2021 9:48 am
I don't really think a - relatively - slow move generator is a big problem for chess engines under 2500-3000 elo. My engine, Loki, has the same perft time at depth 4 (kiwipete) as JVMerlino's, using a pseudo-legal move generator and no bulk counting. I have already gotten it to play at 2400-2500 on CCRL without having changed the move generator iirc.
Having said that, a lightning fast move generator is nice
Although your board representation has a tendency to be on the slow side (note tendency: it can be made really fast) as compared to something like bitboard representations, another really big factor in perft speed is the make move and undo move functions. How are these written? And what about the move generator itself? Additionally, what language are you using? I'm not familiar with the syntax in your code example.
If you are satisfied with their current implementations and are only looking for low-level speedups, I recommend reading through
this CPW page on optimization
Well my engine is written in Golang actually, which I realize isn't the best language for writing a fast chess engine in, but I'm not looking for the fastest engine, I just want something that has reasonable move generation speed.
And the way DoMove and UndoMove are written is by having a chain of if statements dealing with the different move types that are generated. Here are some snippets of DoMove:
Code: Select all
if move.MoveType == CastleWKS {
doCastling(board, 4, 6, 7, 5)
board.CanCastleWKS = false
board.CanCastleWQS = false
} else if move.MoveType == CastleWQS {
doCastling(board, 4, 2, 0, 3)
board.CanCastleWQS = false
board.CanCastleWKS = false
} else if move.MoveType == CastleBKS {
doCastling(board, 60, 62, 63, 61)
board.CanCastleBKS = false
board.CanCastleBQS = false
} else if move.MoveType == CastleBQS {
doCastling(board, 60, 58, 56, 59)
board.CanCastleBQS = false
board.CanCastleBKS = false
...
pieceType := board.Pieces[move.From]
pieceColor := board.Colors[move.From]
captureType := board.Pieces[move.To]
captureColor := board.Colors[move.To]
board.Pieces[move.From] = NoPiece
board.Colors[move.From] = NoColor
board.Pieces[move.To] = pieceType
board.Colors[move.To] = pieceColor
undoMove := UndoMove{PieceColor: pieceColor, PieceType: pieceType, CaptureColor: captureColor, CaptureType: captureType}
undoMove.CanCastleWKS = board.CanCastleWKS
undoMove.CanCastleWQS = board.CanCastleWQS
undoMove.CanCastleBKS = board.CanCastleBKS
undoMove.CanCastleBQS = board.CanCastleBQS
// Check if there is now a en passant target square
if pieceType == Pawn && moveIsDoublePawnPush(move) {
board.EPSquare = move.To + 8
if pieceColor == White {
board.EPSquare = move.To - 8
}
}
// Update king positions
if pieceType == King && pieceColor == White {
board.WhiteKingPos = move.To
} else if pieceType == King && pieceColor == Black {
board.BlackKingPos = move.To
}
// Update the castling rights. Only do this AFTER we've already saved them.
if pieceType == King && pieceColor == White {
board.CanCastleWKS = false
board.CanCastleWQS = false
} else if pieceType == King && pieceColor == Black {
board.CanCastleBKS = false
board.CanCastleBQS = false
} else if pieceType == Rook && pieceColor == White {
if move.From == 7 {
board.CanCastleWKS = false
} else if move.From == 0 {
board.CanCastleWQS = false
}
} else if pieceType == Rook && pieceColor == Black {
if move.From == 63 {
board.CanCastleBKS = false
} else if move.From == 56 {
board.CanCastleBQS = false
}
}
// If a rook is taken, make sure to update the castling rights.
if captureType == Rook && captureColor == White {
if move.To == 7 {
board.CanCastleWKS = false
} else if move.To == 0 {
board.CanCastleWQS = false
}
} else if captureType == Rook && captureColor == Black {
if move.To == 63 {
board.CanCastleBKS = false
} else if move.To == 56 {
board.CanCastleBQS = false
}
}
board.UndoMoves = append(board.UndoMoves, undoMove)
}
The full function I feel is much to do big to post here (which might be an indication of what the issue is lol), but if it's fine to do so, then I
l'll gladly post DoMove and UndoMove.
As for the move generator, I'm using the 10x12 board representation as you already know, and deltas to go through and calculate the pseudo legal moves. For example, here is my function to calculate pseudo legal slider moves:
Code: Select all
func computeMovesForSlider(board *Board, from int, enemyColor Color, deltas []int) (moves []Move) {
for _, delta := range deltas {
to := Mailbox120[Mailbox64[from]+delta]
for to != -1 && board.Colors[to] == NoColor {
moves = append(moves, Move{From: from, To: to, MoveType: Quiet})
to = Mailbox120[Mailbox64[to]+delta]
}
if to != -1 && board.Colors[to] == enemyColor {
moves = append(moves, Move{From: from, To: to, MoveType: Attack})
}
}
return moves
}
Where I'm running into an issue is move generation for pawns. All the other pieces move generation was relatively simple, but because of pawns' weirdness (en passant, double pushing, capturing diagonally but move straight), my function for calculating pawn moves is rough ~100 lines of code, which again, I feel like is indicative of the problem, but I'm not sure how to shorten the logic. I've already gone back and tried to get rid of repeated logic and messy, inefficient code.