/// <summary> /// Gets the list pf PV nodes for the specified bitboard and color. Nodes count is limited by /// <see cref="AIConstants.MaxDepth"/> value to avoid infinite repetitions. /// </summary> /// <param name="bitboard">The initial bitboard.</param> /// <param name="color">The initial color.</param> /// <returns>The list of PV nodes.</returns> private PVNodesList GetPVNodes(Bitboard bitboard, Color color) { var pvNodes = new PVNodesList(); var boardHash = bitboard.GetHashForColor(color); while (_transpositionTable.Exists(boardHash) && pvNodes.Count < AIConstants.MaxDepth) { var pvNode = _transpositionTable.Get(boardHash); if (pvNode.BestMove == null) { break; } pvNodes.Add(pvNode.BestMove); bitboard = bitboard.Move(pvNode.BestMove); color = ColorOperations.Invert(color); boardHash = bitboard.GetHashForColor(color); } return(pvNodes); }
/// <summary> /// Temporary method to calculating best move. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="bestMove">The best possible move from nested nodes.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, AIStats stats) { var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } if (depth <= 0) { stats.EndNodes++; return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, bitboard, bitboard.Moves); var firstMove = true; foreach (var move in availableMoves) { var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); firstMove = false; } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { stats.AlphaBetaCutoffs++; break; } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode(); updateTranspositionNode.Score = bestValue; updateTranspositionNode.Depth = depth; updateTranspositionNode.BestMove = bestMove; if (bestValue <= originalAlpha) { updateTranspositionNode.Type = ScoreType.UpperBound; } else if (bestValue >= beta) { updateTranspositionNode.Type = ScoreType.LowerBound; } else { updateTranspositionNode.Type = ScoreType.Exact; } _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }
/// <summary> /// Regular search, the core of AI algorithms. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="alpha">The alpha value.</param> /// <param name="beta">The beta value.</param> /// <param name="deadline">The deadline (time after which search is immediately terminated).</param> /// <param name="helper">The flag indicating whether the search is an helper or not.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, long deadline, bool helper, AIStats stats) { var root = stats.TotalNodes == 0; var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } #if TRANSPOSITION_TABLE if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } #endif if (depth <= 0) { stats.EndNodes++; #if QUIESCENCE_SEARCH return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); #else bitboard.Calculate(GeneratorMode.CalculateAttacks, false); return(bitboard.GetEvaluation()); #endif } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, depth, bitboard, bitboard.Moves, helper); var firstMove = true; foreach (var move in availableMoves) { if (DateTime.Now.Ticks >= deadline) { break; } if (root) { if (_patternsDetector.IsPattern(bitboard, move)) { continue; } } var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); #if NEGASCOUT firstMove = false; #endif } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, deadline, helper, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { if (move is QuietMove) { _historyTable.AddKiller(color, depth, bestMove); _killerTable.AddKiller(depth, move); } #if ALPHABETA_PRUNNING stats.AlphaBetaCutoffs++; break; #endif } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode { Score = bestValue, Depth = depth, BestMove = bestMove, Type = GetTranspositionNodeType(originalAlpha, beta, bestValue) }; _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }