00001 //---------------------------------------------------------------------------- 00002 /** @file SgSearch.h 00003 Search engine. 00004 00005 SgSearch is the search engine of the Smart Game Board, providing depth- 00006 first search with iterative deepening and transposition tables. 00007 */ 00008 //---------------------------------------------------------------------------- 00009 00010 #ifndef SG_SEARCH_H 00011 #define SG_SEARCH_H 00012 00013 #include "SgBlackWhite.h" 00014 #include "SgHash.h" 00015 #include "SgMove.h" 00016 #include "SgSearchStatistics.h" 00017 #include "SgSearchTracer.h" 00018 #include "SgStack.h" 00019 #include "SgTimer.h" 00020 #include "SgVector.h" 00021 00022 template <class DATA> class SgHashTable; 00023 class SgNode; 00024 class SgProbCut; 00025 class SgSearchControl; 00026 00027 //---------------------------------------------------------------------------- 00028 00029 /** Used in class SgSearch to implement killer heuristic. 00030 Keeps track of two moves that have been successful at a particular level. 00031 The moves are sorted by frequency. 00032 */ 00033 class SgKiller 00034 { 00035 public: 00036 SgKiller(); 00037 00038 void MarkKiller(SgMove killer); 00039 00040 void Clear(); 00041 00042 SgMove GetKiller1() const; 00043 00044 SgMove GetKiller2() const; 00045 00046 private: 00047 SgMove m_killer1; 00048 00049 SgMove m_killer2; 00050 00051 int m_count1; 00052 00053 int m_count2; 00054 }; 00055 00056 inline SgKiller::SgKiller() 00057 : m_killer1(SG_NULLMOVE), 00058 m_killer2(SG_NULLMOVE), 00059 m_count1(0), 00060 m_count2(0) 00061 { 00062 } 00063 00064 inline SgMove SgKiller::GetKiller1() const 00065 { 00066 return m_killer1; 00067 } 00068 00069 inline SgMove SgKiller::GetKiller2() const 00070 { 00071 return m_killer2; 00072 } 00073 00074 //---------------------------------------------------------------------------- 00075 00076 /** Hash data used in class SgSearch. */ 00077 class SgSearchHashData 00078 { 00079 public: 00080 SgSearchHashData(); 00081 00082 SgSearchHashData(int depth, signed value, SgMove bestMove, 00083 bool isOnlyUpperBound = false, 00084 bool isOnlyLowerBound = false, 00085 bool isExactValue = false); 00086 00087 ~SgSearchHashData(); 00088 00089 int Depth() const; 00090 00091 int Value() const; 00092 00093 SgMove BestMove() const; 00094 00095 bool IsOnlyUpperBound() const; 00096 00097 bool IsOnlyLowerBound() const; 00098 00099 void AdjustBounds(int* lower, int* upper); 00100 00101 bool IsBetterThan(const SgSearchHashData& data) const; 00102 00103 bool IsValid() const; 00104 00105 bool IsExactValue() const; 00106 00107 void Invalidate(); 00108 00109 void AgeData(); 00110 00111 private: 00112 unsigned m_depth : 12; 00113 00114 unsigned m_isUpperBound : 1; 00115 00116 unsigned m_isLowerBound : 1; 00117 00118 unsigned m_isValid : 1; 00119 00120 unsigned m_isExactValue : 1; 00121 00122 signed m_value : 16; 00123 00124 SgMove m_bestMove; 00125 }; 00126 00127 typedef SgHashTable<SgSearchHashData> SgSearchHashTable; 00128 00129 inline SgSearchHashData::SgSearchHashData() 00130 : m_depth(0), 00131 m_isUpperBound(false), 00132 m_isLowerBound(false), 00133 m_isValid(false), 00134 m_isExactValue(false), 00135 m_value(-1), 00136 m_bestMove(SG_NULLMOVE) 00137 { } 00138 00139 inline SgSearchHashData::SgSearchHashData(int depth, signed value, 00140 SgMove bestMove, 00141 bool isOnlyUpperBound, 00142 bool isOnlyLowerBound, 00143 bool isExactValue) 00144 : m_depth(depth), 00145 m_isUpperBound(isOnlyUpperBound), 00146 m_isLowerBound(isOnlyLowerBound), 00147 m_isValid(true), 00148 m_isExactValue(isExactValue), 00149 m_value(value), 00150 m_bestMove(bestMove) 00151 { 00152 // Ensure value fits in 16 bits 00153 SG_ASSERT(m_value == value); 00154 } 00155 00156 inline SgSearchHashData::~SgSearchHashData() 00157 { 00158 } 00159 00160 inline int SgSearchHashData::Depth() const 00161 { 00162 return static_cast<int> (m_depth); 00163 } 00164 00165 inline int SgSearchHashData::Value() const 00166 { 00167 return static_cast<int> (m_value); 00168 } 00169 00170 inline SgMove SgSearchHashData::BestMove() const 00171 { 00172 return m_bestMove; 00173 } 00174 00175 inline bool SgSearchHashData::IsOnlyUpperBound() const 00176 { 00177 return m_isUpperBound != 0; 00178 } 00179 00180 inline bool SgSearchHashData::IsOnlyLowerBound() const 00181 { 00182 return m_isLowerBound != 0; 00183 } 00184 00185 inline void SgSearchHashData::AdjustBounds(int* lower, int* upper) 00186 { 00187 if (IsOnlyUpperBound()) 00188 *upper = std::min(*upper, Value()); 00189 else if (IsOnlyLowerBound()) 00190 *lower = std::max(*lower, Value()); 00191 else 00192 { // If not just an upper or lower bound, then this is precise. 00193 *lower = Value(); 00194 *upper = Value(); 00195 } 00196 } 00197 00198 inline bool SgSearchHashData::IsValid() const 00199 { 00200 return m_isValid; 00201 } 00202 00203 inline bool SgSearchHashData::IsExactValue() const 00204 { 00205 return m_isExactValue; 00206 } 00207 00208 inline void SgSearchHashData::Invalidate() 00209 { 00210 m_isValid = false; 00211 } 00212 00213 inline void SgSearchHashData::AgeData() 00214 { 00215 m_depth = 0; 00216 } 00217 00218 //---------------------------------------------------------------------------- 00219 namespace SgSearchLimit { 00220 static const int MAX_DEPTH = 256; 00221 } 00222 00223 typedef SgStack<SgMove, SgSearchLimit::MAX_DEPTH> SgSearchStack; 00224 //---------------------------------------------------------------------------- 00225 00226 /** Alpha-beta search. 00227 The problem-specific part of the search is isolated in five methods: 00228 move generation, position evaluation, executing and taking back moves, 00229 and time control. These need to be overridden for each kind of search. 00230 The evaluation may employ lookahead or a quiescence search to 00231 find the value. If so, the sequence from the current position 00232 to the position where that value is really reached is returned, 00233 otherwise the empty list is returned. 00234 00235 @todo Why does AmaSearch::Evaluate need the hash table, shouldn't that be 00236 done in SgSearch? 00237 @todo Remove m_depth, pass as argument to Evaluate instead 00238 @todo Use best-response as move ordering heuristic 00239 */ 00240 class SgSearch 00241 { 00242 public: 00243 /** One DEPTH_UNIT corresponds to the default search depth for one move, 00244 in the internal representation of search. Used for fractional ply 00245 extensions. E.g. extending from atari in Go may count as only 00246 1/8 ply = DEPTH_UNIT/8. This way forced lines can be searched 00247 much deeper at a low nominal search depth. 00248 */ 00249 static const int DEPTH_UNIT = 100; 00250 00251 /** Remark: could make it 512 if deep ladder search a problem */ 00252 static const int MAX_DEPTH = 256; 00253 00254 /** Infinity for windowed searches. 00255 Needs prefix SG_ even if not in global namespace, because there is a 00256 conflict with a global macro INFINITY. 00257 */ 00258 static const int SG_INFINITY; 00259 00260 /** Constructor. 00261 @param hash Hash table to use or 0, if no hash table should be used. 00262 */ 00263 SgSearch(SgSearchHashTable* hash); 00264 00265 virtual ~SgSearch(); 00266 00267 /** Stop search if depth limit was not reached in current iteration. 00268 Usually this should return true, but it depends on the move generation 00269 in the subclass. For example, if the move generation prunes some 00270 moves at lower depths, because the goal cannot be reached at the 00271 current depth, this function has to return false. 00272 */ 00273 virtual bool CheckDepthLimitReached() const = 0; 00274 00275 const SgSearchHashTable* HashTable() const; 00276 00277 void SetHashTable(SgSearchHashTable* hashtable); 00278 00279 const SgSearchControl* SearchControl() const; 00280 00281 /** Search control. 00282 Set the abort checking function; pass 0 to disable abort checking. 00283 Caller keeps ownership of control. 00284 */ 00285 void SetSearchControl(SgSearchControl* control); 00286 00287 /** ProbCut. 00288 Set the ProbCut bounds; pass 0 to disable ProbCut. 00289 Caller keeps ownership of probcut. 00290 */ 00291 void SetProbCut(SgProbCut* probcut); 00292 00293 /** Convert move to string (game dependent). */ 00294 virtual std::string MoveString(SgMove move) const = 0; 00295 00296 virtual void SetToPlay(SgBlackWhite toPlay) = 0; 00297 00298 /** Hook function called at the beginning of a search. 00299 Default implementation does nothing. 00300 */ 00301 virtual void OnStartSearch(); 00302 00303 /** Looks 'depthLimit' moves ahead to find the value of the 00304 current position and the optimal sequence. 00305 No values outside the range ['boundLo'..'boundHi'] are expected; 00306 if values outside that range are encountered, a value <= 'boundLo' 00307 or >= 'boundHi' will be returned, and the search should be repeated 00308 with new bounds. 00309 If node is not 0, then add the whole search tree below '*node'. 00310 The hash table is seeded with the sequence passed in, so that partial 00311 results from a previous search can speed up a re-search. 00312 */ 00313 int DepthFirstSearch(int depthLimit, int boundLo, int boundHi, 00314 SgVector<SgMove>* sequence, bool clearHash = true, 00315 SgNode* traceNode = 0); 00316 00317 /** Call DepthFirstSearch with window [-SG_INFINITY,+SG_INFINITY] */ 00318 int DepthFirstSearch(int depthLimit, SgVector<SgMove>* sequence, 00319 bool clearHash = true, SgNode* traceNode = 0); 00320 00321 /** Calls DepthFirstSearch repeatedly with the depth limit starting at 00322 'depthMin' and increasing with each iteration. 00323 Stops when the problem is solved, meaning that a result that is 00324 definitely good for one of the players is reached, or when 'depthMax' 00325 is reached. The bound parameters are just passed to DepthFirstSearch. 00326 If node is not 0, then add the whole search tree below '*node'. 00327 The hash table is seeded with the sequence passed in, so that partial 00328 results from a previous search can speed up a re-search. 00329 */ 00330 int IteratedSearch(int depthMin, int depthMax, int boundLo, 00331 int boundHi, SgVector<SgMove>* sequence, 00332 bool clearHash = true, SgNode* traceNode = 0); 00333 00334 00335 /** Call IteratedSearch with window [-SG_INFINITY,+SG_INFINITY] */ 00336 int IteratedSearch(int depthMin, int depthMax, SgVector<SgMove>* sequence, 00337 bool clearHash = true, SgNode* traceNode = 0); 00338 00339 /** During IteratedSearch or CombinedSearch, this returns the current 00340 depth that's being searched to. 00341 */ 00342 int IteratedSearchDepthLimit() const; 00343 00344 /** Called at start of each depth level of IteratedSearch. 00345 Can be overridden to adapt search (or instrumentation) to current 00346 depth. Must call inherited. 00347 */ 00348 virtual void StartOfDepth(int depthLimit); 00349 00350 /** Return whether the search was aborted. */ 00351 bool Aborted() const; 00352 00353 /** Mark this search as aborted. 00354 Will terminate next time AbortSearch gets called. Or mark search as 00355 not having been aborted (e.g. when minimizing area and first search 00356 succeeds but second one doesn't have enough time to complete). 00357 */ 00358 void SetAbortSearch(bool fAborted = true); 00359 00360 void SetScout(bool flag = true); 00361 00362 void SetKillers(bool flag = true); 00363 00364 void SetOpponentBest(bool flag = true); 00365 00366 void SetNullMove(bool flag = true); 00367 00368 void SetNullMoveDepth(int depth); 00369 00370 /** Get the current statistics. Can be called during search. */ 00371 void GetStatistics(SgSearchStatistics* stat); 00372 00373 const SgSearchStatistics& Statistics() const 00374 { 00375 return m_stat; 00376 } 00377 00378 /** Starts the clock and clears the statistics. 00379 Can be nested; only the outermost call actually does anything. 00380 */ 00381 void StartTime(); 00382 00383 /** Stops the clock and clears the statistics. 00384 Can be nested; only the outermost call actually does anything. 00385 */ 00386 void StopTime(); 00387 00388 /** Generate moves. 00389 @param moves The returned list is the set of moves to be tried. 00390 These moves will be tested for legality, so illegal moves can also be 00391 included if that speeds up move generation. If the empty list 00392 is returned, the evaluation function will be called with the 00393 same position right away (thus an evaluation done during move 00394 generation can be saved and returned from the move evaluation). 00395 @param depth The remaining depth until a terminal node is reached 00396 (height > 0). Note that this is a fractional value: each move may be 00397 counted as less than one full move to look deeper in some variations. 00398 The value is expressed in DEPTH_UNIT rather than using float to be 00399 stored compactly in the hash table. 00400 */ 00401 virtual void Generate(SgVector<SgMove>* moves, int depth) = 0; 00402 00403 /** The returned value reflects the value of the position, with 00404 positive values being good for the current player (ToPlay). 00405 @param isExact Return value, if set, the value is exact, even if it is 00406 not a terminal positions and Generate would generate moves. 00407 @param depth See SgSearch::Generate() 00408 @return The evaluation of the current position. 00409 */ 00410 virtual int Evaluate(bool* isExact, int depth) = 0; 00411 00412 /** Return true if the move was executed, false if it was illegal 00413 and could not be played. 00414 @param move 00415 @param delta The amount by which that move shall decrement the current 00416 depth. Normal is DEPTH_UNIT, but forcing moves may return a delta of 00417 zero to look deeper into one variation. 00418 @param depth See SgSearch::Generate() (Execute could need to know the 00419 depth to reject moves depending on the depth that were originally 00420 generated, e.g. used in ExCaptureTask::Execute) 00421 */ 00422 virtual bool Execute(SgMove move, int* delta, int depth) = 0; 00423 00424 /** Takes back the most recent move successfully executed by Execute. */ 00425 virtual void TakeBack() = 0; 00426 00427 /** Return the current player. */ 00428 virtual SgBlackWhite GetToPlay() const = 0; 00429 00430 /** Test whether search should be aborted. 00431 Return true to abort search. Default implementation checks Abort() of 00432 the installed search control. 00433 */ 00434 virtual bool AbortSearch(); 00435 00436 /** Return the hash code for the current position. */ 00437 virtual SgHashCode GetHashCode() const = 0; 00438 00439 /** The current depth of the search, incremented by 1 for each 00440 move that's played. Value is 0 at root level of search. 00441 */ 00442 int CurrentDepth() const; 00443 00444 /** Indicates which move in the movelist at the previous level was 00445 executed. 00446 This may be necessary if the value or moves at a 00447 position depend on the sequence leading to that position. 00448 */ 00449 SgMove PrevMove() const; 00450 00451 /** The move prior to the previous move. 00452 Knowing both the last two moves is necessary to decide whether the 00453 current position is a seki, where it's best for both players to pass. 00454 */ 00455 SgMove PrevMove2() const; 00456 00457 /** Is the game over? */ 00458 virtual bool EndOfGame() const = 0; 00459 00460 /** Initialize PrevMove, CurrentDepth and other variables so that they can 00461 be accessed when move generation/evaluation are called directly, 00462 not as part of a search. 00463 */ 00464 void InitSearch(int startDepth = 0); 00465 00466 /** Is tracing currently active?*/ 00467 bool TraceIsOn() const; 00468 00469 /** Default creates a SgSearchTracer; override for specific traces */ 00470 virtual void CreateTracer(); 00471 00472 /** Set tracer object. Search object assumes ownership */ 00473 void SetTracer(SgSearchTracer* tracer); 00474 00475 SgSearchTracer* Tracer() const; 00476 00477 void SetAbortFrequency(int value); 00478 00479 /** Core Alpha-beta search. Usually not called directly - 00480 call DepthFirstSearch or IteratedSearch instead. */ 00481 int SearchEngine(int depth, int alpha, int beta, 00482 SgStack<SgMove, SgSearch::MAX_DEPTH>& stack, 00483 bool* isExactValue, bool lastNullMove = false); 00484 00485 private: 00486 /** Hash table */ 00487 SgSearchHashTable* m_hash; 00488 00489 /** Used to build a trace tree of the search for debugging */ 00490 SgSearchTracer* m_tracer; 00491 00492 /** The depth of the current position - number of moves from start */ 00493 int m_currentDepth; 00494 00495 int m_depthLimit; 00496 00497 /** Stack of all moves executed in search. Used by PrevMove() */ 00498 SgVector<SgMove> m_moveStack; 00499 00500 bool m_useScout; 00501 00502 bool m_useKillers; 00503 00504 /** Move best move from parent to front */ 00505 bool m_useOpponentBest; 00506 00507 /** Use null move heuristic for forward pruning */ 00508 bool m_useNullMove; 00509 00510 /** How much less deep to search during null move pruning */ 00511 int m_nullMoveDepth; 00512 00513 /** True if search is in the process of being aborted. */ 00514 bool m_aborted; 00515 00516 /** Flag that new best move was found in current iteration. */ 00517 bool m_foundNewBest; 00518 00519 /** Keeps track of whether the depth limit was reached. */ 00520 bool m_reachedDepthLimit; 00521 00522 SgSearchStatistics m_stat; 00523 00524 SgTimer m_timer; 00525 00526 int m_timerLevel; 00527 00528 /** The search result from the previous iteration */ 00529 int m_prevValue; 00530 00531 /** The PV from the previous search iteration */ 00532 SgVector<SgMove> m_prevSequence; 00533 00534 static const int MAX_KILLER_DEPTH = 10; 00535 00536 /** Killer heuristic. */ 00537 SgArray<SgKiller,MAX_KILLER_DEPTH + 1> m_killers; 00538 00539 SgSearchControl* m_control; 00540 00541 SgProbCut* m_probcut; 00542 00543 int m_abortFrequency; 00544 00545 /** Depth-first search (see implementation) */ 00546 int DFS(int startDepth, int depthLimit, int boundLo, int boundHi, 00547 SgVector<SgMove>* sequence, bool* isExactValue); 00548 00549 /** Try to find current position in m_hash */ 00550 bool LookupHash(SgSearchHashData& data) const; 00551 00552 bool NullMovePrune(int depth, int delta, int beta); 00553 00554 /** Store current position in hash table */ 00555 void StoreHash(int depth, int value, SgMove move, bool isUpperBound, 00556 bool isLowerBound, bool isExact); 00557 00558 /** Seed the hash table with the given sequence. */ 00559 void AddSequenceToHash(const SgVector<SgMove>& sequence, int depth); 00560 00561 /** Evaluate current position; possibly write debug output */ 00562 int CallEvaluate(int depth, bool* isExact); 00563 00564 /** Execute move; update m_moveStack, m_currentDepth and statistics */ 00565 bool CallExecute(SgMove move, int* delta, int depth); 00566 00567 /** Generate moves; possibly write debug output */ 00568 void CallGenerate(SgVector<SgMove>* moves, int depth); 00569 00570 /** Take back move; update m_moveStack and m_currentDepth */ 00571 void CallTakeBack(); 00572 00573 bool TryMove(SgMove move, const SgVector<SgMove>& specialMoves, 00574 const int depth, 00575 const int alpha, const int beta, 00576 int& loValue, int& hiValue, 00577 SgSearchStack& stack, 00578 bool& allExact, 00579 bool& isCutoff); 00580 00581 bool TrySpecialMove(SgMove move, SgVector<SgMove>& specialMoves, 00582 const int depth, 00583 const int alpha, const int beta, 00584 int& loValue, int& hiValue, 00585 SgSearchStack& stack, 00586 bool& allExact, 00587 bool& isCutoff); 00588 00589 /** Not implemented */ 00590 SgSearch(const SgSearch&); 00591 00592 /** Not implemented */ 00593 SgSearch& operator=(const SgSearch&); 00594 }; 00595 00596 inline bool SgSearch::Aborted() const 00597 { 00598 return m_aborted; 00599 } 00600 00601 inline int SgSearch::CurrentDepth() const 00602 { 00603 return m_currentDepth; 00604 } 00605 00606 inline int SgSearch::DepthFirstSearch(int depthLimit, 00607 SgVector<SgMove>* sequence, 00608 bool clearHash, SgNode* traceNode) 00609 { 00610 return DepthFirstSearch(depthLimit, -SG_INFINITY, SG_INFINITY, sequence, 00611 clearHash, traceNode); 00612 } 00613 00614 inline const SgSearchHashTable* SgSearch::HashTable() const 00615 { 00616 return m_hash; 00617 } 00618 00619 inline void SgSearch::SetHashTable(SgSearchHashTable* hashtable) 00620 { 00621 m_hash = hashtable; 00622 } 00623 00624 inline int SgSearch::IteratedSearchDepthLimit() const 00625 { 00626 return m_depthLimit; 00627 } 00628 00629 inline int SgSearch::IteratedSearch(int depthMin, int depthMax, 00630 SgVector<SgMove>* sequence, 00631 bool clearHash, 00632 SgNode* traceNode) 00633 { 00634 return IteratedSearch(depthMin, depthMax, -SG_INFINITY, SG_INFINITY, 00635 sequence, clearHash, traceNode); 00636 } 00637 00638 inline SgMove SgSearch::PrevMove() const 00639 { 00640 return m_moveStack.Back(); 00641 } 00642 00643 inline SgMove SgSearch::PrevMove2() const 00644 { 00645 return m_moveStack.TopNth(2); 00646 } 00647 00648 inline const SgSearchControl* SgSearch::SearchControl() const 00649 { 00650 return m_control; 00651 } 00652 00653 inline void SgSearch::SetAbortFrequency(int value) 00654 { 00655 m_abortFrequency = value; 00656 } 00657 00658 inline void SgSearch::SetAbortSearch(bool fAborted) 00659 { 00660 m_aborted = fAborted; 00661 } 00662 00663 inline void SgSearch::SetKillers(bool flag) 00664 { 00665 m_useKillers = flag; 00666 } 00667 00668 inline void SgSearch::SetNullMove(bool flag) 00669 { 00670 m_useNullMove = flag; 00671 } 00672 00673 inline void SgSearch::SetNullMoveDepth(int depth) 00674 { 00675 m_nullMoveDepth = depth; 00676 } 00677 00678 inline void SgSearch::SetOpponentBest(bool flag) 00679 { 00680 m_useOpponentBest = flag; 00681 } 00682 00683 inline void SgSearch::SetScout(bool flag) 00684 { 00685 m_useScout = flag; 00686 } 00687 00688 inline void SgSearch::SetTracer(SgSearchTracer* tracer) 00689 { 00690 // check that an existing tracer is not overwritten. 00691 // This can potentially be allowed in the future if needed. 00692 SG_ASSERT(! m_tracer || ! tracer); 00693 m_tracer = tracer; 00694 } 00695 00696 inline SgSearchTracer* SgSearch::Tracer() const 00697 { 00698 return m_tracer; 00699 } 00700 00701 //---------------------------------------------------------------------------- 00702 00703 #endif // SG_SEARCH_H