int SearchCut(int alpha, int beta, int depth, int height, int mate_threat, bool allowNull = true) { beta = Math.Min(beta, G.MATE - height); if (depth <= 0) { if (stepList[stepList.Count - 1].move.checking) { //被照将时,推迟进入静态搜索 depth++; } else { return(SearchQuiesce(alpha, beta, 0, height)); } } stat.ZeroWindowNodes++; int best = HarmlessPruning(height); if (best >= beta) { return(best); } #if USE_HASH HashStruct t = TT.ReadHash(Key); if (t.Move.sqSrc == 0) { TransKiller = null; } else { if (t.AlphaDepth >= depth) { if (t.Alpha <= alpha) { return(t.Alpha); } } if (t.BetaDepth >= depth) { if (t.Beta >= beta) { return(t.Beta); } } if (t.Alpha == t.Beta) { return(t.Alpha); } TransKiller = t.Move; TransKiller.pcSrc = pcSquares[TransKiller.sqSrc]; TransKiller.pcDst = pcSquares[TransKiller.sqDst]; } #endif Debug.Assert(height < G.MAX_PLY); MOVE lastMove = stepList[stepList.Count - 1].move; #if NULL_MOVE if (allowNull && depth >= G.NullDepth) { //for any opponent pure loss and bad capture, it's better to take it and see the accurate score //for any opponent good capture, we are already so bad, it makes no sense to do null move //for those equal material exchange, we must finish recapture //for mating and checking moves, we can not skip the moves if (!lastMove.checking && lastMove.score > HistoryScore && lastMove.score < GoodScore && lastMove.pcDst == 0 && Math.Abs(beta) < G.WIN) { MakeNullMove(); int vl = -SearchCut(-beta, -alpha, depth - G.NullReduction - 1, height + 1, mate_threat, false); UnmakeNullMove(); #if NULL_VERIFICATION if (depth > G.VerReduction && vl >= beta) { vl = SearchCut(beta, depth - G.VerReduction, height + 1, false); } #endif if (vl >= beta) { Debug.Assert(vl < G.WIN); // do not return unproven mates TT.WriteHash(Key, G.HASH_BETA, vl, depth, new MOVE()); stat.NullCutoffs++; return(vl); } } } #endif IEnumerable <MOVE> moves = GetNextMove(7, height, mate_threat); MOVE mvBest = new MOVE(); int opt_value = G.MATE; List <MOVE> mvPlayed = new List <MOVE>(); foreach (MOVE mv in moves) { int new_depth = depth - 1; if (mv.checking) { new_depth++; } if (mate_threat < 0) { if (best < -G.WIN && mvPlayed.Count > 0) { mate_threat = height; alpha = -G.WIN; } } else { MOVE matekiller = Killers[mate_threat + 1, 0]; if (mv == matekiller || mate_threat == height && best > -G.WIN || !IsLegalMove(matekiller.sqSrc, matekiller.sqDst)) { mate_threat = -1; } } #if HISTORY_PRUNING bool reduced = false; if (depth >= G.HistoryDepth && !lastMove.checking && new_depth < depth && mvPlayed.Count >= G.HistoryMoveNb) { int k = GetHistoryIndex(mv); if ((float)(HistHit[k]) / HistTotal[k] < 0.6) { new_depth--; reduced = true; stat.HistoryReduced++; } } #endif #if FUTILITY_PRUNING if (depth == 1 && !lastMove.checking && !mv.checking && new_depth == 0 && mv.pcDst == 0) { if (opt_value == G.MATE) { opt_value = Simple_Evaluate() + G.FutilityMargin; } if (opt_value < beta) { continue; } } #endif //if the opponent is delaying mate by checking, do not enter quiesce until the mate killer move is tried if (new_depth == 0 && mate_threat > 0) { new_depth = 1; } Debug.Write(new string('\t', height)); Debug.WriteLine($"{mv} {alpha}, {beta}, {best} {mv.killer}"); MakeMove(mv, false); int vl; //to avoid quiesce search beta cut off too early, use -best instead of -alpha as new beta if (new_depth == 0 && alpha < G.WIN) { vl = -SearchCut(-beta, -best, 0, height + 1, mate_threat); } else { vl = -SearchCut(-beta, -alpha, new_depth, height + 1, mate_threat); } #if HISTORY_PRUNING if (vl >= beta && reduced) { new_depth++; vl = -SearchCut(-beta, -alpha, new_depth, height + 1, mate_threat); stat.HistoryResearched++; } #endif UnmakeMove(); if (vl > best) { best = vl; mvBest = mv; if (vl >= beta) { SetBestMove(mvBest, best, depth, height); foreach (MOVE m1 in mvPlayed) { HistoryBad(m1); } #if USE_HASH TT.WriteHash(Key, G.HASH_BETA, best, depth, mvBest); #endif stat.BetaCutoffs++; return(vl); } if (vl > alpha) { alpha = vl; } } mvPlayed.Add(mv); } #if USE_HASH TT.WriteHash(Key, G.HASH_ALPHA, best, depth, mvBest); #endif return(best); }
int SearchPV(int alpha, int beta, int depth, int height, out List <MOVE> pvs) { beta = Math.Min(beta, G.MATE - height); pvs = new List <MOVE>(); if (depth <= 0) { if (stepList[stepList.Count - 1].move.checking) { //被照将时,推迟进入静态搜索 depth++; } else { return(SearchQuiesce(alpha, beta, 0, height)); } } stat.PVNodes++; int best = HarmlessPruning(height); //distance pruning if (best >= beta) { return(best); } MOVE mvBest = new MOVE(); int hashFlag = 0; List <MOVE> subpv = null; TransKiller = null; #if USE_HASH HashStruct t = TT.ReadHash(Key); if (t.Move.sqSrc == 0) { TransKiller = null; } else { if (t.AlphaDepth >= depth) { if (t.Alpha <= alpha) { return(t.Alpha); } } if (t.BetaDepth >= depth) { if (t.Beta >= beta) { return(t.Beta); } } TransKiller = t.Move; TransKiller.pcSrc = pcSquares[TransKiller.sqSrc]; TransKiller.pcDst = pcSquares[TransKiller.sqDst]; } #endif #if INTERNAL_ITERATIVE_DEEPENING if (depth >= G.IIDDepth && TransKiller is null) { int new_depth = depth - G.IIDReduction; Debug.Assert(new_depth > 0); int vl = SearchPV(alpha, beta, new_depth, height, out subpv); if (vl < alpha) { vl = SearchPV(-G.MATE, beta, new_depth, height, out subpv); } if (subpv.Count > 0) { TransKiller = subpv[0]; } } #endif IEnumerable <MOVE> moves = GetNextMove(7, height); foreach (MOVE mv in moves) { Debug.Write(new string('\t', height)); Debug.WriteLine($"{mv} {alpha}, {beta}, {best},{mv.killer}"); int new_depth = depth - 1; if (mv.sqDst == stepList[stepList.Count - 1].move.sqDst && mv.score > 0 || mv.checking) { new_depth++; } MakeMove(mv, false); int vl; if (mvBest.sqSrc == 0) { vl = -SearchPV(-beta, -alpha, new_depth, height + 1, out subpv); } else { //当depth == 0, 且best < alpha时,传给quiesce的beta用 -best代替 -alpha,可以避免eval() > beta过早返回。 //返回的值和最佳着法只是因为搜索深度浅而貌似高于best。后续重新搜索时这个非最佳着法被首先搜索,降低了效率。 if (new_depth == 0 && alpha < G.WIN) { vl = -SearchCut(-alpha - 1, -best, new_depth, height + 1, -1); } else { vl = -SearchCut(-alpha - 1, -alpha, new_depth, height + 1, -1); } if (vl > alpha) // && vl < beta { Debug.WriteLine("Re-search"); Debug.Write(new string('\t', height)); Debug.WriteLine($"{mv} {alpha}, {beta}, {vl}"); vl = -SearchPV(-beta, -vl, new_depth, height + 1, out subpv); stat.PVChanged++; } } UnmakeMove(); if (vl > best) { best = vl; if (vl >= beta) { stat.BetaCutoffs++; mvBest = mv; hashFlag = G.HASH_BETA; break; } if (vl > alpha) { alpha = vl; mvBest = mv; hashFlag = G.HASH_PV; pvs.Clear(); pvs.Add(mv); pvs.AddRange(subpv); } } else { HistoryBad(mv); } } if (best > -G.WIN) { #if USE_HASH TT.WriteHash(Key, hashFlag, best, depth, mvBest); #endif if (mvBest.pcSrc != 0) { SetBestMove(mvBest, best, depth, height); } } return(best); }