I have gotten into the part of scoring/sorting moves and I find that with the approach of having the move as a ulong it makes it very consuming to score and sort the moves. It gives barely no difference in speed, when it gave wonders in speed when I didn't use bitboards in my previous engine.
The approach of sorting is something like this at the moment, taken basically straight from my previous engine:
Code: Select all
private static List<ulong> SortMoves(int ply, List<ulong> moves)
{
// Assign a score to each move
List<int> scoreList = new List<int>();
foreach (ulong move in moves)
{
scoreList.Add(ScoreMove(ply, move));
}
// Sort the moves based on their score
List<ulong> sortedMoves = moves.Zip(scoreList,
(move, score) => (Move: move, Score: score))
.OrderByDescending(move => move.Score)
.Select(move => move.Move)
.ToList();
// Return the new list of moves
return sortedMoves;
}
private static int ScoreMove(int ply, ulong move)
{
// PV scoring if allowed
if (scorePV)
{
// Make sure we have a PV move
if (pvTable[0][ply] == move)
{
// Disable PV flag and return the PV score
scorePV = false;
return SearchConstants.pvScore;
}
}
// Capture moves (or enpassants)
if (Move.IsCapture((int)Move.GetMoveFlag(move)) || Move.IsEnPassant((int)Move.GetMoveFlag(move)))
{
int _pieceCaptured = -1;
if (Move.IsEnPassant((int)Move.GetMoveFlag(move)))
{
_pieceCaptured = Piece.Pawn;
}
else
{
// Loop over other sides bitboards to find the piece
int toSquare = (int)Move.GetToSquare(move);
for (int piece = Piece.Pawn; piece <= Piece.King; piece++)
{
if (BitOperations.GetBit(boardState.Pieces[Color.Invert(boardState.ColorToMove)][piece], toSquare) != 0)
{
_pieceCaptured = piece;
break;
}
}
}
return SearchConstants.MVV_LVA +
SearchConstants.MVV_LVA_Values[_pieceCaptured] -
SearchConstants.MVV_LVA_Values[(int)Move.GetPieceMoved(move)];
}
// Quiet moves
else
{
// Killer move 1 or 2, else return the history move
if (killerMoves[0][ply] == move) return SearchConstants.firstKillerMove;
else if (killerMoves[1][ply] == move) return SearchConstants.secondKilleRMove;
else return historyMoves[(int)Move.GetPieceMoved(move)][(int)Move.GetToSquare(move)];
}
Code: Select all
private static float Negamax(int depth, int ply, float alpha, float beta)
{
float score;
bool foundPV = false;
// Init PV
pvTable[ply][ply] = 0ul;
pvLength[ply] = 0;
// Find if it is a 3 fold repetition
if (boardState.CheckForDraw()) return 0;
// Check if we reached depth 0
if (depth == 0)
{
score = Evaluate.EvaluateOnlyMaterial(boardState);
return score;
}
// Get legal moves
boardState.GetAllMoves();
List<ulong> children = new List<ulong>(boardState.PossibleMoves);
// Sort moves
children = SortMoves(ply, children);
// If we are following PV line we want to enable PV scoring
if (followPV) EnablePVScoring(ply, children);
// Init number of legal moves and moves searched
int legalMoves = 0;
// Negamax loop
foreach (ulong move in children)
{
// Try if move is legal
if (boardState.MakeMove(move))
{
// Increase legal moves and node count
legalMoves++;
nodes++;
// Do a normal search
score = -Negamax(depth - 1, ply + 1, -beta, -alpha);
// Unmake the move
boardState.UnmakeMove(move);
// Found a better move
if (score > alpha)
{
foundPV = true;
alpha = score;
// History moves (non capturing and not enpassant)
if (!Move.IsCapture((int)Move.GetMoveFlag(move)) && !Move.IsEnPassant((int)Move.GetMoveFlag(move)))
{
historyMoves[(int)Move.GetPieceMoved(move)][(int)Move.GetToSquare(move)] += depth;
}
// Write PV move to PV table for the given ply
pvTable[ply][ply] = move;
// Loop over the next ply
for (int i = 0; i < pvLength[ply + 1]; i++)
{
// Copy move from deeper ply into current ply's line
pvTable[ply][ply + 1 + i] = pvTable[ply + 1][ply + 1 + i];
}
// Adjust PV lenght
pvLength[ply] = 1 + pvLength[ply + 1];
// Fail hard beta cutoff (node fails high)
if (score >= beta)
{
// Store killer moves (non capturing and not enpassant)
if (!Move.IsCapture((int)Move.GetMoveFlag(move)) && !Move.IsEnPassant((int)Move.GetMoveFlag(move)))
{
killerMoves[1][ply] = killerMoves[0][ply];
killerMoves[0][ply] = move;
}
return beta;
}
}
}
}
Another thing is on all the places where I need to check if the move is capture or enpassant. Also I need to loop over the enemy bitboards to find the captured piece since I don't store this either somewhere.
I am a bit stuck on how to do the sorting the fastest way possible. Any general ideas or inspiration that you can give me? How to think of scoring/sorting moves when they are represented as a ulong?