00001 //---------------------------------------------------------------------------- 00002 /** @file GoUctUtil.h */ 00003 //---------------------------------------------------------------------------- 00004 00005 #ifndef GOUCT_UTIL_H 00006 #define GOUCT_UTIL_H 00007 00008 #include <iosfwd> 00009 #include "GoBoard.h" 00010 #include "GoBoardUtil.h" 00011 #include "GoEyeUtil.h" 00012 #include "GoModBoard.h" 00013 #include "GoUctBoard.h" 00014 #include "SgBlackWhite.h" 00015 #include "SgPoint.h" 00016 #include "SgRandom.h" 00017 #include "SgUctSearch.h" 00018 #include "SgUtil.h" 00019 00020 class SgBWSet; 00021 template<typename T,int N> class SgArrayList; 00022 class SgUctNode; 00023 class SgUctTree; 00024 00025 //---------------------------------------------------------------------------- 00026 00027 /** General utility functions used in GoUct. 00028 These functions are used in GoUct, but should not depend on other classes 00029 in GoUct to avoid cyclic dependencies. */ 00030 namespace GoUctUtil 00031 { 00032 /** reject random move if it was self atari */ 00033 const bool REMOVE_SELF_ATARI = false; 00034 00035 /** reject random move if it was both atari and self atari */ 00036 const bool REMOVE_MUTUAL_ATARI = true; 00037 00038 const int SELF_ATARI_LIMIT = 8; 00039 00040 const int MUTUAL_ATARI_LIMIT = 2; 00041 00042 /** Conservative clump correction. 00043 Only "very clumpy" moves are replaced. 00044 If false, more "clumps" are replaced. */ 00045 const bool CONSERVATIVE_CLUMP = true; 00046 00047 /** Used in clump correction. */ 00048 const int LINE_1_LIMIT = CONSERVATIVE_CLUMP ? 4 : 3; 00049 00050 /** Used in clump correction. */ 00051 const int LINE_2_OR_MORE_LIMIT = CONSERVATIVE_CLUMP ? 6 : 5; 00052 00053 void ClearStatistics(SgPointArray<SgUctStatistics>& stats); 00054 00055 /** Check if move is self-atari and find other liberty, if yes. 00056 This can be applied as a filter in the playout policy, after a move 00057 was generated. It is a useful correction to the move generation using 00058 GoBoardUtil::IsCompletelySurrounded, which prunes backfilling moves. 00059 Does not check if the move is legal, because this is usually already 00060 checked in the playout policy, and requires that the move is no 00061 suicide move (checked with an assertion). 00062 00063 1. If the move is a self-atari 00064 of more than 1 stone it is replaced by the liberty of the resulting 00065 block, unless it is also a self-atari. 00066 00067 2. If p is single stone self-atari, possibly replace by neighbor. 00068 If p is a single stone with only one liberty and it is not a capture 00069 move: check if neighboring point has more liberties. If 00070 yes, replace by neighbor. 00071 @param bd 00072 @param p A (legal) move 00073 @return The replacement move, if one was found, otherwise the 00074 original move. */ 00075 template<class BOARD> 00076 bool DoSelfAtariCorrection(const BOARD& bd, SgPoint& p); 00077 00078 /** Check if p makes ugly clump. Possibly replace by neighbor. 00079 If p is close to many own stones: 00080 check if neighboring point looks better. If 00081 yes, replace by neighbor. */ 00082 template<class BOARD> 00083 bool DoClumpCorrection(const BOARD& bd, SgPoint& p); 00084 00085 /** Check, if playing at a lib gains liberties. 00086 Does not handle capturing moves for efficiency. Not needed, because 00087 capturing moves have a higher priority in the playout. */ 00088 template<class BOARD> 00089 bool GainsLiberties(const BOARD& bd, SgPoint anchor, SgPoint lib); 00090 00091 /** Generate a forced opening move. 00092 This function can be used to generate opening moves instead of doing a 00093 Monte-Carlo tree search, which often returns random looking moves in 00094 the opening on large boards. Experiments showed also an improvement in 00095 playing strength if this function is used. The function currently 00096 generates a move on the 4-4 point of an empty corner under the 00097 following conditions: 00098 # The board size is 13 or larger 00099 # There are no more than 5 stones of each color on the board (avoids 00100 that the move generation triggers in positions containing lots of 00101 setup stones) 00102 # All points in the corner up to and including the 5th row are empty 00103 @return A randomly selected move that fulfills the conditions or 00104 SG_NULLPOINT if no such move exists. */ 00105 SgPoint GenForcedOpeningMove(const GoBoard& bd); 00106 00107 /** Filter for generating moves in random phase. 00108 Checks if a point (must be empty) is a legal move and 00109 GoBoardUtil::IsCompletelySurrounded() returns false. 00110 If a policy generates no pass move as long as there are still moves 00111 on the board that this function would return true for, then the 00112 end position can be scored with GoBoardUtil::ScoreSimpleEndPosition(). */ 00113 template<class BOARD> 00114 bool GeneratePoint(const BOARD& bd, SgBalancer& balancer, SgPoint p, 00115 SgBlackWhite toPlay); 00116 00117 /** Print information about search as Gfx commands for GoGui. 00118 Can be used for GoGui live graphics during the search or GoGui 00119 analyze command type "gfx" after the search (see http://gogui.sf.net). 00120 Best move and best response move as variation (shown as 00121 shadow stones in GoGui). 00122 @param search The search containing the tree and statistics 00123 @param toPlay The color toPlay at the root node of the tree 00124 @param out The stream to write the gfx commands to */ 00125 void GfxBestMove(const SgUctSearch& search, SgBlackWhite toPlay, 00126 std::ostream& out); 00127 00128 /** Print move counts as Gfx commands for GoGui. 00129 Can be used for GoGui live graphics during the search or GoGui 00130 analyze command type "gfx" after the search (see http://gogui.sf.net). 00131 Prints a LABEL command to display the move counts. 00132 @param tree 00133 @param out The stream to write the gfx commands to */ 00134 void GfxCounts(const SgUctTree& tree, std::ostream& out); 00135 00136 /** Print the move values as Gfx commands for GoGui. 00137 Can be used for GoGui live graphics during the search or GoGui 00138 analyze command type "gfx" after the search (see http://gogui.sf.net). 00139 The values of the moves in the root node are shown using an 00140 INFLUENCE gfx command. 00141 @param search The search containing the tree and statistics 00142 @param toPlay The color to play in the root node of the UCT tree 00143 @param out The stream to write the gfx commands to */ 00144 void GfxMoveValues(const SgUctSearch& search, SgBlackWhite toPlay, 00145 std::ostream& out); 00146 00147 /** Print best sequence of search in GoGui live-gfx format. */ 00148 void GfxSequence(const SgUctSearch& search, SgBlackWhite toPlay, 00149 std::ostream& out); 00150 00151 /** Print information about search as GoGui Gfx commands for text 00152 in the status line. 00153 Can be used for GoGui live graphics during the search or GoGui 00154 analyze command type "gfx" after the search (see http://gogui.sf.net). 00155 The following information is in the status line: 00156 - N = Number games 00157 - V = Value of root node 00158 - Len = Average simulation sequence length 00159 - Tree = Average/maximum moves of simulation sequence in tree 00160 - Abrt = Percentage of games aborted (due to maximum game length) 00161 - Gm/s = Simulations per second 00162 @param search The search containing the tree and statistics 00163 @param out The stream to write the gfx commands to */ 00164 void GfxStatus(const SgUctSearch& search, std::ostream& out); 00165 00166 /** Print territory statistics as GoGui gfx commands. 00167 Can be used for GoGui live graphics during the search or GoGui 00168 analyze command type "gfx" after the search (see http://gogui.sf.net). 00169 Uses INFLUENCE gfx command. */ 00170 void GfxTerritoryStatistics( 00171 const SgPointArray<SgUctStatistics>& territoryStatistics, 00172 const GoBoard& bd, std::ostream& out); 00173 00174 /** selfatari of a larger number of stones and also atari on opponent. */ 00175 template<class BOARD> 00176 bool IsMutualAtari(const BOARD& bd, SgBalancer& balancer, SgPoint p, 00177 SgBlackWhite toPlay); 00178 00179 /** Save tree contained in a search as a Go SGF file. 00180 The SGF file is written directly without using SgGameWriter to avoid 00181 a memory-intensive construction of an intermediate game tree. 00182 @param tree The tree 00183 @param boardSize The size of the board 00184 @param stones The Go position corresponding to the root node of the 00185 tree 00186 @param toPlay The color toPlay at the root node of the tree 00187 @param out The stream to save to tree to 00188 @param maxDepth Only save tree to a certain depth. -1 means no limit. */ 00189 void SaveTree(const SgUctTree& tree, int boardSize, const SgBWSet& stones, 00190 SgBlackWhite toPlay, std::ostream& out, int maxDepth = -1); 00191 00192 /** Select a random move from a list of empty points. 00193 The check if GeneratePoint() returns true for the point is done after 00194 the random selection to avoid calling this function for every point in 00195 the list. If GeneratePoint() returns false, the point is removed from 00196 the list and the process is repeated. 00197 @param bd The board 00198 @param toPlay The color to generate the move for 00199 @param emptyPts The list of empty points (will potentially be modified 00200 in this function for efficiency reasons) 00201 @param random The random generator 00202 @param balancer The balancer used in GeneratePoint() 00203 @return The move or SG_NULLMOVE if no empty point is a legal move that 00204 should be generated */ 00205 template<class BOARD> 00206 SgPoint SelectRandom(const BOARD& bd, SgBlackWhite toPlay, 00207 GoPointList& emptyPts, 00208 SgRandom& random, SgBalancer& balancer); 00209 00210 /** Utility function used in DoClumpCorrection() */ 00211 template<class BOARD> 00212 void SetEdgeCorrection(const BOARD& bd, SgPoint p, int& edgeCorrection); 00213 00214 /** Return statistics of all children of a node. 00215 @param search The search containing the tree and statistics 00216 @param bSort Whether sort the children 00217 @param node The node */ 00218 std::string ChildrenStatistics(const SgUctSearch& search, 00219 bool bSort, const SgUctNode& node); 00220 00221 /** check if anchors[] are subset of neighbor blocks of nb */ 00222 template<class BOARD> 00223 bool SubsetOfBlocks(const BOARD& bd, const SgPoint anchor[], SgPoint nb); 00224 } 00225 00226 //---------------------------------------------------------------------------- 00227 00228 template<class BOARD> 00229 bool GoUctUtil::DoClumpCorrection(const BOARD& bd, SgPoint& p) 00230 { 00231 // if not a clump, don't correct p. 00232 if (bd.NumEmptyNeighbors(p) != 1) 00233 return false; 00234 const SgBlackWhite toPlay = bd.ToPlay(); 00235 if (bd.Line(p) == 1) 00236 { 00237 if ( bd.Num8Neighbors(p, toPlay) < LINE_1_LIMIT 00238 || bd.NumNeighbors(p, toPlay) != 2 00239 ) 00240 return false; 00241 } 00242 else if ( bd.Num8Neighbors(p, toPlay) < LINE_2_OR_MORE_LIMIT 00243 || bd.NumNeighbors(p, toPlay) != 3 00244 ) 00245 return false; 00246 00247 // only swap if nb is better than p 00248 const SgPoint nb = GoEyeUtil::EmptyNeighbor(bd, p); 00249 int edgeCorrection_p = 0; 00250 int edgeCorrection_nb = 0; 00251 SetEdgeCorrection(bd, p, edgeCorrection_p); 00252 SetEdgeCorrection(bd, nb, edgeCorrection_nb); 00253 if ( bd.Num8Neighbors(nb, toPlay) + edgeCorrection_nb < 00254 bd.Num8Neighbors(p, toPlay) + edgeCorrection_p 00255 && bd.NumNeighbors(nb, toPlay) <= bd.NumNeighbors(p, toPlay) 00256 && 00257 ( bd.NumEmptyNeighbors(nb) >= 2 00258 || ! GoBoardUtil::SelfAtari(bd, nb) 00259 ) 00260 ) 00261 { 00262 if (CONSERVATIVE_CLUMP) // no further tests, nb is good 00263 { 00264 p = nb; 00265 return true; 00266 } 00267 else 00268 { 00269 // keep p if it was a connection move and nb does not connect at 00270 // least the same blocks 00271 SgPoint anchor[4 + 1]; 00272 bd.NeighborBlocks(p, toPlay, anchor); 00273 SG_ASSERT(anchor[0] != SG_ENDPOINT); // at least 1 block 00274 if (anchor[1] == SG_ENDPOINT // no connection, only 1 block 00275 || SubsetOfBlocks<BOARD>(bd, anchor, nb)) 00276 { 00277 p = nb; 00278 return true; 00279 } 00280 } 00281 } 00282 return false; 00283 } 00284 00285 template<class BOARD> 00286 inline bool GoUctUtil::DoSelfAtariCorrection(const BOARD& bd, SgPoint& p) 00287 { 00288 // Function is inline despite its large size, because it returns quickly 00289 // on average, which makes the function call an overhead 00290 00291 const SgBlackWhite toPlay = bd.ToPlay(); 00292 // no self-atari 00293 if (bd.NumEmptyNeighbors(p) >= 2) 00294 return false; 00295 if (bd.NumNeighbors(p, toPlay) > 0) // p part of existing block(s) 00296 { 00297 if (! GoBoardUtil::SelfAtari(bd, p)) 00298 return false; 00299 SgBlackWhite opp = SgOppBW(toPlay); 00300 SgPoint replaceMove = SG_NULLMOVE; 00301 // ReplaceMove is the liberty we would have after playing at p 00302 for (SgNb4Iterator it(p); it; ++it) 00303 { 00304 SgBoardColor c = bd.GetColor(*it); 00305 if (c == SG_EMPTY) 00306 replaceMove = *it; 00307 else if (c == toPlay) 00308 { 00309 for (typename BOARD::LibertyIterator it2(bd, *it); it2; ++it2) 00310 if (*it2 != p) 00311 { 00312 replaceMove = *it2; 00313 break; 00314 } 00315 } 00316 else if (c == opp && bd.InAtari(*it)) 00317 replaceMove = *it; 00318 if (replaceMove != SG_NULLMOVE) 00319 break; 00320 } 00321 SG_ASSERT(replaceMove != SG_NULLMOVE); 00322 if ( bd.IsLegal(replaceMove) 00323 && ! GoBoardUtil::SelfAtari(bd, replaceMove) 00324 ) 00325 { 00326 p = replaceMove; 00327 return true; 00328 } 00329 } 00330 else if (bd.NumEmptyNeighbors(p) > 0 && ! bd.CanCapture(p, toPlay)) 00331 // single stone with empty neighbors - possibly replace 00332 { 00333 // should we shift to nb? Is it better than p? 00334 const SgPoint nb = GoEyeUtil::EmptyNeighbor(bd, p); 00335 // Check if legal, could violate superko (with BOARD=GoBoard in 00336 // GoUctDefaultPriorKnowledge) 00337 if ( bd.IsLegal(nb) 00338 && ( bd.NumEmptyNeighbors(nb) >= 2 00339 || bd.CanCapture(nb, toPlay) 00340 ) 00341 ) 00342 { 00343 // nb seems better than p - switch. 00344 p = nb; 00345 return true; 00346 } 00347 } 00348 // no replacement found 00349 return false; 00350 } 00351 00352 template<class BOARD> 00353 bool GoUctUtil::GainsLiberties(const BOARD& bd, SgPoint anchor, SgPoint lib) 00354 { 00355 SG_ASSERT(bd.IsEmpty(lib)); 00356 SG_ASSERT(bd.Anchor(anchor) == anchor); 00357 const SgBlackWhite color = bd.GetStone(anchor); 00358 int nu = -2; // need 2 new libs (lose 1 lib by playing on lib itself) 00359 for (SgNb4Iterator it(lib); it; ++it) 00360 { 00361 SgEmptyBlackWhite c = bd.GetColor(*it); 00362 if (c == SG_EMPTY) 00363 { 00364 if (! bd.IsLibertyOfBlock(*it, anchor)) 00365 if (++nu >= 0) 00366 return true; 00367 } 00368 else if (c == color) // merge with block 00369 { 00370 const SgPoint anchor2 = bd.Anchor(*it); 00371 if (anchor != anchor2) 00372 for (typename BOARD::LibertyIterator it(bd, anchor2); it; 00373 ++it) 00374 if (! bd.IsLibertyOfBlock(*it, anchor)) 00375 if (++nu >= 0) 00376 return true; 00377 } 00378 // else capture - not handled, see function documentation 00379 } 00380 return false; 00381 } 00382 00383 template<class BOARD> 00384 inline bool GoUctUtil::IsMutualAtari(const BOARD& bd, SgBalancer& balancer, 00385 SgPoint p, SgBlackWhite toPlay) 00386 { 00387 int nuStones = 0; 00388 if ( GoBoardUtil::SelfAtari(bd, p, nuStones) 00389 && nuStones > MUTUAL_ATARI_LIMIT 00390 && ( nuStones > GoEyeUtil::NAKADE_LIMIT 00391 || ! GoEyeUtil::MakesNakadeShape(bd, p, toPlay) 00392 ) 00393 ) 00394 { 00395 SG_ASSERT(bd.ToPlay() == toPlay); 00396 SgBlackWhite opp = SgOppBW(toPlay); 00397 bool selfatari = 00398 bd.HasNeighbors(p, opp) && 00399 GoBoardUtil::SelfAtariForColor(bd, p, opp); 00400 if (selfatari && balancer.Play(toPlay)) 00401 { 00402 /* 00403 static int count = 0; 00404 if (++count < 30) 00405 SgDebug() << bd << "Removed mutual atari" 00406 << SgWriteMove(p, bd.ToPlay()) << '\n'; */ 00407 return true; 00408 } 00409 } 00410 return false; 00411 } 00412 00413 template<class BOARD> 00414 inline bool GoUctUtil::GeneratePoint(const BOARD& bd, SgBalancer& balancer, 00415 SgPoint p, SgBlackWhite toPlay) 00416 { 00417 SG_ASSERT(bd.IsEmpty(p)); 00418 SG_ASSERT(bd.ToPlay() == toPlay); 00419 if ( GoBoardUtil::IsCompletelySurrounded(bd, p) 00420 //|| GoEyeUtil::IsTwoPointEye(bd, p, toPlay) 00421 || ! bd.IsLegal(p, toPlay) 00422 ) 00423 return false; 00424 if (REMOVE_SELF_ATARI) 00425 { 00426 int nuStones = 0; 00427 if ( GoBoardUtil::SelfAtari(bd, p, nuStones) 00428 && nuStones > SELF_ATARI_LIMIT 00429 // todo: check for nakade shapes here. 00430 ) 00431 { 00432 //SgDebug() << bd << "Removed selfatari" 00433 //<< SgWriteMove(p, toPlay); 00434 return false; 00435 } 00436 } 00437 00438 if (REMOVE_MUTUAL_ATARI && IsMutualAtari(bd, balancer, p, toPlay)) 00439 return false; 00440 return true; 00441 } 00442 00443 template<class BOARD> 00444 inline SgPoint GoUctUtil::SelectRandom(const BOARD& bd, 00445 SgBlackWhite toPlay, 00446 GoPointList& emptyPts, 00447 SgRandom& random, 00448 SgBalancer& balancer) 00449 { 00450 while (true) 00451 { 00452 int length = emptyPts.Length(); 00453 if (length == 0) 00454 break; 00455 int index = random.SmallInt(length); 00456 SgPoint p = emptyPts[index]; 00457 SG_ASSERT(bd.IsEmpty(p)); 00458 if (GeneratePoint(bd, balancer, p, toPlay)) 00459 return p; 00460 emptyPts[index] = emptyPts[length - 1]; 00461 emptyPts.PopBack(); 00462 } 00463 return SG_NULLMOVE; 00464 } 00465 00466 template<class BOARD> 00467 void GoUctUtil::SetEdgeCorrection(const BOARD& bd, SgPoint p, 00468 int& edgeCorrection) 00469 { 00470 if (bd.Line(p) == 1) 00471 { 00472 edgeCorrection += 3; 00473 if (bd.Pos(p) == 1) 00474 edgeCorrection += 2; 00475 } 00476 } 00477 00478 00479 template<class BOARD> 00480 bool GoUctUtil::SubsetOfBlocks(const BOARD& bd, const SgPoint anchor[], 00481 SgPoint nb) 00482 { 00483 SgPoint nbanchor[4 + 1]; 00484 bd.NeighborBlocks(nb, bd.ToPlay(), nbanchor); 00485 if (nbanchor[0] == SG_ENDPOINT) 00486 return false; 00487 for (int i = 0; anchor[i] != SG_ENDPOINT; ++i) 00488 if (! GoBoardUtil::ContainsAnchor(nbanchor, anchor[i])) 00489 return false; 00490 return true; 00491 } 00492 00493 //---------------------------------------------------------------------------- 00494 00495 #endif // GOUCT_UTIL_H