Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

GoUctPlayer.h

Go to the documentation of this file.
00001 //----------------------------------------------------------------------------
00002 /** @file GoUctPlayer.h
00003     Class GoUctPlayer. */
00004 //----------------------------------------------------------------------------
00005 
00006 #ifndef GOUCT_PLAYER_H
00007 #define GOUCT_PLAYER_H
00008 
00009 #include <boost/scoped_ptr.hpp>
00010 #include <vector>
00011 #include "GoBoard.h"
00012 #include "GoBoardRestorer.h"
00013 #include "GoPlayer.h"
00014 #include "GoTimeControl.h"
00015 #include "GoUctDefaultRootFilter.h"
00016 #include "GoUctGlobalSearch.h"
00017 #include "GoUctObjectWithSearch.h"
00018 #include "GoUctPlayoutPolicy.h"
00019 #include "GoUctRootFilter.h"
00020 #include "SgArrayList.h"
00021 #include "SgDebug.h"
00022 #include "SgNbIterator.h"
00023 #include "SgNode.h"
00024 #include "SgPointArray.h"
00025 #include "SgRestorer.h"
00026 #include "SgMpiSynchronizer.h"
00027 #include "SgTime.h"
00028 #include "SgTimer.h"
00029 #include "SgUctTreeUtil.h"
00030 #include "SgWrite.h"
00031 
00032 template<typename T,int SIZE> class SgSList;
00033 
00034 //----------------------------------------------------------------------------
00035 
00036 /** What search mode to use in GoUctPlayer to select a move. */
00037 enum GoUctGlobalSearchMode
00038 {
00039     /** No search, use playout policy to select a move. */
00040     GOUCT_SEARCHMODE_PLAYOUTPOLICY,
00041 
00042     /** Use UCT search. */
00043     GOUCT_SEARCHMODE_UCT,
00044 
00045     /** Do a 1-ply MC search. */
00046     GOUCT_SEARCHMODE_ONEPLY
00047 };
00048 
00049 //----------------------------------------------------------------------------
00050 
00051 /** Player using UCT. */
00052 template <class SEARCH, class THREAD>
00053 class GoUctPlayer
00054     : public GoPlayer,
00055       public GoUctObjectWithSearch,
00056       public SgObjectWithDefaultTimeControl
00057 {
00058 public:
00059     /** Statistics collected by GoUctPlayer. */
00060     struct Statistics
00061     {
00062         std::size_t m_nuGenMove;
00063 
00064         SgStatisticsExt<float,std::size_t> m_reuse;
00065 
00066         SgStatisticsExt<double,std::size_t> m_gamesPerSecond;
00067 
00068         Statistics();
00069 
00070         void Clear();
00071 
00072         /** Write in human readable format. */
00073         void Write(std::ostream& out) const;
00074     };
00075 
00076     GoUctPlayoutPolicyParam m_playoutPolicyParam;
00077 
00078     /** Constructor.
00079         @param bd The board. */
00080     GoUctPlayer(const GoBoard& bd);
00081 
00082     ~GoUctPlayer();
00083 
00084 
00085     /** @name Virtual functions of GoBoardSynchronizer */
00086     // @{
00087 
00088     void OnBoardChange();
00089 
00090     // @} // @name
00091 
00092 
00093     /** @name Virtual functions of GoPlayer */
00094     // @{
00095 
00096     SgPoint GenMove(const SgTimeRecord& time, SgBlackWhite toPlay);
00097 
00098     std::string Name() const;
00099 
00100     void Ponder();
00101 
00102     // @} // @name
00103 
00104 
00105     /** @name Virtual functions of SgObjectWithDefaultTimeControl */
00106     // @{
00107 
00108     SgDefaultTimeControl& TimeControl();
00109 
00110     const SgDefaultTimeControl& TimeControl() const;
00111 
00112     // @} // @name
00113 
00114 
00115     /** @name Virtual functions of GoUctObjectWithSearch */
00116     // @{
00117 
00118     GoUctSearch& Search();
00119 
00120     const GoUctSearch& Search() const;
00121 
00122     // @} // @name
00123 
00124 
00125     /** @name Parameters */
00126     // @{
00127 
00128     /** Automatically adapt the search parameters optimized for the current
00129         board size.
00130         If on, GoUctGlobalSearch::SetDefaultParameters will automatically
00131         be called, if the board size changes. */
00132     bool AutoParam() const;
00133 
00134     /** See AutoParam() */
00135     void SetAutoParam(bool enable);
00136 
00137     /** Pass early.
00138         Aborts search early, if value is above 1 - ResignThreshold(), and
00139         performs a second search to see, if it is still a win and all points
00140         are safe (using territory statistics) after playing a pass. If this
00141         is true, it plays a pass. */
00142     bool EarlyPass() const;
00143 
00144     /** See EarlyPass() */
00145     void SetEarlyPass(bool enable);
00146 
00147     /** Enforce opening moves in the corner on large boards.
00148         See GoUctUtil::GenForcedOpeningMove. Default is true. */
00149     bool ForcedOpeningMoves() const;
00150 
00151     /** See ForcedOpeningMoves() */
00152     void SetForcedOpeningMoves(bool enable);
00153 
00154     /** Ignore time settings of the game.
00155         Ignore time record given to GenMove() and only obeys maximum
00156         number of games and maximum time. Default is true. */
00157     bool IgnoreClock() const;
00158 
00159     /** See IgnoreClock() */
00160     void SetIgnoreClock(bool enable);
00161 
00162     /** Limit on number of simulated games per move. */
00163     SgUctValue MaxGames() const;
00164 
00165     /** See MaxGames() */
00166     void SetMaxGames(SgUctValue maxGames);
00167 
00168     /** Think during the opponents time.
00169         For enabling pondering, ReuseSubtree() also has to be true.
00170         Pondering search will be terminated after MaxGames() or
00171         MaxPonderTime(). */
00172     bool EnablePonder() const;
00173 
00174     /** See EnablePonder() */
00175     void SetEnablePonder(bool enable);
00176 
00177     /** Maximum ponder time in seconds.
00178         @see EnablePonder() */
00179     double MaxPonderTime() const;
00180 
00181     /** See MaxPonderTime() */
00182     void SetMaxPonderTime(double seconds);
00183 
00184     /** Minimum number of simulations to check for resign.
00185         This minimum number of simulations is also required to apply the
00186         early pass check (see EarlyPass()).
00187         Default is 3000. */
00188     SgUctValue ResignMinGames() const;
00189 
00190     /** See ResignMinGames()     */
00191     void SetResignMinGames(SgUctValue n);
00192 
00193     /** Use the root filter. */
00194     bool UseRootFilter() const;
00195 
00196     /** See UseRootFilter() */
00197     void SetUseRootFilter(bool enable);
00198 
00199     /** Reuse subtree from last search.
00200         Reuses the subtree from the last search, if the current position is
00201         a number of regular game moves later than the position that the
00202         previous search corresponds to. Default is true. */
00203     bool ReuseSubtree() const;
00204 
00205     /** See ReuseSubtree() */
00206     void SetReuseSubtree(bool enable);
00207 
00208     /** Threshold for position value to resign.
00209         Default is 0.01. */
00210     SgUctValue ResignThreshold() const;
00211 
00212     /** See ResignThreshold() */
00213     void SetResignThreshold(SgUctValue threshold);
00214 
00215     /** See GoUctGlobalSearchMode */
00216     GoUctGlobalSearchMode SearchMode() const;
00217 
00218     /** See GoUctGlobalSearchMode */
00219     void SetSearchMode(GoUctGlobalSearchMode mode);
00220 
00221     /** Print output of GoUctSearch. */
00222     bool WriteDebugOutput() const;
00223 
00224     /** See WriteDebugOutput() */
00225     void SetWriteDebugOutput(bool flag);
00226 
00227     // @} // @name
00228 
00229 
00230     /** @name Virtual functions of SgObjectWithDefaultTimeControl */
00231     // @{
00232 
00233     const Statistics& GetStatistics() const;
00234 
00235     void ClearStatistics();
00236 
00237     // @} // @name
00238 
00239     SEARCH& GlobalSearch();
00240 
00241     const SEARCH& GlobalSearch() const;
00242 
00243     /** Return the current root filter. */
00244     GoUctRootFilter& RootFilter();
00245 
00246     /** Set a new root filter.
00247         Deletes the old root filter and takes ownership of the new filter. */
00248     void SetRootFilter(GoUctRootFilter* filter);
00249 
00250     void SetMpiSynchronizer(const SgMpiSynchronizerHandle &synchronizerHandle);
00251 
00252     SgMpiSynchronizerHandle GetMpiSynchronizer();
00253 
00254  private:
00255     /** See GoUctGlobalSearchMode */
00256     GoUctGlobalSearchMode m_searchMode;
00257 
00258     /** See AutoParam() */
00259     bool m_autoParam;
00260 
00261     /** See ForcedOpeningMoves() */
00262     bool m_forcedOpeningMoves;
00263 
00264     /** See IgnoreClock() */
00265     bool m_ignoreClock;
00266 
00267     /** See EnablePonder() */
00268     bool m_enablePonder;
00269 
00270     /** See UseRootFilter() */
00271     bool m_useRootFilter;
00272 
00273     /** See ReuseSubtree() */
00274     bool m_reuseSubtree;
00275 
00276     /** See EarlyPass() */
00277     bool m_earlyPass;
00278 
00279     /** See ResignThreshold() */
00280     SgUctValue m_resignThreshold;
00281 
00282     /** Used in OnBoardChange() */
00283     int m_lastBoardSize;
00284 
00285     SgUctValue m_maxGames;
00286 
00287     SgUctValue m_resignMinGames;
00288 
00289     double m_maxPonderTime;
00290 
00291     SEARCH m_search;
00292 
00293     GoTimeControl m_timeControl;
00294 
00295     Statistics m_statistics;
00296 
00297     boost::scoped_ptr<GoUctRootFilter> m_rootFilter;
00298 
00299     /** Playout policy used if search mode is
00300         GOUCT_SEARCHMODE_PLAYOUTPOLICY. */
00301     boost::scoped_ptr<GoUctPlayoutPolicy<GoBoard> > m_playoutPolicy;
00302 
00303     SgMpiSynchronizerHandle m_mpiSynchronizer;
00304 
00305     bool m_writeDebugOutput;
00306 
00307     SgMove GenMovePlayoutPolicy(SgBlackWhite toPlay);
00308 
00309     bool DoEarlyPassSearch(SgUctValue maxGames, double maxTime, SgPoint& move);
00310 
00311     SgPoint DoSearch(SgBlackWhite toPlay, double maxTime,
00312                      bool isDuringPondering);
00313 
00314     void FindInitTree(SgUctTree& initTree, SgBlackWhite toPlay,
00315                       double maxTime);
00316 
00317     void SetDefaultParameters(int boardSize);
00318 
00319     bool VerifyNeutralMove(SgUctValue maxGames, double maxTime, SgPoint move);
00320 };
00321 
00322 template <class SEARCH, class THREAD>
00323 inline bool GoUctPlayer<SEARCH, THREAD>::AutoParam() const
00324 {
00325     return m_autoParam;
00326 }
00327 
00328 template <class SEARCH, class THREAD>
00329 inline SEARCH&
00330 GoUctPlayer<SEARCH, THREAD>::GlobalSearch()
00331 {
00332     return m_search;
00333 }
00334 
00335 template <class SEARCH, class THREAD>
00336 inline const SEARCH& GoUctPlayer<SEARCH, THREAD>::GlobalSearch() const
00337 {
00338     return m_search;
00339 }
00340 
00341 template <class SEARCH, class THREAD>
00342 inline bool GoUctPlayer<SEARCH, THREAD>::EarlyPass() const
00343 {
00344     return m_earlyPass;
00345 }
00346 
00347 template <class SEARCH, class THREAD>
00348 inline bool GoUctPlayer<SEARCH, THREAD>::EnablePonder() const
00349 {
00350     return m_enablePonder;
00351 }
00352 
00353 template <class SEARCH, class THREAD>
00354 inline bool GoUctPlayer<SEARCH, THREAD>::ForcedOpeningMoves() const
00355 {
00356     return m_forcedOpeningMoves;
00357 }
00358 
00359 template <class SEARCH, class THREAD>
00360 inline bool GoUctPlayer<SEARCH, THREAD>::IgnoreClock() const
00361 {
00362     return m_ignoreClock;
00363 }
00364 
00365 template <class SEARCH, class THREAD>
00366 inline SgUctValue GoUctPlayer<SEARCH, THREAD>::MaxGames() const
00367 {
00368     return m_maxGames;
00369 }
00370 
00371 template <class SEARCH, class THREAD>
00372 inline double GoUctPlayer<SEARCH, THREAD>::MaxPonderTime() const
00373 {
00374     return m_maxPonderTime;
00375 }
00376 
00377 template <class SEARCH, class THREAD>
00378 inline bool GoUctPlayer<SEARCH, THREAD>::UseRootFilter() const
00379 {
00380     return m_useRootFilter;
00381 }
00382 
00383 template <class SEARCH, class THREAD>
00384 inline SgUctValue GoUctPlayer<SEARCH, THREAD>::ResignMinGames() const
00385 {
00386     return m_resignMinGames;
00387 }
00388 
00389 template <class SEARCH, class THREAD>
00390 inline SgUctValue GoUctPlayer<SEARCH, THREAD>::ResignThreshold() const
00391 {
00392     return m_resignThreshold;
00393 }
00394 
00395 template <class SEARCH, class THREAD>
00396 inline bool GoUctPlayer<SEARCH, THREAD>::ReuseSubtree() const
00397 {
00398     return m_reuseSubtree;
00399 }
00400 
00401 template <class SEARCH, class THREAD>
00402 inline GoUctRootFilter& GoUctPlayer<SEARCH, THREAD>::RootFilter()
00403 {
00404     return *m_rootFilter;
00405 }
00406 
00407 template <class SEARCH, class THREAD>
00408 inline GoUctGlobalSearchMode GoUctPlayer<SEARCH, THREAD>::SearchMode() const
00409 {
00410     return m_searchMode;
00411 }
00412 
00413 template <class SEARCH, class THREAD>
00414 inline void GoUctPlayer<SEARCH, THREAD>::SetAutoParam(bool enable)
00415 {
00416     m_autoParam = enable;
00417 }
00418 
00419 template <class SEARCH, class THREAD>
00420 inline void GoUctPlayer<SEARCH, THREAD>::SetEarlyPass(bool enable)
00421 {
00422     m_earlyPass = enable;
00423 }
00424 
00425 template <class SEARCH, class THREAD>
00426 inline void GoUctPlayer<SEARCH, THREAD>::SetEnablePonder(bool enable)
00427 {
00428     m_enablePonder = enable;
00429 }
00430 
00431 template <class SEARCH, class THREAD>
00432 inline void GoUctPlayer<SEARCH, THREAD>::SetForcedOpeningMoves(bool enable)
00433 {
00434     m_forcedOpeningMoves = enable;
00435 }
00436 
00437 template <class SEARCH, class THREAD>
00438 inline void GoUctPlayer<SEARCH, THREAD>::SetIgnoreClock(bool enable)
00439 {
00440     m_ignoreClock = enable;
00441 }
00442 
00443 template <class SEARCH, class THREAD>
00444 inline void GoUctPlayer<SEARCH, THREAD>::SetMaxGames(SgUctValue maxGames)
00445 {
00446     m_maxGames = maxGames;
00447 }
00448 
00449 template <class SEARCH, class THREAD>
00450 inline void GoUctPlayer<SEARCH, THREAD>::SetMaxPonderTime(double seconds)
00451 {
00452     m_maxPonderTime = seconds;
00453 }
00454 
00455 template <class SEARCH, class THREAD>
00456 inline void GoUctPlayer<SEARCH, THREAD>::SetUseRootFilter(bool enable)
00457 {
00458     m_useRootFilter = enable;
00459 }
00460 
00461 template <class SEARCH, class THREAD>
00462 inline void GoUctPlayer<SEARCH, THREAD>::SetResignMinGames(SgUctValue n)
00463 {
00464     m_resignMinGames = n;
00465 }
00466 
00467 template <class SEARCH, class THREAD>
00468 inline void GoUctPlayer<SEARCH, THREAD>::SetResignThreshold(SgUctValue threshold)
00469 {
00470     m_resignThreshold = threshold;
00471 }
00472 
00473 template <class SEARCH, class THREAD>
00474 inline void GoUctPlayer<SEARCH, THREAD>::SetRootFilter(GoUctRootFilter*
00475                                                        filter)
00476 {
00477     m_rootFilter.reset(filter);
00478 }
00479 
00480 template <class SEARCH, class THREAD>
00481 inline void 
00482 GoUctPlayer<SEARCH, THREAD>::SetSearchMode(GoUctGlobalSearchMode mode)
00483 {
00484     m_searchMode = mode;
00485 }
00486 
00487 template <class SEARCH, class THREAD>
00488 inline void GoUctPlayer<SEARCH, THREAD>::SetMpiSynchronizer(const SgMpiSynchronizerHandle &handle)
00489 {
00490     m_mpiSynchronizer = SgMpiSynchronizerHandle(handle);
00491     m_search.SetMpiSynchronizer(handle);
00492 }
00493 
00494 template <class SEARCH, class THREAD>
00495 inline SgMpiSynchronizerHandle 
00496 GoUctPlayer<SEARCH, THREAD>::GetMpiSynchronizer()
00497 {
00498     return SgMpiSynchronizerHandle(m_mpiSynchronizer);
00499 }
00500 
00501 template <class SEARCH, class THREAD>
00502 GoUctPlayer<SEARCH, THREAD>::Statistics::Statistics()
00503 {
00504     Clear();
00505 }
00506 
00507 template <class SEARCH, class THREAD>
00508 void GoUctPlayer<SEARCH, THREAD>::Statistics::Clear()
00509 {
00510     m_nuGenMove = 0;
00511     m_gamesPerSecond.Clear();
00512     m_reuse.Clear();
00513 }
00514 
00515 template <class SEARCH, class THREAD>
00516 bool GoUctPlayer<SEARCH, THREAD>::WriteDebugOutput() const
00517 {
00518     return m_writeDebugOutput;
00519 }
00520 
00521 template <class SEARCH, class THREAD>
00522 void GoUctPlayer<SEARCH, THREAD>::SetWriteDebugOutput(bool flag)
00523 {
00524     m_writeDebugOutput = flag;
00525 }
00526 
00527 template <class SEARCH, class THREAD>
00528 void GoUctPlayer<SEARCH, THREAD>::Statistics::Write(std::ostream& out) const
00529 {
00530     out << SgWriteLabel("NuGenMove") << m_nuGenMove << '\n'
00531         << SgWriteLabel("GamesPerSec");
00532     m_gamesPerSecond.Write(out);
00533     out << '\n'
00534         << SgWriteLabel("Reuse");
00535     m_reuse.Write(out);
00536     out << '\n';
00537 }
00538 
00539 template <class SEARCH, class THREAD>
00540 GoUctPlayer<SEARCH, THREAD>::GoUctPlayer(const GoBoard& bd)
00541     : GoPlayer(bd),
00542       m_searchMode(GOUCT_SEARCHMODE_UCT),
00543       m_autoParam(true),
00544       m_forcedOpeningMoves(true),
00545       m_ignoreClock(false),
00546       m_enablePonder(false),
00547       m_useRootFilter(true),
00548       m_reuseSubtree(true),
00549       m_earlyPass(true),
00550       m_lastBoardSize(-1),
00551       m_maxGames(std::numeric_limits<SgUctValue>::max()),
00552       m_resignMinGames(5000),
00553       m_maxPonderTime(300),
00554       m_search(Board(),
00555                new GoUctPlayoutPolicyFactory<GoUctBoard>(
00556                                                  m_playoutPolicyParam),
00557                m_playoutPolicyParam),
00558       
00559       m_timeControl(Board()),
00560       m_rootFilter(new GoUctDefaultRootFilter(Board())),
00561       m_mpiSynchronizer(SgMpiNullSynchronizer::Create()),
00562       m_writeDebugOutput(true)
00563 {
00564     SetDefaultParameters(Board().Size());
00565     m_search.SetMpiSynchronizer(m_mpiSynchronizer);
00566 }
00567 
00568 template <class SEARCH, class THREAD>
00569 GoUctPlayer<SEARCH, THREAD>::~GoUctPlayer()
00570 {
00571 }
00572 
00573 template <class SEARCH, class THREAD>
00574 void GoUctPlayer<SEARCH, THREAD>::ClearStatistics()
00575 {
00576     m_statistics.Clear();
00577 }
00578 
00579 /** Perform a search after playing a pass and see if it is still a win and
00580     all points are safe as determined by territory statistics.
00581     @param maxGames Maximum simulations for the search
00582     @param maxTime Maximum time for the search
00583     @param[out] move The move to play (pass or a neutral point to fill)
00584     @return @c true, if it is still a win and everything is safe after a pass */
00585 template <class SEARCH, class THREAD>
00586 bool GoUctPlayer<SEARCH, THREAD>::DoEarlyPassSearch(SgUctValue maxGames, 
00587                                                     double maxTime,
00588                                                     SgPoint& move)
00589 {
00590     SgDebug() << "GoUctPlayer: doing a search if early pass is possible\n";
00591     GoBoard& bd = Board();
00592     bd.Play(SG_PASS);
00593     bool winAfterPass = false;
00594     bool passWins = GoBoardUtil::TrompTaylorPassWins(bd, bd.ToPlay());
00595     m_mpiSynchronizer->SynchronizePassWins(passWins);
00596     if (passWins)
00597     {
00598         // Using GoBoardUtil::TrompTaylorPassWins here is not strictly
00599         // necessary, but safer, because it can take the search in the
00600         // else-statement a while to explore the pass move
00601         winAfterPass = false;
00602     }
00603     else
00604     {
00605         SgRestorer<bool> restorer(&m_search.m_param.m_territoryStatistics);
00606         m_search.m_param.m_territoryStatistics = true;
00607         std::vector<SgPoint> sequence;
00608         SgUctValue value = m_search.Search(maxGames, maxTime, sequence);
00609         value = m_search.InverseEstimate(value);
00610         winAfterPass = (value > 1 - m_resignThreshold);
00611     }
00612     bd.Undo();
00613 
00614     bool earlyPassPossible = true;
00615     if (earlyPassPossible && ! winAfterPass)
00616     {
00617         SgDebug() << "GoUctPlayer: no early pass possible (no win)\n";
00618         earlyPassPossible = false;
00619     }
00620     move = SG_PASS;
00621     THREAD& threadState = dynamic_cast<THREAD&>(m_search.ThreadState(0));
00622     SgPointArray<SgUctStatistics> territory =
00623     threadState.m_territoryStatistics;
00624     if (earlyPassPossible)
00625     {
00626         for (GoBoard::Iterator it(bd); it; ++it)
00627             if (territory[*it].Count() == 0)
00628             {
00629                 // No statistics, maybe all simulations aborted due to
00630                 // max length or mercy rule.
00631                 SgDebug() 
00632                     << "GoUctPlayer: no early pass possible (no stat)\n";
00633                 earlyPassPossible = false;
00634                 break;
00635             }
00636     }
00637 
00638     if (earlyPassPossible)
00639     {
00640         const float threshold = 0.2f; // Safety threshold
00641         for (GoBoard::Iterator it(bd); it; ++it)
00642         {
00643             SgUctValue mean = territory[*it].Mean();
00644             if (mean > threshold && mean < 1 - threshold)
00645             {
00646                 // Check if neutral point
00647                 bool isSafeToPlayAdj = false;
00648                 bool isSafeOppAdj = false;
00649                 for (SgNb4Iterator it2(*it); it2; ++it2)
00650                     if (! bd.IsBorder(*it2))
00651                     {
00652                         SgUctValue mean = territory[*it2].Mean();
00653                         if (mean < threshold)
00654                             isSafeToPlayAdj = true;
00655                         if (mean > 1 - threshold)
00656                             isSafeOppAdj = true;
00657                     }
00658                 if (isSafeToPlayAdj && isSafeOppAdj)
00659                 {
00660                     if (bd.IsLegal(*it) && ! GoBoardUtil::SelfAtari(bd, *it))
00661                         move = *it;
00662                     else
00663                     {
00664                         SgDebug() <<
00665                             "GoUctPlayer: no early pass possible"
00666                             " (neutral illegal or self-atari)\n";
00667                         earlyPassPossible = false;
00668                         break;
00669                     }
00670                 }
00671                 else
00672                 {
00673                     SgDebug()
00674                     << "GoUctPlayer: no early pass possible (unsafe point)\n";
00675                     earlyPassPossible = false;
00676                     break;
00677                 }
00678             }
00679         }
00680     }
00681 
00682     m_mpiSynchronizer->SynchronizeEarlyPassPossible(earlyPassPossible);
00683     if (! earlyPassPossible)
00684         return false;
00685     m_mpiSynchronizer->SynchronizeMove(move);
00686     if (move == SG_PASS)
00687         SgDebug() << "GoUctPlayer: early pass is possible\n";
00688     else if (VerifyNeutralMove(maxGames, maxTime, move))
00689         SgDebug() << "GoUctPlayer: generate play on neutral point\n";
00690     else
00691     {
00692         SgDebug() << "GoUctPlayer: neutral move failed to verify\n";
00693         return false;
00694     }
00695     return true;
00696 }
00697 
00698 /** Run the search for a given color.
00699     @param toPlay
00700     @param maxTime
00701     @param isDuringPondering Hint that search is done during pondering (this
00702     handles the decision to discard an aborted FindInitTree differently)
00703     @return The best move or SG_NULLMOVE if terminal position (can also
00704     happen, if @c isDuringPondering, no search was performed, because
00705     DoSearch() was aborted during FindInitTree()). */
00706 template <class SEARCH, class THREAD>
00707 SgPoint GoUctPlayer<SEARCH, THREAD>::DoSearch(SgBlackWhite toPlay, 
00708                                               double maxTime,
00709                                               bool isDuringPondering)
00710 {
00711     SgUctTree* initTree = 0;
00712     SgTimer timer;
00713     double timeInitTree = 0;
00714     if (m_reuseSubtree)
00715     {
00716         initTree = &m_search.GetTempTree();
00717         timeInitTree = -timer.GetTime();
00718         FindInitTree(*initTree, toPlay, maxTime);
00719         timeInitTree += timer.GetTime();
00720         if (isDuringPondering)
00721         {
00722             bool aborted = SgUserAbort();
00723             m_mpiSynchronizer->SynchronizeUserAbort(aborted);
00724             if (aborted)
00725             // If abort occurs during pondering, better don't start a search
00726             // with a truncated init tree. The search would be aborted after
00727             // one game anyway, because it also checks SgUserAbort(). There is
00728             // a higher chance to reuse a larger part of the current tree in
00729             // the next regular move search.
00730             return SG_NULLMOVE;
00731         }
00732     }
00733     std::vector<SgMove> rootFilter;
00734     double timeRootFilter = 0;
00735     if (m_useRootFilter)
00736     {
00737         timeRootFilter = -timer.GetTime();
00738         rootFilter = m_rootFilter->Get();
00739         timeRootFilter += timer.GetTime();
00740     }
00741     maxTime -= timer.GetTime();
00742     m_search.SetToPlay(toPlay);
00743     std::vector<SgPoint> sequence;
00744     SgUctEarlyAbortParam earlyAbort;
00745     earlyAbort.m_threshold = 1.f - m_resignThreshold;
00746     earlyAbort.m_minGames = m_resignMinGames;
00747     earlyAbort.m_reductionFactor = 3;
00748     SgUctValue value = m_search.Search(m_maxGames, maxTime, sequence, rootFilter,
00749                                   initTree, &earlyAbort);
00750 
00751     bool wasEarlyAbort = m_search.WasEarlyAbort();
00752     SgUctValue rootMoveCount = m_search.Tree().Root().MoveCount();
00753     m_mpiSynchronizer->SynchronizeSearchStatus(value, wasEarlyAbort, rootMoveCount);
00754 
00755     if (m_writeDebugOutput)
00756     {
00757         // Write debug output to a string stream first to avoid intermingling
00758         // of debug output with response in GoGui GTP shell
00759         std::ostringstream out;
00760         m_search.WriteStatistics(out);
00761         out << SgWriteLabel("Value") << std::fixed << std::setprecision(2) 
00762             << value << '\n' << SgWriteLabel("Sequence") 
00763             << SgWritePointList(sequence, "", false);
00764         if (m_reuseSubtree)
00765             out << SgWriteLabel("TimeInitTree") << std::fixed 
00766                 << std::setprecision(2) << timeInitTree << '\n';
00767         if (m_useRootFilter)
00768             out << SgWriteLabel("TimeRootFilter") << std::fixed 
00769                 << std::setprecision(2) << timeRootFilter << '\n';
00770         SgDebug() << out.str();
00771     }
00772 
00773     if (  value < m_resignThreshold
00774        && rootMoveCount > m_resignMinGames
00775        )
00776         return SG_RESIGN;
00777 
00778     SgPoint move;
00779     if (sequence.empty())
00780         move = SG_PASS;
00781     else
00782     {
00783         move = *(sequence.begin());
00784         move = GoUctSearchUtil::TrompTaylorPassCheck(move, m_search);
00785     }
00786 
00787     // If SgUctSearch aborted early, use the remaining time/nodes for doing a
00788     // search, if an early pass is possible
00789     if (m_earlyPass && wasEarlyAbort)
00790     {
00791         maxTime -= timer.GetTime();
00792         SgPoint earlyPassMove;
00793         if (DoEarlyPassSearch(m_maxGames / earlyAbort.m_reductionFactor,
00794                               maxTime, earlyPassMove))
00795             move = earlyPassMove;
00796     }
00797 
00798     m_mpiSynchronizer->SynchronizeMove(move);
00799     return move;
00800 }
00801 
00802 /** Find initial tree for search, if subtree reusing is enabled.
00803     Goes back in the tree until the node is found, the search tree is valid
00804     for and checks if the path of nodes corresponds to an alternating
00805     sequence of moves starting with the color to play of the search tree.
00806     @see SetReuseSubtree */
00807 template <class SEARCH, class THREAD>
00808 void GoUctPlayer<SEARCH, THREAD>::FindInitTree(SgUctTree& initTree, 
00809                                                SgBlackWhite toPlay,
00810                                                double maxTime)
00811 {
00812     Board().SetToPlay(toPlay);
00813     GoBoardHistory currentPosition;
00814     currentPosition.SetFromBoard(Board());
00815     std::vector<SgPoint> sequence;
00816     if (! currentPosition.IsAlternatePlayFollowUpOf(m_search.BoardHistory(),
00817                                                     sequence))
00818     {
00819         SgDebug() << "GoUctPlayer: No tree to reuse found\n";
00820         return;
00821     }
00822     SgUctTreeUtil::ExtractSubtree(m_search.Tree(), initTree, sequence, true,
00823                                   maxTime, m_search.PruneMinCount());
00824     size_t initTreeNodes = initTree.NuNodes();
00825     size_t oldTreeNodes = m_search.Tree().NuNodes();
00826     if (oldTreeNodes > 1 && initTreeNodes >= 1)
00827     {
00828         float reuse = float(initTreeNodes) / float(oldTreeNodes);
00829         int reusePercent = static_cast<int>(100 * reuse);
00830         SgDebug() << "GoUctPlayer: Reusing " << initTreeNodes
00831                   << " nodes (" << reusePercent << "%)\n";
00832 
00833         //SgDebug() << SgWritePointList(sequence, "Sequence", false);
00834         m_statistics.m_reuse.Add(reuse);
00835     }
00836     else
00837     {
00838         SgDebug() << "GoUctPlayer: Subtree to reuse has 0 nodes\n";
00839         m_statistics.m_reuse.Add(0.f);
00840     }
00841 
00842     // Check consistency
00843     if (initTree.Root().HasChildren())
00844     {
00845         for (SgUctChildIterator it(initTree, initTree.Root()); it; ++it)
00846             if (! Board().IsLegal((*it).Move()))
00847             {
00848                 SgWarning() <<
00849                     "GoUctPlayer: illegal move in root child of init tree\n";
00850                 initTree.Clear();
00851                 // Should not happen, if no bugs
00852                 SG_ASSERT(false);
00853             }
00854     }
00855 }
00856 
00857 template <class SEARCH, class THREAD>
00858 SgPoint GoUctPlayer<SEARCH, THREAD>::GenMove(const SgTimeRecord& time,
00859                                              SgBlackWhite toPlay)
00860 {
00861     ++m_statistics.m_nuGenMove;
00862     if (m_searchMode == GOUCT_SEARCHMODE_PLAYOUTPOLICY)
00863         return GenMovePlayoutPolicy(toPlay);
00864     const GoBoard& bd = Board();
00865     SgMove move = SG_NULLMOVE;
00866     if (m_forcedOpeningMoves)
00867     {
00868         move = GoUctUtil::GenForcedOpeningMove(bd);
00869         if (move != SG_NULLMOVE)
00870             SgDebug() << "GoUctPlayer: Forced opening move\n";
00871     }
00872     if (move == SG_NULLMOVE && GoBoardUtil::TrompTaylorPassWins(bd, toPlay))
00873     {
00874         move = SG_PASS;
00875         SgDebug() << "GoUctPlayer: Pass wins (Tromp-Taylor rules)\n";
00876     }
00877     if (move == SG_NULLMOVE)
00878     {
00879         double maxTime;
00880         if (m_ignoreClock)
00881             maxTime = std::numeric_limits<double>::max();
00882         else
00883             maxTime = m_timeControl.TimeForCurrentMove(time,
00884                                                        !m_writeDebugOutput);
00885         SgUctValue value;
00886         if (m_searchMode == GOUCT_SEARCHMODE_ONEPLY)
00887         {
00888             m_search.SetToPlay(toPlay);
00889             move = m_search.SearchOnePly(m_maxGames, maxTime, value);
00890             if (move == SG_NULLMOVE)
00891                 move = SG_PASS;
00892             else
00893             {
00894                 SgUctValue value = (SgUctValue)m_search.Tree().Root().Mean();
00895                 if (value < m_resignThreshold)
00896                     move = SG_RESIGN;
00897             }
00898         }
00899         else
00900         {
00901             SG_ASSERT(m_searchMode == GOUCT_SEARCHMODE_UCT);
00902             move = DoSearch(toPlay, maxTime, false);
00903             m_statistics.m_gamesPerSecond.Add(
00904                                       m_search.Statistics().m_gamesPerSecond);
00905         }
00906     }
00907     return move;
00908 }
00909 
00910 template <class SEARCH, class THREAD>
00911 SgMove GoUctPlayer<SEARCH, THREAD>::GenMovePlayoutPolicy(SgBlackWhite toPlay)
00912 {
00913     GoBoard& bd = Board();
00914     GoBoardRestorer restorer(bd);
00915     bd.SetToPlay(toPlay);
00916     if (m_playoutPolicy.get() == 0)
00917         m_playoutPolicy.reset(
00918             new GoUctPlayoutPolicy<GoBoard>(bd, m_playoutPolicyParam));
00919     m_playoutPolicy->StartPlayout();
00920     SgPoint move = m_playoutPolicy->GenerateMove();
00921     m_playoutPolicy->EndPlayout();
00922     if (move == SG_NULLMOVE)
00923     {
00924         SgDebug() <<
00925             "GoUctPlayer: GoUctPlayoutPolicy generated SG_NULLMOVE\n";
00926         return SG_PASS;
00927     }
00928     return move;
00929 }
00930 
00931 template <class SEARCH, class THREAD>
00932 const typename GoUctPlayer<SEARCH, THREAD>::Statistics& 
00933 GoUctPlayer<SEARCH, THREAD>::GetStatistics() const
00934 {
00935     return m_statistics;
00936 }
00937 
00938 template <class SEARCH, class THREAD>
00939 std::string GoUctPlayer<SEARCH, THREAD>::Name() const
00940 {
00941     return "GoUctPlayer";
00942 }
00943 
00944 template <class SEARCH, class THREAD>
00945 void GoUctPlayer<SEARCH, THREAD>::OnBoardChange()
00946 {
00947     int size = Board().Size();
00948     if (m_autoParam && size != m_lastBoardSize)
00949     {
00950         SgDebug() << "GoUctPlayer: Setting default parameters for size "
00951                   << size << '\n';
00952         SetDefaultParameters(size);
00953         m_search.SetDefaultParameters(size);
00954         m_lastBoardSize = size;
00955     }
00956 }
00957 
00958 template <class SEARCH, class THREAD>
00959 void GoUctPlayer<SEARCH, THREAD>::Ponder()
00960 {
00961     const GoBoard& bd = Board();
00962     if (! m_enablePonder || m_searchMode != GOUCT_SEARCHMODE_UCT)
00963         return;
00964     if (! m_reuseSubtree)
00965     {
00966         // Don't ponder, wouldn't use the result in the next GenMove
00967         // anyway if reuseSubtree is not enabled
00968         SgWarning() << "Pondering needs reuse_subtree enabled.\n";
00969         return;
00970     }
00971     SgDebug() << "GoUctPlayer::Ponder: start\n";
00972     DoSearch(bd.ToPlay(), m_maxPonderTime, true);
00973     SgDebug() << "GoUctPlayer::Ponder: end\n";
00974 }
00975 
00976 template <class SEARCH, class THREAD>
00977 GoUctSearch& GoUctPlayer<SEARCH, THREAD>::Search()
00978 {
00979     return m_search;
00980 }
00981 
00982 template <class SEARCH, class THREAD>
00983 const GoUctSearch& GoUctPlayer<SEARCH, THREAD>::Search() const
00984 {
00985     return m_search;
00986 }
00987 
00988 template <class SEARCH, class THREAD>
00989 void GoUctPlayer<SEARCH, THREAD>::SetDefaultParameters(int boardSize)
00990 {
00991     m_timeControl.SetFastOpenMoves(0);
00992     m_timeControl.SetMinTime(0);
00993     m_timeControl.SetRemainingConstant(0.5);
00994     if (boardSize < 15)
00995     {
00996         m_resignThreshold = (SgUctValue)0.05;
00997     }
00998     else
00999     {
01000         // Need higher resign threshold, because GoUctGlobalSearch uses
01001         // length modification on large board
01002         m_resignThreshold = (SgUctValue)0.08;
01003     }
01004 }
01005 
01006 template <class SEARCH, class THREAD>
01007 void GoUctPlayer<SEARCH, THREAD>::SetReuseSubtree(bool enable)
01008 {
01009     m_reuseSubtree = enable;
01010 }
01011 
01012 template <class SEARCH, class THREAD>
01013 SgDefaultTimeControl& GoUctPlayer<SEARCH, THREAD>::TimeControl()
01014 {
01015     return m_timeControl;
01016 }
01017 
01018 template <class SEARCH, class THREAD>
01019 const SgDefaultTimeControl& GoUctPlayer<SEARCH, THREAD>::TimeControl() const
01020 {
01021     return m_timeControl;
01022 }
01023 
01024 /** Verify that the move selected by DoEarlyPassSearch is viable.
01025     Prevent blunders from so-called neutral moves that are not. */
01026 template <class SEARCH, class THREAD>
01027 bool GoUctPlayer<SEARCH, THREAD>::VerifyNeutralMove(SgUctValue maxGames, 
01028                                                     double maxTime,
01029                                                     SgPoint move)
01030 {
01031     GoBoard& bd = Board();
01032     bd.Play(move);
01033     std::vector<SgPoint> sequence;
01034     SgUctValue value = m_search.Search(maxGames, maxTime, sequence);
01035     value = m_search.InverseEstimate(value);
01036     bd.Undo();
01037     return value >= 1 - m_resignThreshold;
01038 }
01039 
01040 //----------------------------------------------------------------------------
01041 
01042 #endif // GOUCT_PLAYER_H


Sun Mar 13 2011 Doxygen 1.7.1