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 #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


Sun Mar 13 2011 Doxygen 1.7.1