00001 //---------------------------------------------------------------------------- 00002 /** @file SgSearch.cpp 00003 See SgSearch.h. 00004 */ 00005 //---------------------------------------------------------------------------- 00006 00007 #include "SgSystem.h" 00008 #include "SgSearch.h" 00009 00010 #include <algorithm> 00011 #include <iomanip> 00012 #include <limits> 00013 #include <sstream> 00014 #include <math.h> 00015 #include "SgDebug.h" 00016 #include "SgHashTable.h" 00017 #include "SgMath.h" 00018 #include "SgNode.h" 00019 #include "SgProbCut.h" 00020 #include "SgSearchControl.h" 00021 #include "SgSearchValue.h" 00022 #include "SgTime.h" 00023 #include "SgVector.h" 00024 #include "SgWrite.h" 00025 00026 using namespace std; 00027 00028 //---------------------------------------------------------------------------- 00029 00030 namespace{ 00031 const bool DEBUG_SEARCH = false; 00032 const bool DEBUG_SEARCH_ITERATIONS = false; 00033 } 00034 //---------------------------------------------------------------------------- 00035 00036 void SgKiller::MarkKiller(SgMove killer) 00037 { 00038 if (killer == m_killer1) 00039 ++m_count1; 00040 else if (killer == m_killer2) 00041 { 00042 ++m_count2; 00043 if (m_count1 < m_count2) 00044 { 00045 swap(m_killer1, m_killer2); 00046 swap(m_count1, m_count2); 00047 } 00048 } 00049 else if (m_killer1 == SG_NULLMOVE) 00050 { 00051 m_killer1 = killer; 00052 m_count1 = 1; 00053 } 00054 else 00055 { 00056 m_killer2 = killer; 00057 m_count2 = 1; 00058 } 00059 } 00060 00061 void SgKiller::Clear() 00062 { 00063 m_killer1 = SG_NULLMOVE; 00064 m_count1 = 0; 00065 m_killer2 = SG_NULLMOVE; 00066 m_count2 = 0; 00067 } 00068 00069 //---------------------------------------------------------------------------- 00070 00071 bool SgSearchHashData::IsBetterThan(const SgSearchHashData& data) const 00072 { 00073 if (m_depth > data.m_depth) 00074 return true; 00075 if (m_depth < data.m_depth) 00076 return false; 00077 return (! m_isUpperBound && ! m_isLowerBound) 00078 || (m_isLowerBound && data.m_isLowerBound && m_value > data.m_value) 00079 || (m_isUpperBound && data.m_isUpperBound && m_value < data.m_value); 00080 } 00081 00082 //---------------------------------------------------------------------------- 00083 00084 namespace { 00085 00086 /** copy stack onto sequence, starting with Top() */ 00087 void ReverseCopyStack(const SgSearchStack& moveStack, SgVector<SgMove>& sequence) 00088 { 00089 sequence.Clear(); 00090 for (int i = moveStack.Size() - 1; i >= 0; --i) 00091 sequence.PushBack(moveStack[i]); 00092 } 00093 00094 void WriteSgSearchHashData(std::ostream& str, const SgSearch& search, 00095 const SgSearchHashData& data) 00096 { 00097 str << "move = " << search.MoveString(data.BestMove()) 00098 << " exact = " << data.IsExactValue() 00099 << " value = " << data.Value() 00100 << '\n'; 00101 } 00102 00103 00104 void WriteMoves(const SgSearch& search, const SgVector<SgMove>& sequence) 00105 { 00106 for (SgVectorIterator<SgMove> it(sequence); it; ++it) 00107 SgDebug() << ' ' << search.MoveString(*it); 00108 } 00109 00110 void PrintPV(const SgSearch& search, int depth, int value, 00111 const SgVector<SgMove>& sequence, 00112 bool isExactValue) 00113 { 00114 SgDebug() << "Iteration d = " << depth 00115 << ", value = " << value 00116 << ", exact = " << isExactValue 00117 << ", sequence = "; 00118 WriteMoves(search, sequence); 00119 SgDebug() << '\n'; 00120 } 00121 00122 } // namespace 00123 00124 //---------------------------------------------------------------------------- 00125 00126 const int SgSearch::SG_INFINITY = numeric_limits<int>::max(); 00127 00128 SgSearch::SgSearch(SgSearchHashTable* hash) 00129 : m_hash(hash), 00130 m_tracer(0), 00131 m_currentDepth(0), 00132 m_useScout(false), 00133 m_useKillers(false), 00134 m_useOpponentBest(0), 00135 m_useNullMove(0), 00136 m_nullMoveDepth(2), 00137 m_aborted(false), 00138 m_foundNewBest(false), 00139 m_reachedDepthLimit(false), 00140 m_stat(), 00141 m_timerLevel(0), 00142 m_control(0), 00143 m_probcut(0), 00144 m_abortFrequency(1) 00145 { 00146 InitSearch(); 00147 } 00148 00149 SgSearch::~SgSearch() 00150 { 00151 } 00152 00153 void SgSearch::CallGenerate(SgVector<SgMove>* moves, int depth) 00154 { 00155 Generate(moves, depth); 00156 if (DEBUG_SEARCH) 00157 { 00158 SgDebug() << "SgSearch::CallGenerate: d=" << depth; 00159 WriteMoves(*this, *moves); 00160 SgDebug() << '\n'; 00161 } 00162 } 00163 00164 void SgSearch::InitSearch(int startDepth) 00165 { 00166 m_currentDepth = startDepth; 00167 m_moveStack.Clear(); 00168 m_moveStack.PushBack(SG_NULLMOVE); 00169 m_moveStack.PushBack(SG_NULLMOVE); 00170 if (m_useKillers) 00171 { 00172 for (int i = 0; i <= MAX_KILLER_DEPTH; ++i) 00173 m_killers[i].Clear(); 00174 } 00175 } 00176 00177 bool SgSearch::LookupHash(SgSearchHashData& data) const 00178 { 00179 SG_ASSERT(! data.IsValid()); 00180 if (m_hash == 0 || ! m_hash->Lookup(GetHashCode(), &data)) 00181 return false; 00182 if (DEBUG_SEARCH) 00183 { 00184 SgDebug() << "SgSearch::LookupHash: " << GetHashCode() << ' '; 00185 WriteSgSearchHashData(SgDebug(), *this, data); 00186 } 00187 return true; 00188 } 00189 00190 void SgSearch::OnStartSearch() 00191 { 00192 // Default implementation does nothing 00193 } 00194 00195 void SgSearch::SetSearchControl(SgSearchControl* control) 00196 { 00197 m_control = control; 00198 } 00199 00200 void SgSearch::SetProbCut(SgProbCut* probcut) 00201 { 00202 m_probcut = probcut; 00203 } 00204 00205 void SgSearch::StoreHash(int depth, int value, SgMove move, 00206 bool isUpperBound, bool isLowerBound, bool isExact) 00207 { 00208 SG_ASSERT(m_hash); 00209 SgSearchHashData data(depth, value, move, isUpperBound, isLowerBound, 00210 isExact); 00211 if (DEBUG_SEARCH) 00212 { 00213 SgDebug() << "SgSearch::StoreHash: " << GetHashCode() 00214 << ": "; 00215 WriteSgSearchHashData(SgDebug(), *this, data); 00216 } 00217 m_hash->Store(GetHashCode(), data); 00218 } 00219 00220 bool SgSearch::TraceIsOn() const 00221 { 00222 return m_tracer && m_tracer->TraceIsOn(); 00223 } 00224 00225 bool SgSearch::AbortSearch() 00226 { 00227 if (! m_aborted) 00228 { 00229 // Checking abort is potentially expensive, involves system call. 00230 // Only check every m_abortFrequency nodes 00231 if (m_stat.NumNodes() % m_abortFrequency != 0) 00232 return false; 00233 m_aborted = 00234 m_control 00235 && m_control->Abort(m_timer.GetTime(), m_stat.NumNodes()); 00236 if (! m_aborted && SgUserAbort()) 00237 m_aborted = true; 00238 if (m_aborted && TraceIsOn()) 00239 m_tracer->TraceComment("aborted"); 00240 } 00241 return m_aborted; 00242 } 00243 00244 bool SgSearch::NullMovePrune(int depth, int delta, int beta) 00245 { 00246 SgSearchStack ignoreStack; 00247 bool childIsExact = true; 00248 if (beta >= SG_INFINITY - 1) 00249 return false; 00250 if (CallExecute(SG_PASS, &delta, depth)) 00251 { 00252 float nullvalue = -SearchEngine(depth - delta, 00253 -beta, -beta + 1, ignoreStack, &childIsExact, true); 00254 CallTakeBack(); 00255 if (nullvalue >= beta) 00256 { 00257 if (TraceIsOn()) 00258 m_tracer->TraceComment("null-move-cut"); 00259 return true; 00260 } 00261 } 00262 return false; 00263 } 00264 00265 void SgSearch::GetStatistics(SgSearchStatistics* stat) 00266 { 00267 m_stat.SetTimeUsed(m_timer.GetTime()); 00268 *stat = m_stat; 00269 } 00270 00271 void SgSearch::StartTime() 00272 { 00273 if (++m_timerLevel == 1) 00274 { 00275 m_stat.Clear(); 00276 m_timer.Start(); 00277 } 00278 } 00279 00280 void SgSearch::StopTime() 00281 { 00282 if (--m_timerLevel == 0 && ! m_timer.IsStopped()) 00283 m_timer.Stop(); 00284 } 00285 00286 int SgSearch::CallEvaluate(int depth, bool* isExact) 00287 { 00288 int v = Evaluate(isExact, depth); 00289 if (DEBUG_SEARCH) 00290 SgDebug() << "SgSearch::CallEvaluate d = " << depth 00291 << ", v = " << v 00292 << '\n'; 00293 return v; 00294 } 00295 00296 bool SgSearch::CallExecute(SgMove move, int* delta, int depth) 00297 { 00298 const SgBlackWhite toPlay = GetToPlay(); 00299 if (DEBUG_SEARCH) 00300 SgDebug() << "SgSearch::CallExecute: d = " << depth << ' ' 00301 << SgBW(toPlay) << ' ' << MoveString(move) << '\n'; 00302 if (Execute(move, delta, depth)) 00303 { 00304 m_stat.IncNumMoves(); 00305 if (move == SG_PASS) 00306 m_stat.IncNumPassMoves(); 00307 m_moveStack.PushBack(move); 00308 ++m_currentDepth; 00309 if (TraceIsOn()) 00310 m_tracer->AddTraceNode(move, toPlay); 00311 return true; 00312 } 00313 return false; 00314 } 00315 00316 void SgSearch::CallTakeBack() 00317 { 00318 if (DEBUG_SEARCH) 00319 SgDebug() << "SgSearch::CallTakeBack\n"; 00320 TakeBack(); 00321 if (TraceIsOn()) 00322 m_tracer->TakeBackTraceNode(); 00323 m_moveStack.PopBack(); 00324 --m_currentDepth; 00325 } 00326 00327 void SgSearch::CreateTracer() 00328 { 00329 m_tracer = new SgSearchTracer(0); 00330 } 00331 00332 void SgSearch::AddSequenceToHash(const SgVector<SgMove>& sequence, int depth) 00333 { 00334 if (! m_hash) 00335 return; 00336 int numMovesToUndo = 0; 00337 for (SgVectorIterator<SgMove> iter(sequence); iter; ++iter) 00338 { 00339 SgMove move = *iter; 00340 int delta = DEPTH_UNIT; 00341 if (CallExecute(move, &delta, depth)) 00342 { 00343 // Store move with position before the move is played. 00344 CallTakeBack(); 00345 00346 // Move is the only relevant data for seeding the hash table. 00347 SgSearchHashData data(0, 0, move); 00348 SG_ASSERT(move != SG_NULLMOVE); 00349 m_hash->Store(GetHashCode(), data); 00350 if (DEBUG_SEARCH) 00351 SgDebug() << "SgSearch::AddSequenceToHash: " 00352 << MoveString(move) << '\n'; 00353 // Execute move again. 00354 int delta = DEPTH_UNIT; 00355 if (CallExecute(move, &delta, depth)) 00356 ++numMovesToUndo; 00357 else // it just worked, should not fail now. 00358 SG_ASSERT(false); 00359 } 00360 else 00361 break; 00362 } 00363 00364 // Restore the original position. 00365 while (--numMovesToUndo >= 0) 00366 CallTakeBack(); 00367 } 00368 00369 int SgSearch::DFS(int startDepth, int depthLimit, 00370 int boundLo, int boundHi, 00371 SgVector<SgMove>* sequence, bool* isExactValue) 00372 { 00373 InitSearch(startDepth); 00374 SG_ASSERT(m_currentDepth == startDepth); 00375 SG_ASSERT(sequence); 00376 m_aborted = false; 00377 m_foundNewBest = false; 00378 SgSearchStack moveStack; 00379 int value = SearchEngine(depthLimit * DEPTH_UNIT, 00380 boundLo, boundHi, moveStack, 00381 isExactValue); 00382 ReverseCopyStack(moveStack, *sequence); 00383 return value; 00384 } 00385 00386 00387 int SgSearch::DepthFirstSearch(int depthLimit, int boundLo, int boundHi, 00388 SgVector<SgMove>* sequence, bool clearHash, 00389 SgNode* traceNode) 00390 { 00391 SG_ASSERT(sequence); 00392 OnStartSearch(); 00393 if (m_tracer && traceNode) 00394 { 00395 SG_ASSERT(m_tracer->TraceNode() == 0); 00396 SG_ASSERT(m_tracer->TraceIsOn()); 00397 m_tracer->InitTracing("DepthFirstSearch"); 00398 } 00399 StartTime(); 00400 if (clearHash && m_hash) 00401 { 00402 m_hash->Clear(); 00403 AddSequenceToHash(*sequence, 0); 00404 } 00405 m_depthLimit = 0; 00406 bool isExactValue = true; 00407 int value = DFS(0, depthLimit, boundLo, boundHi, sequence, &isExactValue); 00408 StopTime(); 00409 if (m_tracer && traceNode) 00410 m_tracer->AppendTrace(traceNode); 00411 return value; 00412 } 00413 00414 int SgSearch::IteratedSearch(int depthMin, int depthMax, int boundLo, 00415 int boundHi, SgVector<SgMove>* sequence, 00416 bool clearHash, SgNode* traceNode) 00417 { 00418 SG_ASSERT(sequence); 00419 OnStartSearch(); 00420 if (m_tracer && traceNode) 00421 { 00422 SG_ASSERT(m_tracer->TraceNode() == 0); 00423 SG_ASSERT(m_tracer->TraceIsOn()); 00424 m_tracer->InitTracing("IteratedSearch"); 00425 } 00426 StartTime(); 00427 if (clearHash && m_hash) 00428 { 00429 m_hash->Clear(); 00430 AddSequenceToHash(*sequence, 0); 00431 } 00432 00433 int value = 0; 00434 m_depthLimit = depthMin; 00435 // done in DFS, but that's too late, is tested after StartOfDepth 00436 m_aborted = false; 00437 00438 // Keep track of value and sequence of previous iteration in case search 00439 // gets aborted (since value and sequence are then ill-defined). 00440 m_prevValue = 0; 00441 m_prevSequence.Clear(); 00442 bool isExactValue = true; 00443 00444 do 00445 { 00446 if (m_control != 0 00447 && ! m_control->StartNextIteration(m_depthLimit, 00448 m_timer.GetTime(), 00449 m_stat.NumNodes())) 00450 SetAbortSearch(); 00451 if (m_aborted) 00452 break; 00453 StartOfDepth(m_depthLimit); 00454 00455 // Record depth limit of depths where we actually do some search. 00456 m_stat.SetDepthReached(m_depthLimit); 00457 00458 // Remember whether we actually reach the depth limit. If not, no 00459 // sense in increasing the depth limit, won't find anything new. 00460 m_reachedDepthLimit = false; 00461 isExactValue = true; 00462 m_foundNewBest = false; 00463 00464 // Fixed-depth search. 00465 value = DFS(0, m_depthLimit, boundLo, boundHi, sequence, 00466 &isExactValue); 00467 00468 // Restore result of previous iteration if aborted. 00469 if (m_aborted) 00470 { 00471 if (m_prevSequence.NonEmpty() && ! m_foundNewBest) 00472 { 00473 value = m_prevValue; 00474 *sequence = m_prevSequence; 00475 } 00476 break; 00477 } 00478 else 00479 { 00480 if (DEBUG_SEARCH_ITERATIONS) 00481 PrintPV(*this, m_depthLimit, value, *sequence, isExactValue); 00482 m_prevValue = value; 00483 m_prevSequence = *sequence; 00484 } 00485 00486 // Stop iteration as soon as exact result or a bounding value found. 00487 if (isExactValue || value <= boundLo || boundHi <= value) 00488 break; 00489 00490 ++m_depthLimit; 00491 00492 } while ( m_depthLimit <= depthMax 00493 && ! isExactValue 00494 && ! m_aborted 00495 && (! CheckDepthLimitReached() || m_reachedDepthLimit) 00496 ); 00497 00498 StopTime(); 00499 if (m_tracer && traceNode) 00500 m_tracer->AppendTrace(traceNode); 00501 return value; 00502 } 00503 00504 bool SgSearch::TryMove(SgMove move, const SgVector<SgMove>& specialMoves, 00505 const int depth, 00506 const int alpha, const int beta, 00507 int& loValue, int& hiValue, 00508 SgSearchStack& stack, 00509 bool& allExact, 00510 bool& isCutoff) 00511 { 00512 if (specialMoves.Contains(move)) // already tried move before 00513 return false; 00514 00515 int delta = DEPTH_UNIT; 00516 if (! CallExecute(move, &delta, depth)) 00517 return false; 00518 00519 bool childIsExact = true; 00520 SgSearchStack newStack; 00521 int merit = -SearchEngine(depth - delta, -hiValue, 00522 -max(loValue, alpha), newStack, 00523 &childIsExact); 00524 if (loValue < merit && ! m_aborted) // new best move 00525 { 00526 loValue = merit; 00527 if (m_useScout) 00528 { 00529 // If getting a move that's better than what we have 00530 // so far, not good enough to cause a cutoff, was 00531 // searched with a narrow window, and doesn't 00532 // immediately lead to a terminal node, then search 00533 // again with a wide window to get a more precise 00534 // value. 00535 if ( alpha < merit 00536 && merit < beta 00537 && delta < depth 00538 ) 00539 { 00540 childIsExact = true; 00541 loValue = -SearchEngine(depth-delta, -beta, 00542 -merit, newStack, 00543 &childIsExact); 00544 } 00545 hiValue = max(loValue, alpha) + 1; 00546 } 00547 stack.CopyFrom(newStack); 00548 stack.Push(move); 00549 SG_ASSERT(move != SG_NULLMOVE); 00550 if (m_currentDepth == 1 && ! m_aborted) 00551 m_foundNewBest = true; 00552 } 00553 if (! childIsExact) 00554 allExact = false; 00555 CallTakeBack(); 00556 if (loValue >= beta) 00557 { 00558 // Move generated a cutoff: add this move to the list of 00559 // killers. 00560 if (m_useKillers && m_currentDepth <= MAX_KILLER_DEPTH) 00561 m_killers[m_currentDepth].MarkKiller(move); 00562 if (TraceIsOn()) 00563 m_tracer->TraceComment("b-cut"); 00564 isCutoff = true; 00565 } 00566 return true; 00567 } 00568 00569 bool SgSearch::TrySpecialMove(SgMove move, SgVector<SgMove>& specialMoves, 00570 const int depth, 00571 const int alpha, const int beta, 00572 int& loValue, int& hiValue, 00573 SgSearchStack& stack, 00574 bool& allExact, 00575 bool& isCutoff) 00576 00577 { 00578 if (specialMoves.Contains(move)) 00579 return false; 00580 bool executed = TryMove(move, specialMoves, 00581 depth, alpha, beta, 00582 loValue, hiValue, stack, 00583 allExact, isCutoff); 00584 specialMoves.PushBack(move); 00585 return executed; 00586 } 00587 00588 int SgSearch::SearchEngine(const int depth, int alpha, int beta, 00589 SgSearchStack& stack, bool* isExactValue, 00590 bool lastNullMove) 00591 { 00592 SG_ASSERT(stack.IsEmpty() || stack.Top() != SG_NULLMOVE); 00593 SG_ASSERT(alpha < beta); 00594 00595 // Only place we check whether the search has been newly aborted. In all 00596 // other places, just check whether search was aborted before. 00597 // AR: what to return here? 00598 // if - (SG_INFINITY-1), then will be positive on next level? 00599 if (AbortSearch()) 00600 { 00601 *isExactValue = false; 00602 return alpha; 00603 } 00604 00605 // Null move pruning 00606 if ( m_useNullMove 00607 && depth > 0 00608 && ! lastNullMove 00609 && NullMovePrune(depth, DEPTH_UNIT * (1 + m_nullMoveDepth), beta) 00610 ) 00611 { 00612 *isExactValue = false; 00613 return beta; 00614 } 00615 00616 // ProbCut 00617 if (m_probcut && m_probcut->IsEnabled()) 00618 { 00619 int probCutValue; 00620 if (m_probcut->ProbCut(*this, depth, alpha, beta, stack, 00621 isExactValue, &probCutValue) 00622 ) 00623 return probCutValue; 00624 } 00625 00626 m_stat.IncNumNodes(); 00627 bool hasMove = false; // true if a move has been executed at this level 00628 int loValue = -(SG_INFINITY - 1); 00629 m_reachedDepthLimit = m_reachedDepthLimit || (depth <= 0); 00630 00631 // check whether position is solved from hash table. 00632 SgSearchHashData data; // initialized to ! data.IsValid() 00633 if (LookupHash(data)) 00634 { 00635 if (data.IsExactValue()) // exact value: stop search 00636 { 00637 *isExactValue = true; 00638 stack.Clear(); 00639 if (data.BestMove() != SG_NULLMOVE) 00640 stack.Push(data.BestMove()); 00641 if (TraceIsOn()) 00642 m_tracer->TraceValue(data.Value(), GetToPlay(), 00643 "exact-hash", true); 00644 return data.Value(); 00645 } 00646 } 00647 00648 bool allExact = true; // Do all moves have exact evaluation? 00649 if (depth > 0 && ! EndOfGame()) 00650 { 00651 // Check whether current position has already been encountered. 00652 SgMove tryFirst = SG_NULLMOVE; 00653 SgMove opponentBest = SG_NULLMOVE; 00654 if (data.IsValid()) 00655 { 00656 if (data.Depth() > 0) 00657 { 00658 tryFirst = data.BestMove(); 00659 SG_ASSERT(tryFirst != SG_NULLMOVE); 00660 } 00661 00662 // If data returned from hash table is based on equal or deeper 00663 // search than what we plan to do right now, just use that data. 00664 // The hash table may have deeper data for the current position 00665 // since the same number of moves may result in more 'depth' 00666 // left if the 'delta' for the moves has been smaller, which 00667 // will happen when most moves came from the cache. 00668 if (depth <= data.Depth()) 00669 { 00670 // Rely on value returned from hash table to be for the 00671 // current position. In Go, it can happen that the move is 00672 // not legal (ko recapture) 00673 int delta = DEPTH_UNIT; 00674 bool canExecute = CallExecute(tryFirst, &delta, depth); 00675 if (canExecute) 00676 CallTakeBack(); 00677 else 00678 tryFirst = SG_NULLMOVE; 00679 if (tryFirst != SG_NULLMOVE || data.IsExactValue()) 00680 { 00681 // getting a deep enough hash hit or an exact value 00682 // is as good as reaching the depth limit by search. 00683 m_reachedDepthLimit = true; 00684 // Update bounds with data from cache. 00685 data.AdjustBounds(&alpha, &beta); 00686 00687 if (alpha >= beta) 00688 { 00689 *isExactValue = data.IsExactValue(); 00690 stack.Clear(); 00691 if (tryFirst != SG_NULLMOVE) 00692 stack.Push(tryFirst); 00693 if (TraceIsOn()) 00694 m_tracer->TraceValue(data.Value(), GetToPlay(), 00695 "Hash hit", *isExactValue); 00696 return data.Value(); 00697 } 00698 } 00699 } 00700 00701 int delta = DEPTH_UNIT; 00702 if ( tryFirst != SG_NULLMOVE 00703 && CallExecute(tryFirst, &delta, depth) 00704 ) 00705 { 00706 bool childIsExact = true; 00707 loValue = -SearchEngine(depth-delta, -beta, -alpha, stack, 00708 &childIsExact); 00709 if (TraceIsOn()) 00710 m_tracer->TraceComment("tryFirst"); 00711 CallTakeBack(); 00712 hasMove = true; 00713 if (m_aborted) 00714 { 00715 if (TraceIsOn()) 00716 m_tracer->TraceComment("aborted"); 00717 *isExactValue = false; 00718 return (1 < m_currentDepth) ? alpha : loValue; 00719 } 00720 if (stack.NonEmpty()) 00721 { 00722 opponentBest = stack.Top(); 00723 SG_ASSERT(opponentBest != SG_NULLMOVE); 00724 } 00725 stack.Push(tryFirst); 00726 if (! childIsExact) 00727 allExact = false; 00728 if (loValue >= beta) 00729 { 00730 if (TraceIsOn()) 00731 m_tracer->TraceValue(loValue, GetToPlay()); 00732 // store in hash table. Known to be exact only if 00733 // solved for one player. 00734 bool isExact = SgSearchValue::IsSolved(loValue); 00735 StoreHash(depth, loValue, tryFirst, 00736 false /*isUpperBound*/, 00737 true /*isLowerBound*/, isExact); 00738 *isExactValue = isExact; 00739 if (TraceIsOn()) 00740 m_tracer->TraceValue(loValue, GetToPlay(), 00741 "b-cut", isExact); 00742 return loValue; 00743 } 00744 } 00745 } 00746 00747 // 'hiValue' is equal to 'beta' for alpha-beta algorithm, and gets set 00748 // to alpha+1 for Scout, except for the first move. 00749 int hiValue = 00750 (hasMove && m_useScout) ? max(loValue, alpha) + 1 : beta; 00751 bool foundCutoff = false; 00752 SgVector<SgMove> specialMoves; 00753 // Don't execute 'tryFirst' again. 00754 if (tryFirst != SG_NULLMOVE) 00755 specialMoves.PushBack(tryFirst); 00756 00757 // Heuristic: "a good move for my opponent is a good move for me" 00758 if ( ! foundCutoff 00759 && m_useOpponentBest 00760 && opponentBest != SG_NULLMOVE 00761 && TrySpecialMove(opponentBest, specialMoves, 00762 depth, alpha, beta, 00763 loValue, hiValue, stack, 00764 allExact, foundCutoff) 00765 ) 00766 hasMove = true; 00767 00768 if ( ! foundCutoff 00769 && m_useKillers 00770 && m_currentDepth <= MAX_KILLER_DEPTH 00771 ) 00772 { 00773 SgMove killer1 = m_killers[m_currentDepth].GetKiller1(); 00774 if ( killer1 != SG_NULLMOVE 00775 && TrySpecialMove(killer1, specialMoves, 00776 depth, alpha, beta, 00777 loValue, hiValue, stack, 00778 allExact, foundCutoff) 00779 ) 00780 hasMove = true; 00781 SgMove killer2 = m_killers[m_currentDepth].GetKiller2(); 00782 if ( ! foundCutoff 00783 && killer2 != SG_NULLMOVE 00784 && TrySpecialMove(killer2, specialMoves, 00785 depth, alpha, beta, 00786 loValue, hiValue, stack, 00787 allExact, foundCutoff) 00788 ) 00789 hasMove = true; 00790 } 00791 00792 // Generate the moves for this position. 00793 SgVector<SgMove> moves; 00794 if (! foundCutoff && ! m_aborted) 00795 { 00796 CallGenerate(&moves, depth); 00797 // Iterate through all the moves to find the best move and 00798 // correct value for this position. 00799 for (SgVectorIterator<SgMove> it(moves); it && ! foundCutoff; ++it) 00800 { 00801 if (TryMove(*it, specialMoves, 00802 depth, alpha, beta, 00803 loValue, hiValue, stack, 00804 allExact, foundCutoff) 00805 ) 00806 hasMove = true; 00807 if (! foundCutoff && m_aborted) 00808 { 00809 if (TraceIsOn()) 00810 m_tracer->TraceComment("ABORTED"); 00811 *isExactValue = false; 00812 return (1 < m_currentDepth) ? alpha : loValue; 00813 } 00814 } 00815 } 00816 00817 // Make sure the move added to the hash table really got generated. 00818 #ifndef NDEBUG 00819 if (hasMove && stack.NonEmpty() && ! m_aborted) 00820 { 00821 SgMove bestMove = stack.Top(); 00822 SG_ASSERT(bestMove != SG_NULLMOVE); 00823 SG_ASSERT( specialMoves.Contains(bestMove) 00824 || moves.Contains(bestMove) 00825 ); 00826 } 00827 #endif 00828 } 00829 00830 bool isSolved = ! m_aborted; 00831 if (! m_aborted) 00832 { 00833 // Evaluate position if terminal node (either no moves generated, or 00834 // none of the generated moves were legal). 00835 bool solvedByEval = false; 00836 if (! hasMove) 00837 { 00838 m_stat.IncNumEvals(); 00839 stack.Clear(); 00840 loValue = CallEvaluate(depth, &solvedByEval); 00841 } 00842 00843 // Save data about current position in the hash table. 00844 isSolved = solvedByEval 00845 || SgSearchValue::IsSolved(loValue) 00846 || (hasMove && allExact); 00847 // || EndOfGame(); bug: cannot store exact score after two passes. 00848 if ( m_hash 00849 && ! m_aborted 00850 && (isSolved || stack.NonEmpty()) 00851 ) 00852 { 00853 SgMove bestMove = SG_NULLMOVE; 00854 if (stack.NonEmpty()) 00855 { 00856 bestMove = stack.Top(); 00857 SG_ASSERT(bestMove != SG_NULLMOVE); 00858 } 00859 SG_ASSERT(alpha <= beta); 00860 StoreHash(depth, loValue, bestMove, 00861 (loValue <= alpha) /* upper */, 00862 (beta <= loValue) /* lower*/, isSolved); 00863 } 00864 } 00865 00866 // If aborted search and didn't find any values, just return alpha. 00867 // Can't return best found so far, since may not have tried the optimal 00868 // counter-move yet. However, return best value found so far on top 00869 // level, since assuming hash move will have been tried first. 00870 if (m_aborted && (1 < m_currentDepth || loValue < alpha)) 00871 loValue = alpha; 00872 00873 *isExactValue = isSolved; 00874 if (TraceIsOn()) 00875 m_tracer->TraceValue(loValue, GetToPlay(), 0, isSolved); 00876 SG_ASSERT(stack.IsEmpty() || stack.Top() != SG_NULLMOVE); 00877 return loValue; 00878 } 00879 00880 void SgSearch::StartOfDepth(int depth) 00881 { 00882 if (DEBUG_SEARCH) 00883 SgDebug() << "SgSearch::StartOfDepth: " << depth << '\n'; 00884 // add another separate search tree. We are either at the root of the 00885 // previous depth tree or, if depth==0, one level higher at the 00886 // linking node. 00887 if (m_tracer && ! m_aborted) 00888 m_tracer->StartOfDepth(depth); 00889 } 00890 00891 //---------------------------------------------------------------------------- 00892