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