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