int Widen(POS *p, int depth, int * pv, int lastScore) { // Function performs aspiration search, progressively widening the window. // Code structure modelled after Senpai 1.0. int cur_val = lastScore, alpha, beta; if (depth > 6 && lastScore < MAX_EVAL) { for (int margin = 10; margin < 500; margin *= 2) { alpha = lastScore - margin; beta = lastScore + margin; cur_val = SearchRoot(p, 0, alpha, beta, depth, pv); if (abort_search) break; if (cur_val < alpha) Timer.OnFailLow(); if (cur_val > alpha && cur_val < beta) return cur_val; // we have finished within the window if (cur_val > MAX_EVAL) break; // verify mate searching with infinite bounds } } cur_val = SearchRoot(p, 0, -INF, INF, root_depth, pv); // full window search return cur_val; }
void Iterate(POS *p, int *pv) { int val = 0, cur_val = 0; U64 nps = 0; Timer.SetIterationTiming(); int max_root_depth = Timer.GetData(MAX_DEPTH); root_side = p->side; SetAsymmetricEval(p->side); // Are we operating in slowdown mode or on node limit? Timer.special_mode = 0; if (Timer.nps_limit || Timer.GetData(MAX_NODES) > 0) Timer.special_mode = 1; // Search with increasing depth for (root_depth = 1; root_depth <= max_root_depth; root_depth++) { int elapsed = Timer.GetElapsedTime(); if (elapsed) nps = nodes * 1000 / elapsed; #if defined _WIN32 || defined _WIN64 printf("info depth %d time %d nodes %I64d nps %I64d\n", root_depth, elapsed, nodes, nps); #else printf("info depth %d time %d nodes %lld nps %lld\n", root_depth, elapsed, nodes, nps); #endif if (use_aspiration) cur_val = Widen(p, root_depth, pv, cur_val); else cur_val = SearchRoot(p, 0, -INF, INF, root_depth, pv); // full window search // don't search too deep with only one move available if (root_depth == 8 && !fl_has_choice && !Timer.IsInfiniteMode() ) break; if (abort_search || Timer.FinishIteration()) break; val = cur_val; } }
// 主搜索例程 void SearchMain(int nDepth) { int i, vl, vlLast, nDraw; int nCurrTimer, nLimitTimer, nLimitNodes; bool bUnique; #ifndef CCHESS_A3800 int nBookMoves; uint32_t dwMoveStr; BookStruct bks[MAX_GEN_MOVES]; #endif // 主搜索例程包括以下几个步骤: // 1. 遇到和棋则直接返回 if (Search.pos.IsDraw() || Search.pos.RepStatus(3) > 0) { #ifndef CCHESS_A3800 printf("nobestmove\n"); fflush(stdout); #endif return; } #ifndef CCHESS_A3800 // 2. 从开局库中搜索着法 if (Search.bUseBook) { // a. 获取开局库中的所有走法 nBookMoves = GetBookMoves(Search.pos, Search.szBookFile, bks); if (nBookMoves > 0) { vl = 0; for (i = 0; i < nBookMoves; i ++) { vl += bks[i].wvl; dwMoveStr = MOVE_COORD(bks[i].wmv); printf("info depth 0 score %d pv %.4s\n", bks[i].wvl, (const char *) &dwMoveStr); fflush(stdout); } // b. 根据权重随机选择一个走法 vl = Search.rc4Random.NextLong() % (uint32_t) vl; for (i = 0; i < nBookMoves; i ++) { vl -= bks[i].wvl; if (vl < 0) { break; } } __ASSERT(vl < 0); __ASSERT(i < nBookMoves); // c. 如果开局库中的着法够成循环局面,那么不走这个着法 Search.pos.MakeMove(bks[i].wmv); if (Search.pos.RepStatus(3) == 0) { dwMoveStr = MOVE_COORD(bks[i].wmv); printf("bestmove %.4s", (const char *) &dwMoveStr); // d. 给出后台思考的着法(开局库中第一个即权重最大的后续着法) nBookMoves = GetBookMoves(Search.pos, Search.szBookFile, bks); Search.pos.UndoMakeMove(); if (nBookMoves > 0) { dwMoveStr = MOVE_COORD(bks[0].wmv); printf(" ponder %.4s", (const char *) &dwMoveStr); } printf("\n"); fflush(stdout); return; } Search.pos.UndoMakeMove(); } } #endif // 3. 如果深度为零则返回静态搜索值 if (nDepth == 0) { #ifndef CCHESS_A3800 printf("info depth 0 score %d\n", SearchQuiesc(Search.pos, -MATE_VALUE, MATE_VALUE)); fflush(stdout); printf("nobestmove\n"); fflush(stdout); #endif return; } // 4. 生成根结点的每个着法 Search2.MoveSort.InitRoot(Search.pos, Search.nBanMoves, Search.wmvBanList); // 5. 初始化时间和计数器 Search2.bStop = Search2.bPonderStop = Search2.bPopPv = Search2.bPopCurrMove = false; Search2.nPopDepth = Search2.vlPopValue = 0; Search2.nAllNodes = Search2.nMainNodes = Search2.nUnchanged = 0; Search2.wmvPvLine[0] = 0; ClearKiller(Search2.wmvKiller); ClearHistory(); ClearHash(); // 由于 ClearHash() 需要消耗一定时间,所以计时从这以后开始比较合理 Search2.llTime = GetTime(); vlLast = 0; // 如果走了10回合无用着法,那么允许主动提和,以后每隔8回合提和一次 nDraw = -Search.pos.LastMove().CptDrw; if (nDraw > 5 && ((nDraw - 4) / 2) % 8 == 0) { Search.bDraw = true; } bUnique = false; nCurrTimer = 0; // 6. 做迭代加深搜索 for (i = 1; i <= nDepth; i ++) { // 需要输出主要变例时,第一个"info depth n"是不输出的 #ifndef CCHESS_A3800 if (Search2.bPopPv || Search.bDebug) { printf("info depth %d\n", i); fflush(stdout); } // 7. 根据搜索的时间决定,是否需要输出主要变例和当前思考的着法 Search2.bPopPv = (nCurrTimer > 300); Search2.bPopCurrMove = (nCurrTimer > 3000); #endif // 8. 搜索根结点 vl = SearchRoot(i); if (Search2.bStop) { if (vl > -MATE_VALUE) { vlLast = vl; // 跳出后,vlLast会用来判断认输或投降,所以需要给定最近一个值 } break; // 没有跳出,则"vl"是可靠值 } nCurrTimer = (int) (GetTime() - Search2.llTime); // 9. 如果搜索时间超过适当时限,则终止搜索 if (Search.nGoMode == GO_MODE_TIMER) { // a. 如果没有使用空着裁剪,那么适当时限减半(因为分枝因子加倍了) nLimitTimer = (Search.bNullMove ? Search.nProperTimer : Search.nProperTimer / 2); // b. 如果当前搜索值没有落后前一层很多,那么适当时限减半 nLimitTimer = (vl + DROPDOWN_VALUE >= vlLast ? nLimitTimer / 2 : nLimitTimer); // c. 如果最佳着法连续多层没有变化,那么适当时限减半 nLimitTimer = (Search2.nUnchanged >= UNCHANGED_DEPTH ? nLimitTimer / 2 : nLimitTimer); if (nCurrTimer > nLimitTimer) { if (Search.bPonder) { Search2.bPonderStop = true; // 如果处于后台思考模式,那么只是在后台思考命中后立即中止搜索。 } else { vlLast = vl; break; // 不管是否跳出,"vlLast"都已更新 } } } else if (Search.nGoMode == GO_MODE_NODES) { // nLimitNodes的计算方法与nLimitTimer是一样的 nLimitNodes = (Search.bNullMove ? Search.nNodes : Search.nNodes / 2); nLimitNodes = (vl + DROPDOWN_VALUE >= vlLast ? nLimitNodes / 2 : nLimitNodes); nLimitNodes = (Search2.nUnchanged >= UNCHANGED_DEPTH ? nLimitNodes / 2 : nLimitNodes); // GO_MODE_NODES下是不延长后台思考时间的 if (Search2.nAllNodes > nLimitNodes) { vlLast = vl; break; } } vlLast = vl; // 10. 搜索到杀棋则终止搜索 if (vlLast > WIN_VALUE || vlLast < -WIN_VALUE) { break; } // 11. 是唯一着法,则终止搜索 if (SearchUnique(1 - WIN_VALUE, i)) { bUnique = true; break; } } #ifdef CCHESS_A3800 Search.mvResult = Search2.wmvPvLine[0]; #else // 12. 输出最佳着法及其最佳应对(作为后台思考的猜测着法) if (Search2.wmvPvLine[0] != 0) { PopPvLine(); dwMoveStr = MOVE_COORD(Search2.wmvPvLine[0]); printf("bestmove %.4s", (const char *) &dwMoveStr); if (Search2.wmvPvLine[1] != 0) { dwMoveStr = MOVE_COORD(Search2.wmvPvLine[1]); printf(" ponder %.4s", (const char *) &dwMoveStr); } // 13. 判断是否认输或提和,但是经过唯一着法检验的不适合认输或提和(因为搜索深度不够) if (!bUnique) { if (vlLast > -WIN_VALUE && vlLast < -RESIGN_VALUE) { printf(" resign"); } else if (Search.bDraw && !Search.pos.NullSafe() && vlLast < DRAW_OFFER_VALUE * 2) { printf(" draw"); } } } else { printf("nobestmove"); } printf("\n"); fflush(stdout); #endif }
/** * Implements the state machine. */ void StateMachine(void) { char *gameend; State = STATE_WAITING; CurrentPosition = InitialPosition(); ComputerSide = Black; while(State != STATE_END) { if(AutoSave) { SaveGame(CurrentPosition, AutoSaveFileName); } switch(State) { case STATE_WAITING: if(!XBoardMode) { Print(0, "%s(%d): ", CurrentPosition->turn == White ? "White":"Black", (CurrentPosition->ply/2)+1); } if(!ReadLine(InputBuffer, 1023)) { State = STATE_END; } else { struct Command *command = ParseInput(InputBuffer); if(command) { ExecuteCommand(command); if(command->move != M_NONE) { if(!ForceMode) State = STATE_CALCULATING; } } } break; case STATE_CALCULATING: ComputerSide = CurrentPosition->turn; SearchRoot(CurrentPosition); if(EasyMode || ForceMode) { State = STATE_WAITING; } else { State = STATE_PONDERING; } break; case STATE_PONDERING: switch(PermanentBrain(CurrentPosition)) { case PB_NO_PB_MOVE: State = STATE_WAITING; break; case PB_NO_PB_HIT: State = ForceMode ? STATE_WAITING : STATE_CALCULATING; break; case PB_HIT: if(EasyMode) { State = STATE_WAITING; } break; } break; case STATE_ANALYZING: AnalysisMode(CurrentPosition); break; } /* * Check for game termination */ gameend = GameEnd(CurrentPosition); if(gameend != NULL) { Print(0, "%s\n", gameend); if(State == STATE_ANALYZING) { State = STATE_WAITING; } } } FreePosition(CurrentPosition); }