Rein,
My use of the yield statement is in code that's not ready for release. Here are some code snippets to give you sense of how it works.
I'll show the capture-only enumerator because it's simpler.
Code: Select all
public override IEnumerator<Move> GetEnumerator()
{
// Examine all pieces of the side that just moved in order of piece value descending.
Moves colMoves;
// Captures of queens
colMoves = this.GetCapturesOfPieces(this.Board.Pieces.Queens(this.Board.SideJustMoved));
foreach (Move objMove in colMoves)
{
// Select move.
yield return objMove;
}
// Captures of rooks
colMoves = this.GetCapturesOfPieces(this.Board.Pieces.Rooks(this.Board.SideJustMoved));
foreach (Move objMove in colMoves)
{
// Select move.
yield return objMove;
}
// Captures of bishops and knights
colMoves = this.GetCapturesOfPieces(this.Board.Pieces.Minors(this.Board.SideJustMoved));
foreach (Move objMove in colMoves)
{
// Select move.
yield return objMove;
}
// Captures of pawns
colMoves = this.GetCapturesOfPieces(this.Board.Pieces.Pawns(this.Board.SideJustMoved));
foreach (Move objMove in colMoves)
{
// Select move.
yield return objMove;
}
}
Find attackers of a piece.
Code: Select all
private Moves GetCapturesOfPieces(List<Piece> Pieces)
{
Moves colMoves = new Moves();
foreach (Piece objPiece in Pieces)
{
// Determine from which directions piece can be attacked.
List<Direction> colAttackDirections = this.Board.GetAttackDirections(objPiece.Square, this.Board.SideToMove);
// Get attackers.
List<Piece> colAttackers = this.Board.GetAttackers(objPiece.Square, this.Board.SideToMove, colAttackDirections, false);
foreach (Piece objAttacker in colAttackers)
{
// Create move for attacker.
Move objMove;
int intLocationValue = objAttacker.GetLocationValue(objPiece.Square) - objAttacker.GetLocationValue(objAttacker.Square);
int intMaterialGainIfRecaptured = objPiece.MaterialValue - objAttacker.MaterialValue;
if ((objAttacker.ShortName == Piece.Pawn) && (objAttacker.On7thRank))
{
// Code snipped for pawn promotion
}
else
{
objMove = new Move(objAttacker.Square.Location, objPiece.Square.Location, objAttacker.ShortName,
intLocationValue, true, objPiece.MaterialValue, intMaterialGainIfRecaptured);
colMoves.Add(objMove);
}
}
}
if ((Pieces.Count > 0) && (Pieces[0].ShortName == Piece.Pawn) && (this.Board.EnPassantSquare != null))
{
Square objSquare = this.Board.EnPassantSquare.GetNeighbor(Direction.BehindLeft);
if (objSquare.IsLegal && objSquare.IsOwnOccupied && (objSquare.Piece.ShortName == Piece.Pawn))
{
// Code snipped for en passant capture.
}
}
foreach (Move objMove in colMoves)
{
// Prioritize move.
this.PrioritizeMove(objMove);
}
// Sort moves.
colMoves.Sort(this.MoveComparer);
// Update node count.
this.SearchStats.Nodes += colMoves.Count;
return colMoves;
}
The quiescence search uses the capture enumerator (this.GetCaptureSelector(SearchState, SearchDepth)). The foreach code invokes the enumerator, which runs until it encounters a yield return statement. At that point control is returned to the caller (the quiescence foreach loop). So if a capture of a queen causes a beta cutoff, the code does not waste time finding captures of rooks, bishops, etc. Of course this logic is possible using other techniques. The yield statement makes the code very clean. The enumerator does not have to track state (which pieces have been examined). Tracking state in the all-moves enumerator is more complex (Has the hash move been returned? Pawn promotions? Captures? Killers?)
Code: Select all
public override int RecurseQuiet(SearchState SearchState, SearchDepth SearchDepth, int Alpha, int Beta)
{
if (SearchState == null)
{
throw new ArgumentException("SearchState is null.");
}
if (SearchDepth == null)
{
throw new ArgumentException("SearchDepth is null.");
}
// Examine search state.
this.ExamineSearchState(SearchState);
if (!SearchState.Continue)
{
// Search was interrupted.
return SearchState.Evaluation.InterruptedSearchScore;
}
// Search for a quiet position where no captures are possible.
// Update selective depth.
SearchState.SearchStats.SelectiveDepth = Math.Max(SearchDepth.Depth, SearchState.SearchStats.SelectiveDepth);
// Update nodes evaluated count.
SearchState.SearchStats.NodesEvaluated++;
// Determine static score.
StaticScore objStaticScore = SearchState.Evaluation.GetStaticScore(SearchState, SearchDepth, false, Alpha, Beta);
if (objStaticScore.Score >= Beta)
{
// Prevent worsening of position by making a bad capture. Stand pat.
// Beta cutoff
return Beta;
}
Alpha = Math.Max(objStaticScore.Score, Alpha);
// Initialize search stats.
int intLegalCapture = 0;
int intScore = Alpha - 1;
foreach (Move objMove in this.GetCaptureSelector(SearchState, SearchDepth))
{
if (!objMove.IsLegal.HasValue)
{
// Determine if move is legal.
objMove.IsLegal = SearchState.Board.IsMoveLegal(objMove);
}
if (objMove.IsLegal == true)
{
intLegalCapture++;
}
else
{
// Skip illegal move.
continue;
}
// Determine if move is futile.
if (this.IsMoveFutile(SearchState, SearchDepth, objMove, intLegalCapture, objStaticScore, Alpha, Beta))
{
// Move is too weak.
SearchState.SearchStats.NodesPrunedFutility++;
// Skip move.
continue;
}
// Play move.
SearchState.Board.PlayMove(objMove);
// Search with full alpha / beta window.
intScore = -this.RecurseQuiet(SearchState, SearchDepth.MoveTowardsHorizon(), -Beta, -Alpha);
// Undo move.
SearchState.Board.UndoMove();
if (intScore >= Beta)
{
// Position is not the result of best play by both players.
SearchState.SearchStats.NodesPrunedAlphaBeta++;
// Beta cutoff
return Beta;
}
Alpha = Math.Max(intScore, Alpha);
}
// Return score of best move.
return Alpha;
}