3 Copyright (c) Scott Gasch
11 Routines dealing with the root of the search tree. Specifically
12 move ordering at the root node, searching, search support, pondering,
13 pondering support, and the main iterative deepening loop.
21 $Id: root.c 356 2008-07-02 16:18:12Z scott $
27 volatile MOVE_TIMER g_MoveTimer;
28 ULONG g_uIterateDepth;
29 ULONG g_uSoftExtendLimit;
30 ULONG g_uHardExtendLimit;
31 ULONG g_uExtensionReduction[MAX_PLY_PER_SEARCH];
32 FLAG g_fCanSplit[MAX_PLY_PER_SEARCH];
33 SCORE g_iRootScore[2] = {0, 0};
38 _SetMoveTimerForPonder(void)
43 Set the move timer before a ponder operation. A time limit of -1
44 means search forever... so the only way out of the ponder is for
45 new user input to interrupt it.
57 g_MoveTimer.dStartTime = SystemTimeStamp();
58 g_MoveTimer.dSoftTimeLimit = -1;
59 g_MoveTimer.dHardTimeLimit = -1;
60 g_MoveTimer.uNodeCheckMask = 0x200 - 1;
61 g_MoveTimer.bvFlags = 0;
66 SetMoveTimerToThinkForever(void)
81 g_MoveTimer.dStartTime = SystemTimeStamp();
82 g_MoveTimer.dSoftTimeLimit = -1;
83 g_MoveTimer.dHardTimeLimit = -1;
84 g_MoveTimer.uNodeCheckMask = 0x1000000 - 1;
85 g_MoveTimer.bvFlags = 0;
90 SetMoveTimerForSearch(FLAG fSwitchOver, ULONG uColor)
95 Set the move timer for a search operation.
99 FLAG fSwitchOver : if TRUE then this search is a converted ponder
100 (i.e. they made the ponder move and now we are searching).
108 ULONG uMovesDone = GetMoveNumber(uColor);
109 double dClock = (double)g_Options.uMyClock;
115 // If switching over from a ponder search leave the start time and
116 // move flags alone (i.e. N seconds in the past already).
118 if (FALSE == fSwitchOver)
120 g_MoveTimer.dStartTime = SystemTimeStamp();
121 g_MoveTimer.bvFlags = 0;
125 // Check the clock more often in search if there is little time
126 // left on the clock -- we can't afford an oversearch when the
127 // bullet game is down to the wire.
130 g_MoveTimer.uNodeCheckMask = 0x800 - 1;
132 if (WeAreRunningASuite())
134 g_MoveTimer.uNodeCheckMask = 0x20000 - 1;
136 else if (g_Options.u64MaxNodeCount)
138 g_MoveTimer.uNodeCheckMask = 0x1000 - 1;
144 g_MoveTimer.uNodeCheckMask = 0x400 - 1;
148 g_MoveTimer.uNodeCheckMask = 0x8000 - 1;
153 switch (g_Options.eClock)
156 // Fixed time per move. Think for g_uMyIncrement seconds exactly and
157 // no hard time limit.
160 dSec = (double)g_Options.uMyIncrement;
165 // N moves per time control or entire game in time control.
168 if (g_Options.uMovesPerTimePeriod != 0)
171 // N moves per time control.
173 ASSERT(g_Options.uMovesPerTimePeriod < 256);
175 (g_Options.uMovesPerTimePeriod -
176 ((uMovesDone - 1) % g_Options.uMovesPerTimePeriod));
178 Trace("SetMoveTimer: %u moves left to do this in %s sec.\n",
179 uMovesToDo, TimeToString(dClock));
181 ASSERT(uMovesToDo <= g_Options.uMovesPerTimePeriod);
182 dSec = (dClock / (double)uMovesToDo);
184 uMargin = (ULONG)(dSec * 2.2);
195 // Fixed time finish entire game.
198 Trace("SetMoveTimer: finish the game in %s sec.\n",
199 TimeToString(dClock));
202 uMargin = (ULONG)(dSec * 2.2);
207 // We get back a certain number of seconds with each move made.
209 case CLOCK_INCREMENT:
210 dSec = g_Options.uMyIncrement;
211 dSec += (dClock / 50);
212 uMargin = (int)(dSec * 2.2);
215 Trace("SetMoveTimer: finish the game in %s sec (+%u per move).\n",
216 TimeToString(dClock), g_Options.uMyIncrement);
220 // If we are running out of time, think for less than the
225 dSec -= g_Options.uMyIncrement;
233 Trace("SetMoveTimer: no time limit, think until interrupted.\n");
235 g_MoveTimer.dHardTimeLimit = -1;
236 g_MoveTimer.dSoftTimeLimit = -1;
243 if ((dSec + uMargin) >= (dClock * 7 / 8)) uMargin = 0;
244 g_MoveTimer.dSoftTimeLimit = g_MoveTimer.dStartTime + dSec;
245 g_MoveTimer.dHardTimeLimit = g_MoveTimer.dStartTime + dSec + uMargin;
248 if (TRUE == g_Options.fShouldPost)
250 if (g_MoveTimer.dSoftTimeLimit > 0)
252 Trace("SetMoveTimer: Soft time limit %s seconds.\n",
253 TimeToString(g_MoveTimer.dSoftTimeLimit -
254 g_MoveTimer.dStartTime));
255 ASSERT(g_MoveTimer.dHardTimeLimit > 0);
256 Trace("SetMoveTimer: Hard time limit %s seconds.\n",
257 TimeToString(g_MoveTimer.dHardTimeLimit -
258 g_MoveTimer.dStartTime));
259 if (TRUE == fSwitchOver)
261 Trace("SetMoveTimer: Already searched %s seconds.\n",
262 TimeToString(SystemTimeStamp() -
263 g_MoveTimer.dStartTime));
267 Trace("SetMoveTimer: Checking input and timer every %u nodes.\n",
268 g_MoveTimer.uNodeCheckMask);
275 ReInitializeSearcherContext(POSITION *pos,
276 SEARCHER_THREAD_CONTEXT *ctx)
281 Reinitializes a SEARCHER_THREAD_CONTEXT structure.
286 SEARCHER_THREAD_CONTEXT *ctx
296 memmove(&(ctx->sPosition), pos, sizeof(POSITION));
299 ctx->uPositional = 133;
300 memset(&(ctx->sCounters), 0, sizeof(COUNTERS));
305 InitializeSearcherContext(POSITION *pos,
306 SEARCHER_THREAD_CONTEXT *ctx)
311 Initialize a SEARCHER_THREAD_CONTEXT structure.
316 SEARCHER_THREAD_CONTEXT *ctx
326 memset(ctx, 0, sizeof(SEARCHER_THREAD_CONTEXT));
327 ReInitializeSearcherContext(pos, ctx);
328 ctx->sMoveStack.uUnblockedKeyValue[0] = 1;
330 u < MAX_PLY_PER_SEARCH;
333 ctx->sMoveStack.uUnblockedKeyValue[u] =
334 ctx->sMoveStack.uUnblockedKeyValue[u-1] + 0x28F5C28;
340 InitializeLightweightSearcherContext(POSITION *pos,
341 LIGHTWEIGHT_SEARCHER_CONTEXT *ctx)
346 Initialize a LIGHTWEIGHT_SEARCHER_CONTEXT structure.
351 LIGHTWEIGHT_SEARCHER_CONTEXT *ctx
361 memset(ctx, 0, sizeof(LIGHTWEIGHT_SEARCHER_CONTEXT));
364 memmove(&(ctx->sPosition), pos, sizeof(POSITION));
367 ctx->uPositional = 133;
368 ctx->sMoveStack.uUnblockedKeyValue[0] = 1;
370 u < MAX_PLY_PER_SEARCH;
373 ctx->sMoveStack.uUnblockedKeyValue[u] =
374 ctx->sMoveStack.uUnblockedKeyValue[u-1] + 0x28F5C28;
380 PostMoveSearchReport(SEARCHER_THREAD_CONTEXT *ctx)
385 Dump a report to stdout after every move.
389 SEARCHER_THREAD_CONTEXT *ctx,
402 d = (double)g_MoveTimer.dEndTime - (double)g_MoveTimer.dStartTime + 0.01;
404 Trace("---------------------------------------------\n"
405 "Searched for %5.1f seconds, saw %"
406 COMPILER_LONGLONG_UNSIGNED_FORMAT
408 COMPILER_LONGLONG_UNSIGNED_FORMAT
409 " qnodes) (%6.0f nps).\n",
410 d, ctx->sCounters.tree.u64TotalNodeCount,
411 ctx->sCounters.tree.u64QNodeCount,
412 ((double)ctx->sCounters.tree.u64TotalNodeCount / d));
413 if (g_Options.fThinking)
415 snprintf(buf, ARRAY_LENGTH(buf), "d%u, %s, %3.1fs, %6.0fknps, PV=%s",
416 ctx->uRootDepth / ONE_PLY,
417 ScoreToString(ctx->iRootScore),
419 ((double)ctx->sCounters.tree.u64TotalNodeCount / d / 1000),
421 Trace("tellothers %s\n", buf);
425 if (ctx->sCounters.parallel.uNumSplits > 0)
427 n = (double)ctx->sCounters.parallel.uNumSplits;
428 Trace("Split %u times total ",
429 ctx->sCounters.parallel.uNumSplits);
430 Trace("(~%7.2fx/sec).\n", (n / d));
433 n = (double)ctx->sCounters.parallel.uNumSplitsTerminated;
434 Trace(" ...%u (%5.2f percent) terminated early.\n",
435 ctx->sCounters.parallel.uNumSplitsTerminated, ((n/d) * 100.0));
436 DumpHelperIdlenessReport();
442 AnalyzeFullHashTable();
445 d = (double)(ctx->sCounters.hash.u64Probes) + 1;
447 Trace("Hashing percentages: (%5.3f total, %5.3f useful)\n",
448 ((double)(ctx->sCounters.hash.u64OverallHits) / d) * 100.0,
449 ((double)(ctx->sCounters.hash.u64UsefulHits) / d) * 100.0);
450 d = (double)(ctx->sCounters.pawnhash.u64Probes) + 1;
452 n = (double)(ctx->sCounters.pawnhash.u64Hits);
453 Trace("Pawn hash hitrate: %5.3f percent.\n", (n/d) * 100.0);
454 n = (double)(ctx->sCounters.tree.u64NullMoveSuccess);
455 d = (double)(ctx->sCounters.tree.u64NullMoves) + 1;
457 Trace("Null move cutoff rate: %5.3f percent.\n",
460 n = (double)(ctx->sCounters.tree.u64EndgamePruningSuccess);
461 d = n + (double)(ctx->sCounters.tree.u64EndgamePruningFailures);
464 Trace("Endgame pruning heuristic success rate was %5.3f percent.\n"
465 "Endgame pruning heuristic was "
466 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT " / "
467 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT ".\n",
469 ctx->sCounters.tree.u64EndgamePruningSuccess,
470 (ctx->sCounters.tree.u64EndgamePruningSuccess +
471 ctx->sCounters.tree.u64EndgamePruningFailures));
475 Trace("Never used endgame pruning heuristic.\n");
479 n = (double)(ctx->sCounters.tree.u64AvoidNullSuccess);
480 d = n + (double)(ctx->sCounters.tree.u64AvoidNullFailures);
483 Trace("Avoid null move heuristic rate was %5.3f percent.\n"
484 "Avoid null move heuristic was "
485 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT " / "
486 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT ".\n",
488 ctx->sCounters.tree.u64AvoidNullSuccess,
489 (ctx->sCounters.tree.u64AvoidNullSuccess +
490 ctx->sCounters.tree.u64AvoidNullFailures));
494 Trace("Never used the avoid null move heuristic.\n");
496 n = (double)(ctx->sCounters.tree.u64QuickNullSuccess);
497 n += (double)(ctx->sCounters.tree.u64QuickNullDeferredSuccess);
498 d = n + (double)(ctx->sCounters.tree.u64QuickNullFailures);
501 Trace("Quick null move heuristic rate was %5.3f percent.\n"
502 "Quick null move heuristic rate was "
503 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT " ("
504 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT ") / "
505 "%"COMPILER_LONGLONG_UNSIGNED_FORMAT ".\n",
507 ctx->sCounters.tree.u64QuickNullSuccess,
508 ctx->sCounters.tree.u64QuickNullDeferredSuccess,
509 (ctx->sCounters.tree.u64QuickNullSuccess +
510 ctx->sCounters.tree.u64QuickNullDeferredSuccess +
511 ctx->sCounters.tree.u64QuickNullFailures));
515 Trace("Never used the quick null move heuristic.\n");
518 n = (double)ctx->sCounters.tree.u64BetaCutoffsOnFirstMove;
519 d = (double)ctx->sCounters.tree.u64BetaCutoffs + 1;
520 Trace("First move beta cutoff rate was %5.3f percent.\n",
523 d = (double)ctx->sCounters.tree.u64LazyEvals;
524 d += (double)ctx->sCounters.tree.u64FullEvals;
525 d += (double)ctx->sCounters.tree.u64EvalHashHits;
528 Trace("Eval percentages: (%5.2f hash, %5.2f lazy, %5.2f full)\n",
529 ((double)ctx->sCounters.tree.u64EvalHashHits / d) * 100.0,
530 ((double)ctx->sCounters.tree.u64LazyEvals / d) * 100.0,
531 ((double)ctx->sCounters.tree.u64FullEvals / d) * 100.0);
533 Trace("Extensions: (%u +, %u q+, %u 1mv, %u !kmvs, %u mult+, %u pawn\n"
534 " %u threat, %u zug, %u sing, %u endg, %u bm, %u recap)\n",
535 ctx->sCounters.extension.uCheck,
536 ctx->sCounters.extension.uQExtend,
537 ctx->sCounters.extension.uOneLegalMove,
538 ctx->sCounters.extension.uNoLegalKingMoves,
539 ctx->sCounters.extension.uMultiCheck,
540 ctx->sCounters.extension.uPawnPush,
541 ctx->sCounters.extension.uMateThreat,
542 ctx->sCounters.extension.uZugzwang,
543 ctx->sCounters.extension.uSingularReplyToCheck,
544 ctx->sCounters.extension.uEndgame,
545 ctx->sCounters.extension.uBotvinnikMarkoff,
546 ctx->sCounters.extension.uRecapture);
548 n = (double)ctx->sCounters.tree.u64CyclesInEval;
549 Trace("Avg. cpu cycles in eval: %8.1f.\n", (n / d));
556 RootSearch(SEARCHER_THREAD_CONTEXT *ctx,
564 Search at the root of the whole tree.
568 SEARCHER_THREAD_CONTEXT *ctx,
579 PLY_INFO *pi = &ctx->sPlyInfo[ctx->uPly];
580 SCORE iBestScore = -INFINITY;
581 SCORE iInitialAlpha = iAlpha;
586 UINT64 u64StartingNodeCount;
587 ULONG uNumLegalMoves;
590 POSITION *pos = &ctx->sPosition;
591 ASSERT(IS_VALID_SCORE(iAlpha));
592 ASSERT(IS_VALID_SCORE(iBeta));
593 ASSERT(iAlpha < iBeta);
594 ASSERT((ctx->uPly == 0) || (ctx->uPly == 1)); // 1 is for under a ponder
595 memcpy(&pi->sPosition, pos, sizeof(POSITION));
598 ctx->sCounters.tree.u64TotalNodeCount++;
599 pi->PV[ctx->uPly] = NULLMOVE;
600 pi->mvBest = NULLMOVE;
601 g_MoveTimer.bvFlags |= TIMER_SEARCHING_FIRST_MOVE;
603 Trace("---- depth=%u, a=%d, b=%d ----\n", uDepth / ONE_PLY, iAlpha, iBeta);
607 for (x = ctx->sMoveStack.uBegin[ctx->uPly];
608 x < ctx->sMoveStack.uEnd[ctx->uPly];
611 SelectMoveAtRoot(ctx, x);
612 if (ctx->sMoveStack.mvf[x].bvFlags & MVF_MOVE_SEARCHED) break;
613 ctx->sMoveStack.mvf[x].bvFlags |= MVF_MOVE_SEARCHED;
614 mv = ctx->sMoveStack.mvf[x].mv;
615 mv.bvFlags |= WouldGiveCheck(ctx, mv);
617 if (MakeMove(ctx, mv))
620 u64StartingNodeCount = ctx->sCounters.tree.u64TotalNodeCount;
623 // TODO: Fancy recap shit here?
626 uNextDepth = uDepth - ONE_PLY;
627 if (IS_CHECKING_MOVE(mv))
629 uNextDepth += HALF_PLY;
630 ctx->sPlyInfo[ctx->uPly].iExtensionAmount = HALF_PLY;
633 ctx->sSearchFlags.fVerifyNullmove = TRUE;
634 ctx->sSearchFlags.uQsearchDepth = 0;
635 if (uNumLegalMoves == 1)
638 // Move 1, full a..b window
640 iScore = -Search(ctx, -iBeta, -iAlpha, uNextDepth);
645 // Moves 2..N, minimal window search
648 ASSERT(uNumLegalMoves > 1);
649 iScore = -Search(ctx, -iAlpha - 1, -iAlpha, uNextDepth);
650 if ((iAlpha < iScore) && (iScore < iBeta))
652 iScore = -Search(ctx, -iBeta, -iAlpha, uNextDepth);
655 ctx->sSearchFlags.fVerifyNullmove = FALSE;
657 if (WE_SHOULD_STOP_SEARCHING)
659 iBestScore = INVALID_SCORE;
663 Trace("%2u. %-8s => node=%" COMPILER_LONGLONG_UNSIGNED_FORMAT
664 ", predict=%d, actual=%d, ",
666 MoveToSan(mv, &ctx->sPosition),
667 u64StartingNodeCount,
668 ctx->sMoveStack.mvf[x].iValue,
670 ASSERT(PositionsAreEquivalent(&pi->sPosition, &ctx->sPosition));
673 // Keep track of how many moves are under each one we
674 // search and use this as the basis for root move ordering
677 u64StartingNodeCount = (ctx->sCounters.tree.u64TotalNodeCount -
678 u64StartingNodeCount);
679 u64StartingNodeCount >>= 9;
680 u64StartingNodeCount &= (MAX_INT / 4);
681 ctx->sMoveStack.mvf[x].iValue =
682 (SCORE)(u64StartingNodeCount + iScore);
684 Trace("next_predict: %d\n", ctx->sMoveStack.mvf[x].iValue);
685 ASSERT(iBestScore <= iAlpha);
686 ASSERT(iAlpha < iBeta);
688 if (iScore > iBestScore)
695 // We have a best move so far. As of now it is
696 // the one we'll be playing. Also make sure to
697 // search it first at the next depth.
700 Trace("Best so far... root score=%d.\n", iScore);
702 ctx->mvRootMove = mv;
703 ctx->iRootScore = iScore;
704 ctx->uRootDepth = uDepth;
705 ctx->sMoveStack.mvf[x].iValue = MAX_INT;
708 // If there was a previous PV move then knock its
709 // score down so that it will be considered second
710 // on the next depth.
712 for (y = ctx->sMoveStack.uBegin[ctx->uPly];
716 if (ctx->sMoveStack.mvf[y].iValue == MAX_INT)
718 ctx->sMoveStack.mvf[y].iValue /= 2;
728 UpdateDynamicMoveOrdering(ctx,
733 UtilPrintPV(ctx, iAlpha, iBeta, iScore, mv);
734 KEEP_TRACK_OF_FIRST_MOVE_FHs(iBestScore == -INFINITY);
735 ctx->sMoveStack.mvf[x].bvFlags &= ~MVF_MOVE_SEARCHED;
743 UtilPrintPV(ctx, iAlpha, iBeta, iScore, mv);
748 g_MoveTimer.bvFlags &= ~TIMER_SEARCHING_FIRST_MOVE;
752 if (iAlpha == iInitialAlpha)
757 ASSERT(iBestScore <= iAlpha);
758 ASSERT(PositionsAreEquivalent(pos, &pi->sPosition));
759 UtilPrintPV(ctx, iAlpha, iBeta, iBestScore, mv);
769 _DetectPreMoveTerminalStates(SEARCHER_THREAD_CONTEXT *ctx,
777 POSITION *pos = &ctx->sPosition;
778 ULONG uMajors[2], uMinors[2], uWhiteBishops[2], uBlackBishops[2];
780 ret.eResult = RESULT_IN_PROGRESS;
781 ret.szDescription[0] = '\0';
784 // Look for insufficient material.
786 uMajors[u] = (pos->uNonPawnCount[u][ROOK] +
787 pos->uNonPawnCount[u][QUEEN]);
788 uMinors[u] = (pos->uNonPawnCount[u][KNIGHT] +
789 pos->uNonPawnCount[u][BISHOP]);
790 uWhiteBishops[u] = pos->uWhiteSqBishopCount[u];
791 uBlackBishops[u] = pos->uNonPawnCount[u][BISHOP] - uWhiteBishops[u];
794 if ((pos->uPawnCount[u] != 0) ||
795 (pos->uPawnCount[FLIP(u)] != 0)) {
798 if (pos->uNonPawnCount[u][0] == 1) {
799 if (uMajors[FLIP(u)] == 0 &&
800 uMinors[FLIP(u)] == 1) {
801 ret.eResult = RESULT_DRAW;
802 strcpy(ret.szDescription, "insufficient material");
804 } else if (uMajors[FLIP(u)] == 0 &&
805 (uMinors[FLIP(u)] == uWhiteBishops[FLIP(u)] ||
806 uMinors[FLIP(u)] == uBlackBishops[FLIP(u)])) {
807 ret.eResult = RESULT_DRAW;
808 strcpy(ret.szDescription, "insufficient material");
811 } else if ((pos->uNonPawnCount[u][0] == 1 + uWhiteBishops[u] &&
812 pos->uNonPawnCount[FLIP(u)][0] ==
813 1 + uWhiteBishops[FLIP(u)]) ||
814 (pos->uNonPawnCount[u][0] == 1 + uBlackBishops[u] &&
815 pos->uNonPawnCount[FLIP(u)][0] ==
816 1 + uBlackBishops[FLIP(u)])) {
817 ret.eResult = RESULT_DRAW;
818 strcpy(ret.szDescription, "insufficient material");
823 // Count the number of legal moves the side on move has in this position.
824 for (u = ctx->sMoveStack.uBegin[ctx->uPly];
825 u < ctx->sMoveStack.uEnd[ctx->uPly];
827 mv = ctx->sMoveStack.mvf[u].mv;
828 if (MakeMove(ctx, mv)) {
838 // If there's only one legal move then tell the caller what it is.
839 if (uNumLegal == 1) {
844 // There are no legal moves so its checkmate or stalemate.
845 ASSERT(uNumLegal == 0);
846 if (TRUE == fInCheck) {
847 if (ctx->sPosition.uToMove == WHITE) {
848 ret.eResult = RESULT_BLACK_WON;
849 strcpy(ret.szDescription, "black checkmated white");
852 ret.eResult = RESULT_WHITE_WON;
853 strcpy(ret.szDescription, "white checkmated black");
856 ret.eResult = RESULT_DRAW;
857 sprintf(ret.szDescription, "%s stalemated %s",
858 (ctx->sPosition.uToMove == WHITE) ? "black" : "white",
859 (ctx->sPosition.uToMove == WHITE) ? "while" : "black");
866 _DetectPostMoveTerminalStates(SEARCHER_THREAD_CONTEXT *ctx)
871 This function is called by Iterate and MakeTheMove to declare
876 SEARCHER_THREAD_CONTEXT *ctx
885 FLAG fInCheck = InCheck(&ctx->sPosition, ctx->sPosition.uToMove);
887 // Did we just make the 50th+ move without progress?
888 if (ctx->sPosition.uFifty >= 100) {
889 ret.eResult = RESULT_DRAW;
890 strcpy(ret.szDescription, "fifty moves without progress");
893 GenerateMoves(ctx, NULLMOVE, (fInCheck ? GENERATE_ESCAPES :
894 GENERATE_ALL_MOVES));
896 // Did we just repeat a position for the 3rd time?
897 if (IsLegalDrawByRepetition()) {
898 ret.eResult = RESULT_DRAW;
899 strcpy(ret.szDescription, "third repetition of position");
903 // Otherwise look for checkmates, stalemates and insufficient material.
905 return _DetectPreMoveTerminalStates(ctx,
912 _IterateSetSearchGlobals(ULONG uDepth)
915 ULONG uDontSplitLessThan;
918 // Set some globals that will be used throughout this search.
920 g_uIterateDepth = uDepth;
921 ASSERT(g_uIterateDepth > 0);
922 ASSERT(g_uIterateDepth < MAX_PLY_PER_SEARCH);
925 // For use in scaling back extensions that are very far from
928 // Iterate g_Soft g_Hard
930 // 0 1 2 2.5 3 3.5 4 (x Iterate)
931 // |-------------------|----|----|---------|----...
932 // |full full full full|-1/4|-1/2|-3/4 -3/4|none...
934 g_uSoftExtendLimit = g_uIterateDepth * 2;
935 g_uHardExtendLimit = g_uIterateDepth * 4;
936 for (u = 0; u < MAX_PLY_PER_SEARCH; u++)
938 if (u < g_uSoftExtendLimit)
940 g_uExtensionReduction[u] = 0;
942 else if (u < (g_uSoftExtendLimit + g_uIterateDepth / 2))
944 g_uExtensionReduction[u] = QUARTER_PLY;
946 else if (u < (g_uSoftExtendLimit + g_uIterateDepth))
948 g_uExtensionReduction[u] = HALF_PLY;
950 else if (u < g_uHardExtendLimit)
952 g_uExtensionReduction[u] = THREE_QUARTERS_PLY;
956 g_uExtensionReduction[u] = 5 * ONE_PLY;
961 // Determine how much depth we need below a point in the tree in
962 // order to split the search (which is based on iteration depth).
964 uDontSplitLessThan = (ULONG)((double)(uDepth) * 0.30);
965 for (v = 0; v < MAX_PLY_PER_SEARCH; v++)
967 g_fCanSplit[v] = ((v + 1) >= uDontSplitLessThan);
969 g_fCanSplit[0] = FALSE;
973 #define SKIP_GRADUAL_OPENING 600
974 #define INITIAL_HALF_WINDOW 75
975 #define FIRST_FAIL_STEP 150
976 #define SECOND_FAIL_STEP 375
979 _IterateWidenWindow(ULONG uColor,
985 SCORE iRoughScore = g_iRootScore[uColor];
986 static SCORE _Steps[] =
993 // Trace("State = %u.\n", *puState);
994 // Trace("Bound from %d to ", *piBound);
995 if ((abs(iRoughScore - iScore) > SKIP_GRADUAL_OPENING) ||
998 *piBound = iDir * INFINITY;
1003 ASSERT(*puState < 2);
1005 *piBound += iDir * _Steps[*puState];
1008 // Trace("%d.\n", *piBound);
1009 // Trace("State = %u\n", *puState);
1013 GAME_RESULT Iterate(SEARCHER_THREAD_CONTEXT *ctx)
1016 Routine description:
1018 Main iterative deepening loop for both thinking and pondering.
1019 Call RootSearch with progressively deeper depths while there is
1020 remaining time, we haven't found a forced mate, and there's no
1025 SEARCHER_THREAD_CONTEXT *ctx
1029 FLAG : is the game over?
1033 ULONG uNumRootFailLows = 0;
1035 ULONG uFailState = 0;
1038 SCORE iAlpha = -INFINITY;
1039 SCORE iBeta = +INFINITY;
1041 FLAG fInCheck = InCheck(&(ctx->sPosition), ctx->sPosition.uToMove);
1046 memcpy(&board, &(ctx->sPosition), sizeof(POSITION));
1047 VerifyPositionConsistency(&board, FALSE);
1050 uColor = ctx->sPosition.uToMove;
1051 g_iRootScore[uColor] = GetRoughEvalScore(ctx, iAlpha, iBeta, TRUE);
1052 g_iRootScore[FLIP(uColor)] = -g_iRootScore[uColor];
1055 // Generate moves here so that RootSearch doesn't have to.
1057 ASSERT((ctx->uPly == 0) || (ctx->uPly == 1)); // 1 is under a ponder
1058 ctx->sPlyInfo[ctx->uPly].fInCheck = fInCheck;
1059 GenerateMoves(ctx, NULLMOVE, (fInCheck ? GENERATE_ESCAPES :
1060 GENERATE_ALL_MOVES));
1061 ASSERT(ctx->sMoveStack.uBegin[0] == 0);
1064 // See if we are sitting at a checkmate or stalemate position; no
1065 // sense searching if so.
1067 ret = _DetectPreMoveTerminalStates(ctx,
1070 if (ret.eResult != RESULT_IN_PROGRESS) {
1074 // Game still in progress but only one legal move available?
1075 if (mv.uMove != 0) {
1076 ctx->mvRootMove = mv;
1077 ctx->iRootScore = 0;
1078 ctx->uRootDepth = 0;
1079 ctx->sPlyInfo[0].PV[0] = mv;
1080 ctx->sPlyInfo[0].PV[1] = NULLMOVE;
1081 strcpy(ctx->szLastPV, "only");
1082 if (!g_Options.fPondering) {
1083 Trace("ONLY MOVE --> stop searching now\n");
1084 g_MoveTimer.bvFlags |= TIMER_STOPPING;
1085 g_MoveTimer.bvFlags |= TIMER_CURRENT_OBVIOUS;
1087 Trace("ONLY PONDER --> move immediately if prediction correct.\n");
1088 g_MoveTimer.bvFlags |= TIMER_MOVE_IMMEDIATELY;
1089 g_MoveTimer.bvFlags |= TIMER_CURRENT_OBVIOUS;
1095 uDepth <= g_Options.uMaxDepth;
1099 // Re-rank the moves based on nodecounts and mark all moves as
1100 // not yet searched at this depth.
1102 for (u = ctx->sMoveStack.uBegin[ctx->uPly];
1103 u < ctx->sMoveStack.uEnd[ctx->uPly];
1106 ctx->sMoveStack.mvf[u].bvFlags &= ~MVF_MOVE_SEARCHED;
1110 // Make sure a..b window makes sense and set some other per-depth
1111 // globals used by the search.
1113 _IterateSetSearchGlobals(uDepth);
1116 // Try to get a PV for this depth before we're out of time...
1120 if (iBeta > INFINITY) iBeta = +INFINITY;
1121 if (iAlpha < -INFINITY) iAlpha = -INFINITY;
1122 if (iAlpha >= iBeta) iAlpha = iBeta - 1;
1123 iScore = RootSearch(ctx,
1126 uDepth * ONE_PLY + HALF_PLY);
1127 if (g_MoveTimer.bvFlags & TIMER_STOPPING) break;
1128 mv = ctx->mvRootMove;
1131 // Got a PV, we're done with this depth.
1133 if ((iAlpha < iScore) && (iScore < iBeta))
1135 ASSERT(iScore == ctx->iRootScore);
1137 ASSERT(SanityCheckMove(&ctx->sPosition, mv));
1138 iAlpha = iScore - INITIAL_HALF_WINDOW;
1139 iBeta = iScore + INITIAL_HALF_WINDOW;
1140 g_iRootScore[uColor] = iScore;
1141 g_iRootScore[FLIP(uColor)] = -iScore;
1142 g_MoveTimer.bvFlags &= ~TIMER_RESOLVING_ROOT_FH;
1143 g_MoveTimer.bvFlags &= ~TIMER_RESOLVING_ROOT_FL;
1145 (void)CheckTestSuiteMove(mv, iScore, uDepth);
1150 // Root fail low? We'll have to research with a wider
1153 else if (iScore <= iAlpha)
1155 ASSERT(ctx->mvRootMove.uMove);
1156 g_MoveTimer.bvFlags |= TIMER_RESOLVING_ROOT_FL;
1158 if (uNumRootFailLows > 3)
1160 g_MoveTimer.bvFlags |= TIMER_MANY_ROOT_FLS;
1162 _IterateWidenWindow(uColor, iScore, &iAlpha, &uFailState, -1);
1165 // Consider all moves again with wider window...
1167 for (u = ctx->sMoveStack.uBegin[ctx->uPly];
1168 u < ctx->sMoveStack.uEnd[ctx->uPly];
1171 ctx->sMoveStack.mvf[u].bvFlags &= ~MVF_MOVE_SEARCHED;
1176 // Must be a root fail high.
1180 ASSERT(iScore >= iBeta);
1182 ASSERT(SanityCheckMove(&ctx->sPosition, mv));
1183 g_MoveTimer.bvFlags |= TIMER_RESOLVING_ROOT_FH;
1184 _IterateWidenWindow(uColor, iScore, &iBeta, &uFailState, +1);
1187 while(1); // repeat until we run out of time or fall inside a..b
1190 // We are here because either:
1192 // 1. we completed a search depth,
1193 // or 2. we ran out of time,
1194 // or 3. there is one legal move and we want to make it immediately,
1195 // or 4. there was user input and we unrolled the search.
1199 // TODO: Scale back history between iterative depths?
1203 // Mate-in-n when we have searched n? Stop searching.
1205 if (IS_VALID_SCORE(iScore) &&
1206 (ULONG)abs(iScore) >= (INFINITY - uDepth))
1208 Trace("FORCED MATE --> stop searching now\n");
1209 g_MoveTimer.bvFlags |= TIMER_STOPPING;
1213 // Have we reached user's maxdepth option? Stop searching.
1215 if (uDepth >= g_Options.uMaxDepth)
1217 Trace("DEPTH LIMIT --> stop searching now\n");
1218 g_MoveTimer.bvFlags |= TIMER_STOPPING;
1222 // Did the move timer expire? Stop searching.
1224 if (g_MoveTimer.bvFlags & TIMER_STOPPING) break;
1226 g_Options.u64NodesSearched = ctx->sCounters.tree.u64TotalNodeCount;
1227 g_MoveTimer.dEndTime = SystemTimeStamp();
1230 // Here we are at the end of a search. If we were:
1232 // ...Searching then: ...Pondering then:
1234 // 1. we ran out of time 1. they made a different move than
1235 // the predicted move and we aborted
1236 // 2. we ran out of depth
1237 // 2. we ran out of depth
1238 // 3. we found a mate-in-n
1239 // 3. we found a mate-in-n
1240 // TODO: node count limit?
1241 // (4. if they made the predicted move
1242 // then we converted to a search)
1244 ASSERT(ctx->mvRootMove.uMove);
1245 ASSERT(SanityCheckMove(&board, ctx->mvRootMove));
1246 if (g_MoveTimer.bvFlags & TIMER_RESOLVING_ROOT_FL)
1248 ctx->sPlyInfo[ctx->uPly].PV[ctx->uPly] = ctx->mvRootMove;
1249 ctx->sPlyInfo[ctx->uPly].PV[ctx->uPly+1] = NULLMOVE;
1251 ret.eResult = RESULT_IN_PROGRESS;
1252 ret.szDescription[0] = '\0';
1258 MakeTheMove(SEARCHER_THREAD_CONTEXT *ctx)
1261 Routine description:
1263 Make the move that search selected in the official game list.
1267 SEARCHER_THREAD_CONTEXT *ctx
1275 MOVE mv = ctx->mvRootMove;
1278 if (TRUE == g_Options.fThinking)
1281 // TODO: keep track of how many moves in a row we are at or
1282 // below a draw score and use this to respond to draw offers.
1286 // Was this a search that converted from a ponder?
1288 if (TRUE == g_Options.fSuccessfulPonder)
1290 if (FALSE == OfficiallyMakeMove(g_Options.mvPonder, 0, FALSE))
1292 UtilPanic(CANNOT_OFFICIALLY_MAKE_MOVE,
1294 (void *)g_Options.mvPonder.uMove,
1300 g_Options.fSuccessfulPonder = FALSE;
1303 // Only do d2d4 stuff if we're under xboard -- it's the standard.
1304 if (g_Options.fRunningUnderXboard) {
1305 Trace("move %s\n", MoveToIcs(mv));
1307 Trace("move %s\n", MoveToSan(mv, &ctx->sPosition));
1310 if (FALSE == OfficiallyMakeMove(mv, g_iScore, FALSE))
1312 UtilPanic(CANNOT_OFFICIALLY_MAKE_MOVE,
1320 if (g_Options.fVerbosePosting)
1322 PostMoveSearchReport(ctx);
1323 PostMoveTestSuiteReport(ctx);
1326 g_Options.fPondering = g_Options.fThinking = FALSE;
1331 Ponder(POSITION *pos)
1334 Routine description:
1348 static SEARCHER_THREAD_CONTEXT ctx;
1351 InitializeSearcherContext(pos, &ctx);
1352 g_Options.fPondering = TRUE;
1353 g_Options.fThinking = FALSE;
1354 g_Options.fSuccessfulPonder = FALSE;
1355 ret.eResult = RESULT_IN_PROGRESS;
1356 ret.szDescription[0] = '\0';
1359 // When do we not want to ponder
1361 if ((g_Options.ePlayMode == FORCE_MODE) || (g_Options.uMyClock < 10))
1363 g_Options.fPondering = FALSE;
1368 // What should we ponder?
1370 g_Options.mvPonder = GetPonderMove(pos);
1371 if (g_Options.mvPonder.uMove == 0)
1373 g_Options.fPondering = FALSE;
1376 ASSERT(SanityCheckMove(pos, g_Options.mvPonder));
1379 // Prepare to ponder by doing some maintenance on the dynamic move
1380 // ordering scheme counters, changing the dirty tag in the hash
1381 // code, and clearing the root nodes-per-move hash.
1383 MaintainDynamicMoveOrdering();
1387 // Make the move ponder move.
1389 if (FALSE == MakeMove(&ctx, g_Options.mvPonder))
1392 g_Options.fPondering = FALSE;
1397 // TODO: probe the book
1400 _SetMoveTimerForPonder();
1403 // TODO: Any preEval?
1407 // TODO: Set draw value
1411 #if (PERF_COUNTERS && MP)
1412 ClearHelperThreadIdleness();
1414 ASSERT(ctx.uPly == 1);
1415 ret = Iterate(&ctx);
1416 ASSERT(ctx.uPly == 1);
1417 if (ret.eResult == RESULT_IN_PROGRESS) {
1419 return _DetectPostMoveTerminalStates(&ctx);
1426 GAME_RESULT Think(POSITION *pos)
1429 Routine description:
1443 static SEARCHER_THREAD_CONTEXT ctx;
1444 static ULONG _resign_count = 0;
1448 InitializeSearcherContext(pos, &ctx);
1449 g_MoveTimer.bvFlags = 0;
1450 g_Options.fPondering = FALSE;
1451 g_Options.fThinking = TRUE;
1452 g_Options.fSuccessfulPonder = FALSE;
1455 // Prepare to think by doing some maintenance on the dynamic move
1456 // ordering scheme counters, changing the dirty tag in the hash
1457 // code, and clearing the root nodes-per-move hash.
1459 if (NULL != (p = PositionToFen(&(ctx.sPosition))))
1461 Trace("The root position is: %s\n", p);
1462 SystemFreeMemory(p);
1464 MaintainDynamicMoveOrdering();
1468 // Check the opening book, maybe it has a move for us.
1470 if (g_uBookProbeFailures < 3 &&
1471 !WeAreRunningASuite() &&
1472 g_Options.szBookName[0] != '\0') {
1473 mvBook = BookMove(pos, BOOKMOVE_SELECT_MOVE);
1474 if (mvBook.uMove == 0)
1476 g_uBookProbeFailures++;
1480 g_uBookProbeFailures = 0;
1481 ASSERT(SanityCheckMove(pos, mvBook));
1482 ctx.mvRootMove = mvBook;
1484 return _DetectPostMoveTerminalStates(&ctx);
1489 // TODO: Any preEval?
1493 // Set time limit for move
1495 SetMoveTimerForSearch(FALSE, pos->uToMove);
1498 // TODO: Set draw value
1500 #if (PERF_COUNTERS && MP)
1501 ClearHelperThreadIdleness();
1503 GAME_RESULT ret = Iterate(&ctx);
1504 if (ret.eResult == RESULT_IN_PROGRESS) {
1505 if (g_Options.iResignThreshold != 0 &&
1506 g_iRootScore[ctx.sPosition.uToMove] < g_Options.iResignThreshold) {
1508 if (_resign_count > 2) {
1509 if (ctx.sPosition.uToMove == WHITE) {
1510 ret.eResult = RESULT_BLACK_WON;
1511 strcpy(ret.szDescription, "white resigned");
1513 ret.eResult = RESULT_WHITE_WON;
1514 strcpy(ret.szDescription, "black resigned"); }
1515 Trace("tellics resign\n");
1522 return _DetectPostMoveTerminalStates(&ctx);