private void SaveNode(ulong hash, TranspositionNode existingNode, TranspositionNode newNode) { if (existingNode != null) { transpositionTable.Update(hash, existingNode, newNode); } else { transpositionTable.Add(hash, newNode); } }
private int Search(int depth, int ply, int alpha, int beta, PVList pvList, bool nullMoveReduction) { if (ct.IsCancellationRequested) { return(0); } searchStats.Nodes++; if (gameState.IsDraw()) { int score = evaluator.GetDrawScore(engine.EngineColor); if (score > alpha) { pvList.Replace(new PVList()); } return(score); } if (gameState.IsCheck() && depth < ArtemisEngine.MAX_DEPTH) { //check extension depth += 1; } int originalAlpha = alpha; ulong hash = gameState.GetIrrevState().ZobristHash; TTHit ttHit = transpositionTable.TryGetValue(hash, depth, alpha, beta, pvList); if (ttHit.HitType == HitType.Hit) { searchStats.TTHits++; return(ttHit.Score); } TranspositionNode ttNode = ttHit.TTNode; if (depth <= 0 || ply == ArtemisEngine.MAX_DEPTH) { int score = quietSearch.Search(alpha, beta); if (score > alpha) { pvList.Replace(new PVList()); } return(score); } Move bestMove = null; bool cutoff = false; PVList newPV = new PVList(); bool PVNode = alpha != beta - 1; if (!PVNode && ttHit.HitType != HitType.AvoidNullMove && !nullMoveReduction && engine.GameStage != GameStage.Endgame && !gameState.IsCheck()) { //null move pruning gameState.MakeNullMove(); int nextDepth = depth - 1 - config.NullMoveDepthReduction; int score = -Search(nextDepth, ply + 1, -beta, -beta + 1, newPV, true); gameState.UnmakeNullMove(); if (score >= beta) { searchStats.NullMoveCutoffs++; searchStats.AlphaBetaCutoffs++; TranspositionNode newNode = new TranspositionNode(NodeType.CutNode, score, nextDepth + 1, null, null); SaveNode(hash, ttNode, newNode); //alpha-beta cutoff return(beta); } } List <Move> moves = gameState.GetMoves(); Move pvMove = null; if (PVNode) { if (currentPVNode != null) { pvMove = currentPVNode.Move; currentPVNode = currentPVNode.Next; } else { PVNode = false; } } Move hashMove = null; if (ttNode != null) { hashMove = ttNode.BestMove; } Move[] killers = killerMoves.GetKillerMoves(ply); moves = moves.OrderByDescending(m => moveEvaluator.EvaluateMove(m, pvMove, hashMove, killers).Score).ToList(); int originalLen = moves.Count; int moveCount = 0; bool lmrCandidatePosition = depth >= 3 && !PVNode && !gameState.IsCheck(); for (int i = 0; i < moves.Count && !cutoff; i++) { Move move = moves[i]; gameState.MakeMove(move); if (move.IsLegal()) { int nextDepth = depth - 1; bool lmrReduction = false; if (lmrCandidatePosition && moveCount >= 4 && move.IsQuiet() && !gameState.IsCheck()) { lmrReduction = true; nextDepth--; searchStats.LMRReductions++; } int score; if (moveCount == 0 || searchDepth == 1) { score = -Search(nextDepth, ply + 1, -beta, -alpha, newPV, false); } else { ulong moveHash = gameState.GetIrrevState().ZobristHash; if (!config.Multithreading || i >= originalLen || searchedNodes.TryAdd(moveHash, true)) { score = -Search(nextDepth, ply + 1, -alpha - 1, -alpha, newPV, false); searchedNodes.TryRemove(moveHash, out _); if (score > alpha) { if (lmrReduction) { nextDepth++; } score = -Search(nextDepth, ply + 1, -beta, -alpha, newPV, false); } } else { moves.Add(move); gameState.UnmakeMove(move); continue; } } if (score >= beta) { //alpha-beta cutoff searchStats.AlphaBetaCutoffs++; alpha = beta; bestMove = move; cutoff = true; killerMoves.AddMove(move, ply); } else if (score > alpha) { alpha = score; bestMove = move; } moveCount++; } gameState.UnmakeMove(move); } if (moveCount == 0) { //player has no legal moves if (gameState.IsCheck()) { return(-PositionEvaluator.CHECKMATE_SCORE - depth); } else { return(0); } } NodeType nodeType = GetNodeType(originalAlpha, beta, alpha); PVList nodePV = null; if (nodeType == NodeType.PVNode) { searchStats.PVNodes++; PVNode node = new PVNode(bestMove); newPV.AddFirst(node); nodePV = newPV; pvList.Replace(newPV); } TranspositionNode updatedNode = new TranspositionNode(nodeType, alpha, depth, bestMove, nodePV); SaveNode(hash, ttNode, updatedNode); return(alpha); }
/// <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); }