00001 //---------------------------------------------------------------------------- 00002 /** @file GoUctSearch.cpp */ 00003 //---------------------------------------------------------------------------- 00004 00005 #include "SgSystem.h" 00006 #include "GoUctSearch.h" 00007 00008 #include <fstream> 00009 #include <iostream> 00010 #include "GoBoardUtil.h" 00011 #include "GoNodeUtil.h" 00012 #include "GoUctUtil.h" 00013 #include "SgDebug.h" 00014 #include "SgGameWriter.h" 00015 #include "SgNode.h" 00016 #include "SgUctTreeUtil.h" 00017 00018 using namespace std; 00019 00020 //---------------------------------------------------------------------------- 00021 00022 namespace { 00023 00024 //---------------------------------------------------------------------------- 00025 00026 const int MOVERANGE = SG_PASS + 1; 00027 00028 SgNode* AppendChild(SgNode* node, const string& comment) 00029 { 00030 SgNode* child = node->NewRightMostSon(); 00031 child->AddComment(comment); 00032 return child; 00033 } 00034 00035 SgNode* AppendChild(SgNode* node, SgBlackWhite color, SgPoint move) 00036 { 00037 SgNode* child = node->NewRightMostSon(); 00038 SgPropID propId = 00039 (color == SG_BLACK ? SG_PROP_MOVE_BLACK : SG_PROP_MOVE_WHITE); 00040 child->Add(new SgPropMove(propId, move)); 00041 return child; 00042 } 00043 00044 /** Append game to saved simulations (used if m_keepGames is true) */ 00045 void AppendGame(SgNode* node, SgUctValue gameNumber, unsigned int threadId, 00046 SgBlackWhite toPlay, const SgUctGameInfo& info) 00047 { 00048 SG_ASSERT(node != 0); 00049 { 00050 ostringstream comment; 00051 comment << "Thread " << threadId << '\n' 00052 << "Game " << gameNumber << '\n'; 00053 node = AppendChild(node, comment.str()); 00054 } 00055 size_t nuMovesInTree = info.m_inTreeSequence.size(); 00056 for (size_t i = 0; i < nuMovesInTree; ++i) 00057 { 00058 node = AppendChild(node, toPlay, info.m_inTreeSequence[i]); 00059 toPlay = SgOppBW(toPlay); 00060 } 00061 SgNode* lastInTreeNode = node; 00062 SgBlackWhite lastInTreeToPlay = toPlay; 00063 for (size_t i = 0; i < info.m_eval.size(); ++i) 00064 { 00065 node = lastInTreeNode; 00066 toPlay = lastInTreeToPlay; 00067 ostringstream comment; 00068 comment << "Playout " << i << '\n' 00069 << "Eval " << info.m_eval[i] << '\n' 00070 << "Aborted " << info.m_aborted[i] << '\n'; 00071 node = AppendChild(node, comment.str()); 00072 for (size_t j = nuMovesInTree; j < info.m_sequence[i].size(); ++j) 00073 { 00074 node = AppendChild(node, toPlay, info.m_sequence[i][j]); 00075 toPlay = SgOppBW(toPlay); 00076 } 00077 } 00078 } 00079 00080 //---------------------------------------------------------------------------- 00081 00082 } // namespace 00083 00084 //---------------------------------------------------------------------------- 00085 00086 GoUctState::AssertionHandler::AssertionHandler(const GoUctState& state) 00087 : m_state(state) 00088 { 00089 } 00090 00091 void GoUctState::AssertionHandler::Run() 00092 { 00093 m_state.Dump(SgDebug()); 00094 } 00095 00096 //---------------------------------------------------------------------------- 00097 00098 GoUctState::GoUctState(unsigned int threadId, const GoBoard& bd) 00099 : SgUctThreadState(threadId, MOVERANGE), 00100 m_assertionHandler(*this), 00101 m_uctBd(bd), 00102 m_synchronizer(bd) 00103 { 00104 m_synchronizer.SetSubscriber(m_bd); 00105 m_isInPlayout = false; 00106 } 00107 00108 void GoUctState::Dump(ostream& out) const 00109 { 00110 out << "GoUctState[" << m_threadId << "] "; 00111 if (m_isInPlayout) 00112 out << "playout board:\n" << m_uctBd; 00113 else 00114 out << "board:\n" << m_bd; 00115 } 00116 00117 void GoUctState::Execute(SgMove move) 00118 { 00119 SG_ASSERT(! m_isInPlayout); 00120 SG_ASSERT(move == SG_PASS || ! m_bd.Occupied(move)); 00121 // Temporarily switch ko rule to SIMPLEKO to avoid slow full board 00122 // repetition test in GoBoard::Play() 00123 GoRestoreKoRule restoreKoRule(m_bd); 00124 m_bd.Rules().SetKoRule(GoRules::SIMPLEKO); 00125 m_bd.Play(move); 00126 SG_ASSERT(! m_bd.LastMoveInfo(GO_MOVEFLAG_ILLEGAL)); 00127 ++m_gameLength; 00128 } 00129 00130 void GoUctState::ExecutePlayout(SgMove move) 00131 { 00132 SG_ASSERT(m_isInPlayout); 00133 SG_ASSERT(move == SG_PASS || ! m_uctBd.Occupied(move)); 00134 m_uctBd.Play(move); 00135 ++m_gameLength; 00136 } 00137 00138 void GoUctState::GameStart() 00139 { 00140 m_isInPlayout = false; 00141 m_gameLength = 0; 00142 } 00143 00144 void GoUctState::StartPlayout() 00145 { 00146 m_uctBd.Init(m_bd); 00147 } 00148 00149 void GoUctState::StartPlayouts() 00150 { 00151 m_isInPlayout = true; 00152 } 00153 00154 void GoUctState::StartSearch() 00155 { 00156 m_synchronizer.UpdateSubscriber(); 00157 } 00158 00159 void GoUctState::TakeBackInTree(std::size_t nuMoves) 00160 { 00161 for (size_t i = 0; i < nuMoves; ++i) 00162 m_bd.Undo(); 00163 } 00164 00165 void GoUctState::TakeBackPlayout(std::size_t nuMoves) 00166 { 00167 m_gameLength -= nuMoves; 00168 } 00169 00170 //---------------------------------------------------------------------------- 00171 00172 GoUctSearch::GoUctSearch(GoBoard& bd, SgUctThreadStateFactory* factory) 00173 : SgUctSearch(factory, MOVERANGE), 00174 m_keepGames(false), 00175 m_liveGfxInterval(5000), 00176 m_toPlay(SG_BLACK), 00177 m_bd(bd), 00178 m_root(0), 00179 m_liveGfx(GOUCT_LIVEGFX_NONE) 00180 { 00181 SetRaveCheckSame(true); 00182 } 00183 00184 GoUctSearch::~GoUctSearch() 00185 { 00186 if (m_root != 0) 00187 m_root->DeleteTree(); 00188 m_root = 0; 00189 } 00190 00191 std::string GoUctSearch::MoveString(SgMove move) const 00192 { 00193 return SgPointUtil::PointToString(move); 00194 } 00195 00196 void GoUctSearch::OnSearchIteration(SgUctValue gameNumber, 00197 unsigned int threadId, 00198 const SgUctGameInfo& info) 00199 { 00200 SgUctSearch::OnSearchIteration(gameNumber, threadId, info); 00201 00202 if (m_liveGfx != GOUCT_LIVEGFX_NONE && threadId == 0 00203 && gameNumber > m_nextLiveGfx) 00204 { 00205 m_nextLiveGfx = gameNumber + m_liveGfxInterval; 00206 DisplayGfx(); 00207 } 00208 if (! LockFree() && m_root != 0) 00209 AppendGame(m_root, gameNumber, threadId, m_toPlay, info); 00210 } 00211 00212 void GoUctSearch::DisplayGfx() 00213 { 00214 SgDebug() << "gogui-gfx:\n"; 00215 switch (m_liveGfx) 00216 { 00217 case GOUCT_LIVEGFX_COUNTS: 00218 GoUctUtil::GfxBestMove(*this, m_toPlay, SgDebug()); 00219 GoUctUtil::GfxMoveValues(*this, m_toPlay, SgDebug()); 00220 GoUctUtil::GfxCounts(Tree(), SgDebug()); 00221 GoUctUtil::GfxStatus(*this, SgDebug()); 00222 break; 00223 case GOUCT_LIVEGFX_SEQUENCE: 00224 GoUctUtil::GfxSequence(*this, m_toPlay, SgDebug()); 00225 GoUctUtil::GfxStatus(*this, SgDebug()); 00226 break; 00227 case GOUCT_LIVEGFX_NONE: 00228 SG_ASSERT(false); // Should only be called when LiveGfx is enabled 00229 break; 00230 } 00231 SgDebug() << '\n'; 00232 } 00233 00234 void GoUctSearch::OnStartSearch() 00235 { 00236 SgUctSearch::OnStartSearch(); 00237 00238 if (m_root != 0) 00239 { 00240 m_root->DeleteTree(); 00241 m_root = 0; 00242 } 00243 if (m_keepGames) 00244 { 00245 m_root = GoNodeUtil::CreateRoot(m_bd); 00246 if (LockFree()) 00247 SgWarning() << 00248 "GoUctSearch: keep games will be ignored" 00249 " in lock free search\n"; 00250 } 00251 m_toPlay = m_bd.ToPlay(); // Not needed if SetToPlay() was called 00252 for (SgBWIterator it; it; ++it) 00253 m_stones[*it] = m_bd.All(*it); 00254 int size = m_bd.Size(); 00255 // Limit to avoid very long games if m_simpleKo 00256 int maxGameLength = min(3 * size * size, 00257 GO_MAX_NUM_MOVES - m_bd.MoveNumber()); 00258 SetMaxGameLength(maxGameLength); 00259 m_boardHistory.SetFromBoard(m_bd); 00260 00261 m_nextLiveGfx = m_liveGfxInterval; 00262 } 00263 00264 void GoUctSearch::SaveGames(const string& fileName) const 00265 { 00266 if (MpiSynchronizer()->IsRootProcess()) 00267 { 00268 if (m_root == 0) 00269 throw SgException("No games to save"); 00270 ofstream out(fileName.c_str()); 00271 SgGameWriter writer(out); 00272 writer.WriteGame(*m_root, true, 0, 1, 19); 00273 } 00274 } 00275 00276 void GoUctSearch::SaveTree(std::ostream& out, int maxDepth) const 00277 { 00278 GoUctUtil::SaveTree(Tree(), m_bd.Size(), m_stones, m_toPlay, out, 00279 maxDepth); 00280 } 00281 00282 SgBlackWhite GoUctSearch::ToPlay() const 00283 { 00284 return m_toPlay; 00285 } 00286 00287 //---------------------------------------------------------------------------- 00288 00289 SgPoint GoUctSearchUtil::TrompTaylorPassCheck(SgPoint move, 00290 const GoUctSearch& search) 00291 { 00292 const GoBoard& bd = search.Board(); 00293 bool isFirstPass = (bd.GetLastMove() != SG_PASS); 00294 bool isTrompTaylorRules = bd.Rules().CaptureDead(); 00295 if (move != SG_PASS || ! isTrompTaylorRules || ! isFirstPass) 00296 return move; 00297 float komi = bd.Rules().Komi().ToFloat(); 00298 float trompTaylorScore = GoBoardUtil::TrompTaylorScore(bd, komi); 00299 if (search.ToPlay() != SG_BLACK) 00300 trompTaylorScore *= -1; 00301 const SgUctTree& tree = search.Tree(); 00302 const SgUctNode& root = tree.Root(); 00303 SgUctValue value = root.Mean(); 00304 SgUctValue trompTaylorWinValue = (trompTaylorScore > 0 ? 1 : 0); 00305 if (value < trompTaylorWinValue) 00306 return move; 00307 SgDebug() << "GoUctSearchUtil::TrompTaylorPassCheck: bad pass move value=" 00308 << value << " trompTaylorScore=" << trompTaylorScore << '\n'; 00309 vector<SgMove> excludeMoves; 00310 excludeMoves.push_back(SG_PASS); 00311 const SgUctNode* bestChild = search.FindBestChild(root, &excludeMoves); 00312 if (bestChild == 0) 00313 { 00314 SgDebug() << 00315 "GoUctSearchUtil::TrompTaylorPassCheck: " 00316 "(no second best move found)\n"; 00317 return move; 00318 } 00319 return bestChild->Move(); 00320 } 00321 00322 //----------------------------------------------------------------------------