00001 //---------------------------------------------------------------------------- 00002 /** @file GoUctPureRandomGenerator.h */ 00003 //---------------------------------------------------------------------------- 00004 00005 #ifndef GOUCT_PURERANDOMGENERATOR_H 00006 #define GOUCT_PURERANDOMGENERATOR_H 00007 00008 #include <vector> 00009 #include "GoBoard.h" 00010 #include "GoUctUtil.h" 00011 #include "SgWrite.h" 00012 00013 //---------------------------------------------------------------------------- 00014 00015 /** Randomly select from empty points on the board. 00016 Finds and shuffles the empty points on the board at the beginning 00017 to avoid repeated loops over the board to find empty points. 00018 */ 00019 template<class BOARD> 00020 class GoUctPureRandomGenerator 00021 { 00022 public: 00023 GoUctPureRandomGenerator(const BOARD& bd, SgRandom& random); 00024 00025 /** Finds and shuffles the empty points currently on the board. */ 00026 void Start(); 00027 00028 /** Update state. 00029 Must be called after each play on the board. 00030 */ 00031 void OnPlay(); 00032 00033 /** Return a list of points that are currently potentially empty. 00034 As a side-benefit, the generator can be used to get the list of empty 00035 points on the board to speed up full-board loops over empty points 00036 or to get a shuffled list of the empty points (e.g. for finding 00037 legal moves when expanding a node in the in-tree-phase of UCT). 00038 Points in the list are candidates, they still have to be tested, if 00039 they are really empty. 00040 */ 00041 const std::vector<SgPoint>& Candidates() const; 00042 00043 /** Generate a pure random move. 00044 Randomly select an empty point on the board that fulfills 00045 GoUctUtil::GeneratePoint() for the color currently to play on the 00046 board. 00047 */ 00048 SgPoint Generate(SgBalancer& balancer); 00049 00050 /** Generate a move using the fillboard heuristic. 00051 Tries @c numberTries times to select a point on the board and 00052 returns it, if it is empty and all adjacent and diagonal neighbors are 00053 empty. Otherwise it returns SG_NULLMOVE. Using this heuristic before 00054 any other heuristics is helpful to increase playout diversity on large 00055 boards. See section 6.1 of: 00056 Chatriot, Gelly, Hoock, Perez, Rimmel, Teytaud: 00057 <a href="http://www.lri.fr/~teytaud/eg.pdf"> 00058 Combining expert, offline, transient and online learning in 00059 Monte-Carlo exploration</a> 00060 */ 00061 SgPoint GenerateFillboardMove(int numberTries); 00062 00063 private: 00064 const BOARD& m_bd; 00065 00066 float m_invNuPoints; 00067 00068 float m_nuEmptyFloat; 00069 00070 SgRandom& m_random; 00071 00072 /** Points that are potentially empty. */ 00073 std::vector<SgPoint> m_candidates; 00074 00075 bool Empty3x3(SgPoint p) const; 00076 00077 void CheckConsistency() const; 00078 00079 void Insert(SgPoint p); 00080 }; 00081 00082 template<class BOARD> 00083 GoUctPureRandomGenerator<BOARD>::GoUctPureRandomGenerator(const BOARD& bd, 00084 SgRandom& random) 00085 : m_bd(bd), 00086 m_random(random) 00087 { 00088 m_candidates.reserve(GO_MAX_NUM_MOVES); 00089 } 00090 00091 template<class BOARD> 00092 inline const std::vector<SgPoint>& 00093 GoUctPureRandomGenerator<BOARD>::Candidates() 00094 const 00095 { 00096 return m_candidates; 00097 } 00098 00099 template<class BOARD> 00100 inline void GoUctPureRandomGenerator<BOARD>::CheckConsistency() const 00101 { 00102 #if 0 // Expensive check, enable only for debugging 00103 for (GoBoard::Iterator it(m_bd); it; ++it) 00104 { 00105 SgPoint p = *it; 00106 if (m_bd.IsEmpty(p)) 00107 if (find(m_candidates.begin(), m_candidates.end(), p) 00108 == m_candidates.end()) 00109 { 00110 SgDebug() << m_bd 00111 << "Candidates: " << SgWritePointList(m_candidates) 00112 << "does not contain: " << SgWritePoint(p) 00113 << "\nm_bd.CapturedStones(): " 00114 << SgWriteSPointList<SG_MAX_ONBOARD + 1> 00115 (m_bd.CapturedStones()) 00116 << "Last move: " 00117 << SgWritePoint(m_bd.GetLastMove()) << '\n'; 00118 SG_ASSERT(false); 00119 } 00120 } 00121 #endif 00122 } 00123 00124 template<class BOARD> 00125 inline bool GoUctPureRandomGenerator<BOARD>::Empty3x3(SgPoint p) 00126 const 00127 { 00128 return (m_bd.NumEmptyNeighbors(p) == 4 00129 && m_bd.NumEmptyDiagonals(p) == 4); 00130 } 00131 00132 template<class BOARD> 00133 inline SgPoint GoUctPureRandomGenerator<BOARD>::Generate(SgBalancer& balancer) 00134 { 00135 CheckConsistency(); 00136 SgBlackWhite toPlay = m_bd.ToPlay(); 00137 size_t i = m_candidates.size(); 00138 while (true) 00139 { 00140 if (i == 0) 00141 break; 00142 --i; 00143 SgPoint p = m_candidates[i]; 00144 if (! m_bd.IsEmpty(p)) 00145 { 00146 m_candidates[i] = m_candidates[m_candidates.size() - 1]; 00147 m_candidates.pop_back(); 00148 continue; 00149 } 00150 if (GoUctUtil::GeneratePoint(m_bd, balancer, p, toPlay)) 00151 { 00152 CheckConsistency(); 00153 return p; 00154 } 00155 } 00156 CheckConsistency(); 00157 return SG_NULLMOVE; 00158 } 00159 00160 template<class BOARD> 00161 inline SgPoint GoUctPureRandomGenerator<BOARD>::GenerateFillboardMove( 00162 int numberTries) 00163 { 00164 float effectiveTries = numberTries * m_nuEmptyFloat * m_invNuPoints; 00165 size_t i = m_candidates.size(); 00166 while (effectiveTries > 1.f) 00167 { 00168 if (i == 0) 00169 return SG_NULLMOVE; 00170 --i; 00171 SgPoint p = m_candidates[i]; 00172 if (! m_bd.IsEmpty(p)) 00173 { 00174 m_candidates[i] = m_candidates[m_candidates.size() - 1]; 00175 m_candidates.pop_back(); 00176 continue; 00177 } 00178 if (Empty3x3(p)) 00179 return p; 00180 effectiveTries -= 1.f; 00181 } 00182 // Remaning fractional number of tries 00183 if (m_random.Int(100) > 100 * effectiveTries) 00184 return SG_NULLMOVE; 00185 while (true) 00186 { 00187 if (i == 0) 00188 break; 00189 --i; 00190 SgPoint p = m_candidates[i]; 00191 if (! m_bd.IsEmpty(p)) 00192 { 00193 m_candidates[i] = m_candidates[m_candidates.size() - 1]; 00194 m_candidates.pop_back(); 00195 continue; 00196 } 00197 if (Empty3x3(p)) 00198 return p; 00199 break; 00200 } 00201 return SG_NULLMOVE; 00202 } 00203 00204 template<class BOARD> 00205 inline void GoUctPureRandomGenerator<BOARD>::OnPlay() 00206 { 00207 SgPoint lastMove = m_bd.GetLastMove(); 00208 if (lastMove != SG_NULLMOVE && lastMove != SG_PASS 00209 && ! m_bd.IsEmpty(lastMove)) 00210 m_nuEmptyFloat -= 1.f; 00211 const GoPointList& capturedStones = m_bd.CapturedStones(); 00212 if (! capturedStones.IsEmpty()) 00213 { 00214 // Don't remove stone played, too expensive, check later in Generate() 00215 // that generated point is still empty 00216 for (GoPointList::Iterator it(capturedStones); it; ++it) 00217 Insert(*it); 00218 m_nuEmptyFloat += capturedStones.Length(); 00219 } 00220 CheckConsistency(); 00221 } 00222 00223 /** Insert new candidate at random place. */ 00224 template<class BOARD> 00225 inline void GoUctPureRandomGenerator<BOARD>::Insert(SgPoint p) 00226 { 00227 size_t size = m_candidates.size(); 00228 if (size == 0) 00229 m_candidates.push_back(p); 00230 else 00231 { 00232 SgPoint& swapPoint = m_candidates[m_random.Int(size)]; 00233 m_candidates.push_back(swapPoint); 00234 swapPoint = p; 00235 } 00236 } 00237 00238 template<class BOARD> 00239 inline void GoUctPureRandomGenerator<BOARD>::Start() 00240 { 00241 m_nuEmptyFloat = 0; 00242 m_candidates.clear(); 00243 for (typename BOARD::Iterator it(m_bd); it; ++it) 00244 if (m_bd.IsEmpty(*it)) 00245 { 00246 Insert(*it); 00247 m_nuEmptyFloat += 1.f; 00248 } 00249 m_invNuPoints = 1.f / (m_bd.Size() * m_bd.Size()); 00250 CheckConsistency(); 00251 } 00252 00253 //---------------------------------------------------------------------------- 00254 00255 #endif // GOUCT_PURERANDOMGENERATOR_H