private async Task <double?> PrincipalVariationSearchAsync(GameBoard gameBoard, int depth, double alpha, double beta, int color, OrderType orderType, CancellationToken token) { double alphaOriginal = alpha; ulong key = gameBoard.ZobristKey; if (TranspositionTable.TryLookup(key, out TranspositionTableEntry tEntry) && tEntry.Depth >= depth) { if (tEntry.Type == TranspositionTableEntryType.Exact) { return(tEntry.Value); } else if (tEntry.Type == TranspositionTableEntryType.LowerBound) { alpha = Math.Max(alpha, tEntry.Value); } else if (tEntry.Type == TranspositionTableEntryType.UpperBound) { beta = Math.Min(beta, tEntry.Value); } if (alpha >= beta) { return(tEntry.Value); } } if (depth == 0 || gameBoard.GameIsOver) { return(await QuiescenceSearchAsync(gameBoard, QuiescentSearchMaxDepth, alpha, beta, color, token)); } double?bestValue = null; Move bestMove = tEntry?.BestMove; List <Move> moves = GetPreSortedValidMoves(gameBoard, bestMove); bool firstMove = true; foreach (Move move in moves.GetEnumerableByOrderType(orderType)) { bool updateAlpha = false; if (token.IsCancellationRequested) { return(null); } double?value = null; gameBoard.TrustedPlay(move); if (firstMove) { // Full window search value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, orderType, token); updateAlpha = true; firstMove = false; } else { // Null window search value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -alpha - double.Epsilon, -alpha, -color, orderType, token); if (value.HasValue && value > alpha && value < beta) { // Research with full window value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, orderType, token); updateAlpha = true; } } gameBoard.UndoLastMove(); if (!value.HasValue) { return(null); } if (updateAlpha) { alpha = Math.Max(alpha, value.Value); } if (!bestValue.HasValue || value >= bestValue) { bestValue = value; bestMove = move; } if (bestValue >= beta) { break; } } if (bestValue.HasValue) { tEntry = new TranspositionTableEntry(); if (bestValue <= alphaOriginal) { tEntry.Type = TranspositionTableEntryType.UpperBound; } else { tEntry.Type = bestValue >= beta ? TranspositionTableEntryType.LowerBound : TranspositionTableEntryType.Exact; tEntry.BestMove = bestMove; } tEntry.Value = bestValue.Value; tEntry.Depth = depth; TranspositionTable.Store(key, tEntry); } return(bestValue); }
private async Task <EvaluatedMoveCollection> EvaluateMovesToDepthAsync(GameBoard gameBoard, int depth, IEnumerable <EvaluatedMove> movesToEvaluate, CancellationToken token) { double alpha = double.NegativeInfinity; double beta = double.PositiveInfinity; int color = gameBoard.CurrentTurnColor == PlayerColor.White ? 1 : -1; double alphaOriginal = alpha; double?bestValue = null; EvaluatedMoveCollection evaluatedMoves = new EvaluatedMoveCollection(); bool firstMove = true; foreach (EvaluatedMove moveToEvaluate in movesToEvaluate) { bool updateAlpha = false; if (token.IsCancellationRequested) { // Cancel return(new EvaluatedMoveCollection(movesToEvaluate, false)); } gameBoard.TrustedPlay(moveToEvaluate.Move); double?value = null; if (firstMove) { // Full window search value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, OrderType.Default, token); updateAlpha = true; firstMove = false; } else { // Null window search value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -alpha - double.Epsilon, -alpha, -color, OrderType.Default, token); if (value.HasValue && value > alpha && value < beta) { // Research with full window value = -1 * await PrincipalVariationSearchAsync(gameBoard, depth - 1, -beta, -alpha, -color, OrderType.Default, token); updateAlpha = true; } } gameBoard.UndoLastMove(); if (!value.HasValue) { // Cancel occurred during evaluation return(new EvaluatedMoveCollection(movesToEvaluate, false)); } EvaluatedMove evaluatedMove = new EvaluatedMove(moveToEvaluate.Move, value.Value, depth); evaluatedMoves.Add(evaluatedMove); if (updateAlpha) { alpha = Math.Max(alpha, value.Value); } if (!bestValue.HasValue || value >= bestValue) { bestValue = value; } if (bestValue >= beta) { // A winning move has been found, since beta is always infinity in this function break; } } ulong key = gameBoard.ZobristKey; TranspositionTableEntry tEntry = new TranspositionTableEntry(); if (bestValue <= alphaOriginal) { // Losing move since alphaOriginal os negative infinity in this function tEntry.Type = TranspositionTableEntryType.UpperBound; } else { // Move is a lower bound winning move if bestValue >= beta (always infinity in this function), otherwise it's exact tEntry.Type = bestValue >= beta ? TranspositionTableEntryType.LowerBound : TranspositionTableEntryType.Exact; tEntry.BestMove = evaluatedMoves.BestMove.Move; } tEntry.Value = bestValue.Value; tEntry.Depth = depth; TranspositionTable.Store(key, tEntry); return(evaluatedMoves); }