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


17 Jun 2010 Doxygen 1.4.7