00001 //---------------------------------------------------------------------------- 00002 /** @file HexUctState.cpp 00003 00004 @note Use SG_ASSERT so that the assertion handler is used to dump 00005 the state of each thread when an assertion fails. 00006 00007 @bug Running with assertions and a non-zero knowledge threshold in 00008 lock-free mode will cause some assertions to fail. In particular, 00009 the way we handle terminal states (by deleting all children) can 00010 cause SgUctChildIterator to discover it has no children (in 00011 SgUctSearch::UpdateRaveValues and SgUctSearch::SelectChild) which 00012 it asserts is not true. It is also possible for threads to play 00013 into filled-in cells during the in-tree phase. 00014 */ 00015 //---------------------------------------------------------------------------- 00016 00017 #include "SgSystem.h" 00018 00019 #include "SgException.h" 00020 #include "SgMove.h" 00021 00022 #include "BoardUtils.hpp" 00023 #include "BitsetIterator.hpp" 00024 #include "HexUctPolicy.hpp" 00025 #include "HexUctUtil.hpp" 00026 #include "PatternState.hpp" 00027 #include "EndgameUtils.hpp" 00028 #include "SequenceHash.hpp" 00029 00030 using namespace benzene; 00031 00032 /** Prints output during knowledge computation. */ 00033 static const bool DEBUG_KNOWLEDGE = false; 00034 00035 /** Prints hash sequence before computing knowledge. 00036 Use to see what threads are doing knowledge computations. */ 00037 static const bool TRACK_KNOWLEDGE = false; 00038 00039 //---------------------------------------------------------------------------- 00040 00041 namespace 00042 { 00043 00044 /** Returns true if board is entirely filled. */ 00045 bool GameOver(const StoneBoard& brd) 00046 { 00047 return brd.GetEmpty().none(); 00048 } 00049 00050 /** Determines the winner of a filled-in board. */ 00051 HexColor GetWinner(const StoneBoard& brd) 00052 { 00053 SG_ASSERT(GameOver(brd)); 00054 if (BoardUtils::ConnectedOnBitset(brd.Const(), brd.GetColor(BLACK), 00055 NORTH, SOUTH)) 00056 return BLACK; 00057 return WHITE; 00058 } 00059 00060 /** Returns winner if there is one. Returns EMPTY if no winner. */ 00061 HexColor CheckIfWinner(const StoneBoard& brd) 00062 { 00063 if (BoardUtils::ConnectedOnBitset(brd.Const(), brd.GetColor(BLACK), 00064 NORTH, SOUTH)) 00065 return BLACK; 00066 if (BoardUtils::ConnectedOnBitset(brd.Const(), brd.GetColor(WHITE), 00067 EAST, WEST)) 00068 return WHITE; 00069 return EMPTY; 00070 } 00071 00072 } // namespace 00073 00074 //---------------------------------------------------------------------------- 00075 00076 HexUctState::AssertionHandler::AssertionHandler(const HexUctState& state) 00077 : m_state(state) 00078 { 00079 } 00080 00081 void HexUctState::AssertionHandler::Run() 00082 { 00083 LogSevere() << m_state.Dump() << '\n'; 00084 } 00085 00086 //---------------------------------------------------------------------------- 00087 00088 HexUctState::HexUctState(const unsigned int threadId, 00089 HexUctSearch& sch, 00090 int treeUpdateRadius, 00091 int playoutUpdateRadius) 00092 : SgUctThreadState(threadId, HexUctUtil::ComputeMaxNumMoves()), 00093 m_assertionHandler(*this), 00094 00095 m_bd(0), 00096 m_vc_brd(0), 00097 m_policy(0), 00098 m_shared_data(0), 00099 m_knowledge(*this), 00100 m_search(sch), 00101 m_treeUpdateRadius(treeUpdateRadius), 00102 m_playoutUpdateRadius(playoutUpdateRadius), 00103 m_isInPlayout(false) 00104 { 00105 } 00106 00107 HexUctState::~HexUctState() 00108 { 00109 } 00110 00111 void HexUctState::SetPolicy(HexUctSearchPolicy* policy) 00112 { 00113 m_policy.reset(policy); 00114 } 00115 00116 std::string HexUctState::Dump() const 00117 { 00118 std::ostringstream os; 00119 os << "HexUctState[" << m_threadId << "] "; 00120 if (m_isInPlayout) 00121 os << "[playout] "; 00122 os << "board:" << *m_bd; 00123 return os.str(); 00124 } 00125 00126 SgUctValue HexUctState::Evaluate() 00127 { 00128 SG_ASSERT(GameOver(*m_bd)); 00129 SgUctValue score = (GetWinner(*m_bd) == m_toPlay) ? 1.0 : 0.0; 00130 return score; 00131 } 00132 00133 void HexUctState::Execute(SgMove sgmove) 00134 { 00135 ExecuteTreeMove(static_cast<HexPoint>(sgmove)); 00136 m_toPlay = !m_toPlay; 00137 } 00138 00139 void HexUctState::ExecutePlayout(SgMove sgmove) 00140 { 00141 ExecuteRolloutMove(static_cast<HexPoint>(sgmove)); 00142 m_toPlay = !m_toPlay; 00143 } 00144 00145 void HexUctState::ExecuteTreeMove(HexPoint move) 00146 { 00147 { 00148 HexUctPolicy* blah = dynamic_cast<HexUctPolicy*>(m_policy.get()); 00149 if (!blah) 00150 abort(); 00151 blah->AddResponse(m_toPlay, m_lastMovePlayed, move); 00152 } 00153 00154 m_game_sequence.push_back(Move(m_toPlay, move)); 00155 ExecutePlainMove(move, m_treeUpdateRadius); 00156 HexUctStoneData stones; 00157 if (m_shared_data->stones.get(SequenceHash::Hash(m_game_sequence), stones)) 00158 { 00159 m_bd->StartNewGame(); 00160 m_bd->SetColor(BLACK, stones.black); 00161 m_bd->SetColor(WHITE, stones.white); 00162 m_bd->SetPlayed(stones.played); 00163 m_pastate->Update(); 00164 } 00165 } 00166 00167 void HexUctState::ExecuteRolloutMove(HexPoint move) 00168 { 00169 ExecutePlainMove(move, m_playoutUpdateRadius); 00170 } 00171 00172 void HexUctState::ExecutePlainMove(HexPoint cell, int updateRadius) 00173 { 00174 // Lock-free mode: It is possible we are playing into a filled-in 00175 // cell during the in-tree phase. This can occur if the thread 00176 // happens upon this state after fillin was published but before 00177 // the tree was pruned. 00178 // If assertions are off, this results in a board possibly 00179 // containing cells of both colors and erroneous pattern state 00180 // info, resulting in an inaccurate playout value. In practice, 00181 // this does not seem to matter too much. 00182 // If assertions are on, this will cause the search to abort 00183 // needlessly. 00184 // @todo Handle case when assertions are on. 00185 SG_ASSERT(m_bd->IsEmpty(cell)); 00186 SG_ASSERT(m_pastate->UpdateRadius() == updateRadius); 00187 00188 m_bd->PlayMove(m_toPlay, cell); 00189 if (updateRadius == 1) 00190 m_pastate->UpdateRingGodel(cell); 00191 else 00192 m_pastate->Update(cell); 00193 00194 m_lastMovePlayed = cell; 00195 m_new_game = false; 00196 } 00197 00198 bool HexUctState::GenerateAllMoves(SgUctValue count, 00199 std::vector<SgMoveInfo>& moves, 00200 SgProvenNodeType& provenType) 00201 { 00202 moves.clear(); 00203 00204 // Handle root node as a special case 00205 if (m_new_game) 00206 { 00207 for (BitsetIterator it(m_shared_data->root_consider); it; ++it) 00208 moves.push_back(SgMoveInfo(*it)); 00209 if (count == 0) 00210 m_knowledge.ProcessPosition(moves); 00211 return false; 00212 } 00213 00214 bool truncateChildTrees = false; 00215 if (count == 0) 00216 { 00217 { 00218 HexColor winner = CheckIfWinner(*m_bd); 00219 if (winner != EMPTY) 00220 { 00221 provenType = (winner == m_toPlay) 00222 ? SG_PROVEN_WIN : SG_PROVEN_LOSS; 00223 return false; 00224 } 00225 } 00226 for (BitsetIterator it(m_bd->GetEmpty()); it; ++it) 00227 moves.push_back(SgMoveInfo(*it)); 00228 m_knowledge.ProcessPosition(moves); 00229 } 00230 else 00231 { 00232 // Prune moves outside of mustplay and fillin 00233 if (TRACK_KNOWLEDGE) 00234 { 00235 hash_t hash = SequenceHash::Hash(m_game_sequence); 00236 LogInfo() << m_threadId << ": " 00237 << HashUtil::toString(hash) << '\n'; 00238 } 00239 truncateChildTrees = true; 00240 bitset_t moveset = m_bd->GetEmpty() & ComputeKnowledge(provenType); 00241 for (BitsetIterator it(moveset); it; ++it) 00242 moves.push_back(SgMoveInfo(*it)); 00243 } 00244 return truncateChildTrees; 00245 } 00246 00247 SgMove HexUctState::GeneratePlayoutMove(bool& skipRaveUpdate) 00248 { 00249 skipRaveUpdate = false; 00250 00251 if (GameOver(*m_bd)) 00252 return SG_NULLMOVE; 00253 00254 SgPoint move = m_policy->GenerateMove(*m_pastate, m_toPlay, 00255 m_lastMovePlayed); 00256 SG_ASSERT(move != SG_NULLMOVE); 00257 return move; 00258 } 00259 00260 void HexUctState::StartSearch() 00261 { 00262 LogInfo() << "StartSearch()[" << m_threadId <<"]" << '\n'; 00263 m_shared_data = &m_search.SharedData(); 00264 00265 // @todo Fix the interface to HexBoard so this can be constant! 00266 // The problem is that VCBuilder (which is inside of HexBoard) 00267 // expects a non-const reference to a VCBuilderParam object. 00268 HexBoard& brd = const_cast<HexBoard&>(m_search.Board()); 00269 00270 if (!m_bd.get() 00271 || m_bd->Width() != brd.Width() 00272 || m_bd->Height() != brd.Height()) 00273 { 00274 m_bd.reset(new StoneBoard(brd.Width(), brd.Height())); 00275 m_pastate.reset(new PatternState(*m_bd)); 00276 m_vc_brd.reset(new HexBoard(brd.Width(), brd.Height(), 00277 brd.ICE(), brd.Builder().Parameters())); 00278 } 00279 00280 m_policy->InitializeForSearch(); 00281 } 00282 00283 void HexUctState::TakeBackInTree(std::size_t nuMoves) 00284 { 00285 SG_UNUSED(nuMoves); 00286 } 00287 00288 void HexUctState::TakeBackPlayout(std::size_t nuMoves) 00289 { 00290 SG_UNUSED(nuMoves); 00291 } 00292 00293 SgBlackWhite HexUctState::ToPlay() const 00294 { 00295 return HexUctUtil::ToSgBlackWhite(m_toPlay); 00296 } 00297 00298 void HexUctState::GameStart() 00299 { 00300 m_new_game = true; 00301 m_isInPlayout = false; 00302 m_game_sequence = m_shared_data->game_sequence; 00303 m_toPlay = m_shared_data->root_to_play; 00304 m_lastMovePlayed = m_shared_data->root_last_move_played; 00305 m_pastate->SetUpdateRadius(m_treeUpdateRadius); 00306 00307 m_bd->StartNewGame(); 00308 m_bd->SetColor(BLACK, m_shared_data->root_stones.black); 00309 m_bd->SetColor(WHITE, m_shared_data->root_stones.white); 00310 m_bd->SetPlayed(m_shared_data->root_stones.played); 00311 m_pastate->Update(); 00312 } 00313 00314 void HexUctState::StartPlayouts() 00315 { 00316 m_isInPlayout = true; 00317 m_pastate->SetUpdateRadius(m_playoutUpdateRadius); 00318 00319 /** Playout radius should normally be no bigger than tree radius, 00320 but if it is, we need to do an extra update for each playout 00321 during the transition from the tree phase to the playout 00322 phase. */ 00323 if (m_playoutUpdateRadius > m_treeUpdateRadius) 00324 m_pastate->Update(); 00325 } 00326 00327 void HexUctState::StartPlayout() 00328 { 00329 m_policy->InitializeForRollout(*m_bd); 00330 } 00331 00332 void HexUctState::EndPlayout() 00333 { 00334 } 00335 00336 /** Computes moves to consider and stores fillin in the shared 00337 data. */ 00338 bitset_t HexUctState::ComputeKnowledge(SgProvenNodeType& provenType) 00339 { 00340 m_vc_brd->GetPosition().StartNewGame(); 00341 m_vc_brd->GetPosition().SetColor(BLACK, m_bd->GetPlayed(BLACK)); 00342 m_vc_brd->GetPosition().SetColor(WHITE, m_bd->GetPlayed(WHITE)); 00343 m_vc_brd->GetPosition().SetPlayed(m_bd->GetPlayed()); 00344 m_vc_brd->ComputeAll(m_toPlay); 00345 00346 // Consider set will be all empty cells if state is a determined 00347 // state (can't compute consider set in this case and we cannot 00348 // delete the children as this will cause a race condition in the 00349 // parent class). 00350 // 00351 // Consider set is the set of moves to consider otherwise. 00352 bitset_t consider; 00353 if (EndgameUtils::IsDeterminedState(*m_vc_brd, m_toPlay)) 00354 { 00355 HexColor winner = m_toPlay; 00356 provenType = SG_PROVEN_WIN; 00357 if (EndgameUtils::IsLostGame(*m_vc_brd, m_toPlay)) 00358 { 00359 winner = !m_toPlay; 00360 provenType = SG_PROVEN_LOSS; 00361 } 00362 if (DEBUG_KNOWLEDGE) 00363 LogInfo() << "Found win for " << winner << ": " << '\n' 00364 << *m_vc_brd << '\n'; 00365 return m_bd->GetEmpty(); 00366 } 00367 else 00368 { 00369 provenType = SG_NOT_PROVEN; 00370 consider = EndgameUtils::MovesToConsider(*m_vc_brd, m_toPlay); 00371 } 00372 00373 m_shared_data->stones.put(SequenceHash::Hash(m_game_sequence), 00374 HexUctStoneData(m_vc_brd->GetPosition())); 00375 if (DEBUG_KNOWLEDGE) 00376 LogInfo() << "===================================" << '\n' 00377 << "Recomputed state:" << '\n' << *m_bd << '\n' 00378 << "Consider:" << m_vc_brd->Write(consider) << '\n'; 00379 00380 return consider; 00381 } 00382 00383 //----------------------------------------------------------------------------