Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

SgSearch.h

Go to the documentation of this file.
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


17 Jun 2010 Doxygen 1.4.7