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