public bool IsRepetition(int move) { if (!EngineConstants.EnableRepetitionTable) { return(false); } // if move was an attacking-move or pawn move, no repetition if (!MoveUtil.IsQuiet(move) || MoveUtil.GetSourcePieceIndex(move) == Pawn) { return(false); } var moveCountMin = Math.Max(0, MoveCounter - 50); for (var i = MoveCounter - 2; i >= moveCountMin; i -= 2) { if (ZobristKey != ZobristKeyHistory[i]) { continue; } if (Statistics.Enabled) { Statistics.Repetitions++; } return(true); } return(false); }
public static int CalculateBestMove(ChessBoard cb, ThreadData threadData, int ply, int depth, int alpha, int beta, int nullMoveCounter) { if (!IsRunning) { return(ChessConstants.ScoreNotRunning); } if (EngineConstants.Assert) { Assert.IsTrue(depth >= 0); Assert.IsTrue(alpha >= Util.ShortMin && alpha <= Util.ShortMax); Assert.IsTrue(beta >= Util.ShortMin && beta <= Util.ShortMax); } var alphaOrig = alpha; // get extensions depth += Extensions(cb); /* mate-distance pruning */ if (EngineConstants.EnableMateDistancePruning) { alpha = Math.Max(alpha, Util.ShortMin + ply); beta = Math.Min(beta, Util.ShortMax - ply - 1); if (alpha >= beta) { return(alpha); } } // TODO JITWatch unpredictable branch if (depth == 0) { return(QuiescenceUtil.CalculateBestMove(cb, threadData, alpha, beta)); } /* transposition-table */ var ttEntry = TtUtil.GetEntry(cb.ZobristKey); var score = ttEntry.GetScore(ply); if (ttEntry.Key != 0) { if (!EngineConstants.TestTtValues) { if (ttEntry.Depth >= depth) { switch (ttEntry.Flag) { case TtUtil.FlagExact: return(score); case TtUtil.FlagLower: if (score >= beta) { return(score); } break; case TtUtil.FlagUpper: if (score <= alpha) { return(score); } break; } } } } if (Statistics.Enabled) { Statistics.AbNodes++; } var eval = Util.ShortMin; var isPv = beta - alpha != 1; if (!isPv && cb.CheckingPieces == 0) { eval = EvalUtil.GetScore(cb, threadData); /* use tt value as eval */ if (EngineConstants.UseTtScoreAsEval) { if (TtUtil.CanRefineEval(ttEntry, eval, score)) { eval = score; } } /* static null move pruning */ if (EngineConstants.EnableStaticNullMove && depth < StaticNullmoveMargin.Length) { if (eval - StaticNullmoveMargin[depth] >= beta) { if (Statistics.Enabled) { Statistics.StaticNullMoved[depth]++; } return(eval); } } /* razoring */ if (EngineConstants.EnableRazoring && depth < RazoringMargin.Length && Math.Abs(alpha) < EvalConstants.ScoreMateBound) { if (eval + RazoringMargin[depth] < alpha) { score = QuiescenceUtil.CalculateBestMove(cb, threadData, alpha - RazoringMargin[depth], alpha - RazoringMargin[depth] + 1); if (score + RazoringMargin[depth] <= alpha) { if (Statistics.Enabled) { Statistics.Razored[depth]++; } return(score); } } } /* null-move */ if (EngineConstants.EnableNullMove) { if (nullMoveCounter < 2 && eval >= beta && MaterialUtil.HasNonPawnPieces(cb.MaterialKey, cb.ColorToMove)) { cb.DoNullMove(); // TODO less reduction if stm (other side) has only 1 major piece var reduction = depth / 4 + 3 + Math.Min((eval - beta) / 80, 3); score = depth - reduction <= 0 ? -QuiescenceUtil.CalculateBestMove(cb, threadData, -beta, -beta + 1) : -CalculateBestMove(cb, threadData, ply + 1, depth - reduction, -beta, -beta + 1, nullMoveCounter + 1); cb.UndoNullMove(); if (score >= beta) { if (Statistics.Enabled) { Statistics.NullMoveHit++; } return(score); } if (Statistics.Enabled) { Statistics.NullMoveMiss++; } } } } var parentMove = ply == 0 ? 0 : threadData.Previous(); var bestMove = 0; var bestScore = Util.ShortMin - 1; var ttMove = 0; var counterMove = 0; var killer1Move = 0; var killer2Move = 0; var movesPerformed = 0; threadData.StartPly(); var phase = PhaseTt; while (phase <= PhaseQuiet) { switch (phase) { case PhaseTt: if (ttEntry.Key != 0) { ttMove = ttEntry.Move; if (cb.IsValidMove(ttMove)) { threadData.AddMove(ttMove); } // else { // throw new RuntimeException("invalid tt-move found: " + new MoveWrapper(ttMove)); // } } break; case PhaseAttacking: MoveGenerator.GenerateAttacks(threadData, cb); threadData.SetMvvlvaScores(); threadData.Sort(); break; case PhaseKiller1: killer1Move = threadData.GetKiller1(ply); if (killer1Move != 0 && killer1Move != ttMove && cb.IsValidMove(killer1Move)) { threadData.AddMove(killer1Move); break; } phase++; goto case PhaseKiller2; case PhaseKiller2: killer2Move = threadData.GetKiller2(ply); if (killer2Move != 0 && killer2Move != ttMove && cb.IsValidMove(killer2Move)) { threadData.AddMove(killer2Move); break; } phase++; goto case PhaseCounter; case PhaseCounter: counterMove = threadData.GetCounter(cb.ColorToMove, parentMove); if (counterMove != 0 && counterMove != ttMove && counterMove != killer1Move && counterMove != killer2Move && cb.IsValidMove(counterMove)) { threadData.AddMove(counterMove); break; } phase++; goto case PhaseQuiet; case PhaseQuiet: MoveGenerator.GenerateMoves(threadData, cb); threadData.SetHhScores(cb.ColorToMove); threadData.Sort(); break; } while (threadData.HasNext()) { var move = threadData.Next(); switch (phase) { case PhaseQuiet when move == ttMove || move == killer1Move || move == killer2Move || move == counterMove || !cb.IsLegal(move): case PhaseAttacking when move == ttMove || !cb.IsLegal(move): continue; } // pruning allowed? if (!isPv && cb.CheckingPieces == 0 && movesPerformed > 0 && threadData.GetMoveScore() < 100 && !cb.IsDiscoveredMove(MoveUtil.GetFromIndex(move))) { if (phase == PhaseQuiet) { /* late move pruning */ if (EngineConstants.EnableLmp && depth <= 4 && movesPerformed >= depth * 3 + 3) { if (Statistics.Enabled) { Statistics.Lmped[depth]++; } continue; } /* futility pruning */ if (EngineConstants.EnableFutilityPruning && depth < FutilityMargin.Length) { if (!MoveUtil.IsPawnPush78(move)) { if (eval == Util.ShortMin) { eval = EvalUtil.GetScore(cb, threadData); } if (eval + FutilityMargin[depth] <= alpha) { if (Statistics.Enabled) { Statistics.Futile[depth]++; } continue; } } } } /* SEE Pruning */ else if (EngineConstants.EnableSeePruning && depth <= 6 && phase == PhaseAttacking && SeeUtil.GetSeeCaptureScore(cb, move) < -20 * depth * depth) { continue; } } cb.DoMove(move); movesPerformed++; /* draw check */ if (cb.IsRepetition(move) || MaterialUtil.IsDrawByMaterial(cb)) { score = EvalConstants.ScoreDraw; } else { score = alpha + 1; // initial is above alpha var reduction = 1; if (depth > 2 && movesPerformed > 1 && MoveUtil.IsQuiet(move) && !MoveUtil.IsPawnPush78(move)) { reduction = LmrTable[Math.Min(depth, 63)][Math.Min(movesPerformed, 63)]; if (threadData.GetMoveScore() > 40) { reduction -= 1; } if (move == killer1Move || move == killer2Move || move == counterMove) { reduction -= 1; } if (!isPv) { reduction += 1; } reduction = Math.Min(depth - 1, Math.Max(reduction, 1)); } /* LMR */ if (EngineConstants.EnableLmr && reduction != 1) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - reduction, -alpha - 1, -alpha, 0); } /* PVS */ if (EngineConstants.EnablePvs && score > alpha && movesPerformed > 1) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - 1, -alpha - 1, -alpha, 0); } /* normal bounds */ if (score > alpha) { score = -CalculateBestMove(cb, threadData, ply + 1, depth - 1, -beta, -alpha, 0); } } cb.UndoMove(move); if (score > bestScore) { bestScore = score; bestMove = move; if (ply == 0 && IsRunning) { threadData.SetBestMove(cb, bestMove, alphaOrig, beta, bestScore, depth); } alpha = Math.Max(alpha, score); if (alpha >= beta) { if (Statistics.Enabled) { Statistics.FailHigh[Math.Min(movesPerformed - 1, Statistics.FailHigh.Length - 1)]++; } /* killer and history */ if (MoveUtil.IsQuiet(move) && cb.CheckingPieces == 0) { threadData.AddCounterMove(cb.ColorToMove, parentMove, move); threadData.AddKillerMove(move, ply); threadData.AddHhValue(cb.ColorToMove, move, depth); } phase += 10; break; } } if (MoveUtil.IsQuiet(move)) { threadData.AddBfValue(cb.ColorToMove, move, depth); } } phase++; } threadData.EndPly(); /* checkmate or stalemate */ if (movesPerformed == 0) { if (cb.CheckingPieces == 0) { if (Statistics.Enabled) { Statistics.StaleMateCount++; } return(EvalConstants.ScoreDraw); } if (Statistics.Enabled) { Statistics.MateCount++; } return(Util.ShortMin + ply); } if (EngineConstants.Assert) { Assert.IsTrue(bestMove != 0); } // set tt-flag var flag = TtUtil.FlagExact; if (bestScore >= beta) { flag = TtUtil.FlagLower; } else if (bestScore <= alphaOrig) { flag = TtUtil.FlagUpper; } if (IsRunning) { TtUtil.AddValue(cb.ZobristKey, bestScore, ply, depth, flag, bestMove); } Statistics.SetBestMove(cb, bestMove, ttMove, ttEntry, flag, counterMove, killer1Move, killer2Move); if (EngineConstants.TestTtValues) { SearchTestUtil.TestTtValues(score, bestScore, depth, bestMove, flag, ttEntry, ply); } return(bestScore); }