Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

GoUctUtil.h

Go to the documentation of this file.
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


Sun Mar 13 2011 Doxygen 1.7.1