00001 //---------------------------------------------------------------------------- 00002 /** @file MoHexEngine.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgSystem.h" 00007 00008 #include "BitsetIterator.hpp" 00009 #include "MoHexEngine.hpp" 00010 #include "MoHexPlayer.hpp" 00011 #include "PlayAndSolve.hpp" 00012 #include "SwapCheck.hpp" 00013 00014 using namespace benzene; 00015 00016 //---------------------------------------------------------------------------- 00017 00018 namespace { 00019 00020 std::string KnowledgeThresholdToString(const std::vector<SgUctValue>& t) 00021 { 00022 if (t.empty()) 00023 return "0"; 00024 std::ostringstream os; 00025 os << '\"'; 00026 for (std::size_t i = 0; i < t.size(); ++i) 00027 { 00028 if (i > 0) 00029 os << ' '; 00030 os << t[i]; 00031 } 00032 os << '\"'; 00033 return os.str(); 00034 } 00035 00036 std::vector<SgUctValue> KnowledgeThresholdFromString(const std::string& val) 00037 { 00038 std::vector<SgUctValue> v; 00039 std::istringstream is(val); 00040 SgUctValue t; 00041 while (is >> t) 00042 v.push_back(t); 00043 if (v.size() == 1 && v[0] == 0) 00044 v.clear(); 00045 return v; 00046 } 00047 00048 } 00049 00050 //---------------------------------------------------------------------------- 00051 00052 MoHexEngine::MoHexEngine(int boardsize, MoHexPlayer& player) 00053 : BenzeneHtpEngine(boardsize), 00054 m_player(player), 00055 m_book(0), 00056 m_bookCheck(m_book), 00057 m_bookCommands(m_game, m_pe, m_book, m_bookCheck, m_player) 00058 { 00059 m_bookCommands.Register(*this); 00060 RegisterCmd("param_mohex", &MoHexEngine::MoHexParam); 00061 RegisterCmd("param_mohex_policy", &MoHexEngine::MoHexPolicyParam); 00062 RegisterCmd("mohex-save-tree", &MoHexEngine::SaveTree); 00063 RegisterCmd("mohex-save-games", &MoHexEngine::SaveGames); 00064 RegisterCmd("mohex-values", &MoHexEngine::Values); 00065 RegisterCmd("mohex-rave-values", &MoHexEngine::RaveValues); 00066 RegisterCmd("mohex-bounds", &MoHexEngine::Bounds); 00067 } 00068 00069 MoHexEngine::~MoHexEngine() 00070 { 00071 } 00072 00073 //---------------------------------------------------------------------------- 00074 00075 void MoHexEngine::RegisterCmd(const std::string& name, 00076 GtpCallback<MoHexEngine>::Method method) 00077 { 00078 Register(name, new GtpCallback<MoHexEngine>(this, method)); 00079 } 00080 00081 double MoHexEngine::TimeForMove(HexColor color) 00082 { 00083 if (m_player.UseTimeManagement()) 00084 return m_game.TimeRemaining(color) * 0.08; 00085 return m_player.MaxTime(); 00086 } 00087 00088 HexPoint MoHexEngine::GenMove(HexColor color, bool useGameClock) 00089 { 00090 SG_UNUSED(useGameClock); 00091 if (SwapCheck::PlaySwap(m_game, color)) 00092 return SWAP_PIECES; 00093 HexPoint bookMove = m_bookCheck.BestMove(HexState(m_game.Board(), color)); 00094 if (bookMove != INVALID_POINT) 00095 return bookMove; 00096 double maxTime = TimeForMove(color); 00097 return DoSearch(color, maxTime); 00098 } 00099 00100 HexPoint MoHexEngine::DoSearch(HexColor color, double maxTime) 00101 { 00102 HexState state(m_game.Board(), color); 00103 if (m_useParallelSolver) 00104 { 00105 PlayAndSolve ps(*m_pe.brd, *m_se.brd, m_player, m_dfpnSolver, 00106 m_dfpnPositions, m_game); 00107 return ps.GenMove(state, maxTime); 00108 } 00109 else 00110 { 00111 double score; 00112 return m_player.GenMove(state, m_game, m_pe.SyncBoard(m_game.Board()), 00113 maxTime, score); 00114 } 00115 } 00116 00117 //---------------------------------------------------------------------------- 00118 00119 void MoHexEngine::MoHexPolicyParam(HtpCommand& cmd) 00120 { 00121 HexUctPolicyConfig& config = m_player.SharedPolicy().Config(); 00122 if (cmd.NuArg() == 0) 00123 { 00124 cmd << '\n' 00125 << "pattern_check_percent " 00126 << config.pattern_check_percent << '\n' 00127 << "pattern_heuristic " 00128 << config.patternHeuristic << '\n' 00129 << "response_heuristic " 00130 << config.responseHeuristic << '\n' 00131 << "response_threshold " 00132 << config.response_threshold << '\n'; 00133 } 00134 else if (cmd.NuArg() == 2) 00135 { 00136 std::string name = cmd.Arg(0); 00137 if (name == "pattern_check_percent") 00138 config.pattern_check_percent = cmd.IntArg(1, 0, 100); 00139 else if (name == "pattern_heuristic") 00140 config.patternHeuristic = cmd.BoolArg(1); 00141 else if (name == "response_heuristic") 00142 config.responseHeuristic = cmd.BoolArg(1); 00143 else if (name == "response_threshold") 00144 config.response_threshold = cmd.SizeTypeArg(1, 0); 00145 else 00146 throw HtpFailure("Unknown option!"); 00147 } 00148 else 00149 throw HtpFailure("Expected 0 or 2 arguments!"); 00150 } 00151 00152 void MoHexEngine::MoHexParam(HtpCommand& cmd) 00153 { 00154 HexUctSearch& search = m_player.Search(); 00155 00156 if (cmd.NuArg() == 0) 00157 { 00158 cmd << '\n' 00159 << "[bool] backup_ice_info " 00160 << m_player.BackupIceInfo() << '\n' 00161 << "[bool] lock_free " 00162 << search.LockFree() << '\n' 00163 << "[bool] keep_games " 00164 << search.KeepGames() << '\n' 00165 << "[bool] perform_pre_search " 00166 << m_player.PerformPreSearch() << '\n' 00167 << "[bool] ponder " 00168 << m_player.Ponder() << '\n' 00169 << "[bool] reuse_subtree " 00170 << m_player.ReuseSubtree() << '\n' 00171 << "[bool] search_singleton " 00172 << m_player.SearchSingleton() << '\n' 00173 << "[bool] use_livegfx " 00174 << search.LiveGfx() << '\n' 00175 << "[bool] use_parallel_solver " 00176 << m_useParallelSolver << '\n' 00177 << "[bool] use_rave " 00178 << search.Rave() << '\n' 00179 << "[bool] use_time_management " 00180 << m_player.UseTimeManagement() << '\n' 00181 << "[bool] weight_rave_updates " 00182 << search.WeightRaveUpdates() << '\n' 00183 << "[bool] virtual_loss " 00184 << search.VirtualLoss() << '\n' 00185 << "[string] bias_term " 00186 << search.BiasTermConstant() << '\n' 00187 << "[string] expand_threshold " 00188 << search.ExpandThreshold() << '\n' 00189 << "[string] knowledge_threshold " 00190 << KnowledgeThresholdToString(search.KnowledgeThreshold()) << '\n' 00191 << "[string] livegfx_interval " 00192 << search.LiveGfxInterval() << '\n' 00193 << "[string] max_games " 00194 << m_player.MaxGames() << '\n' 00195 << "[string] max_memory " 00196 << search.MaxNodes() * 2 * sizeof(SgUctNode) << '\n' 00197 << "[string] max_nodes " 00198 << search.MaxNodes() << '\n' 00199 << "[string] max_time " 00200 << m_player.MaxTime() << '\n' 00201 << "[string] num_threads " 00202 << search.NumberThreads() << '\n' 00203 << "[string] playout_update_radius " 00204 << search.PlayoutUpdateRadius() << '\n' 00205 << "[string] randomize_rave_frequency " 00206 << search.RandomizeRaveFrequency() << '\n' 00207 << "[string] rave_weight_final " 00208 << search.RaveWeightFinal() << '\n' 00209 << "[string] rave_weight_initial " 00210 << search.RaveWeightInitial() << '\n' 00211 << "[string] tree_update_radius " 00212 << search.TreeUpdateRadius() << '\n'; 00213 } 00214 else if (cmd.NuArg() == 2) 00215 { 00216 std::string name = cmd.Arg(0); 00217 if (name == "backup_ice_info") 00218 m_player.SetBackupIceInfo(cmd.BoolArg(1)); 00219 else if (name == "lock_free") 00220 search.SetLockFree(cmd.BoolArg(1)); 00221 else if (name == "keep_games") 00222 search.SetKeepGames(cmd.BoolArg(1)); 00223 else if (name == "perform_pre_search") 00224 m_player.SetPerformPreSearch(cmd.BoolArg(1)); 00225 else if (name == "ponder") 00226 m_player.SetPonder(cmd.BoolArg(1)); 00227 else if (name == "use_livegfx") 00228 search.SetLiveGfx(cmd.BoolArg(1)); 00229 else if (name == "use_rave") 00230 search.SetRave(cmd.BoolArg(1)); 00231 else if (name == "randomize_rave_frequency") 00232 search.SetRandomizeRaveFrequency(cmd.IntArg(1, 0)); 00233 else if (name == "reuse_subtree") 00234 m_player.SetReuseSubtree(cmd.BoolArg(1)); 00235 else if (name == "bias_term") 00236 search.SetBiasTermConstant(cmd.FloatArg(1)); 00237 else if (name == "expand_threshold") 00238 search.SetExpandThreshold(cmd.IntArg(1, 0)); 00239 else if (name == "knowledge_threshold") 00240 search.SetKnowledgeThreshold 00241 (KnowledgeThresholdFromString(cmd.Arg(1))); 00242 else if (name == "livegfx_interval") 00243 search.SetLiveGfxInterval(cmd.IntArg(1, 0)); 00244 else if (name == "max_games") 00245 m_player.SetMaxGames(cmd.IntArg(1, 0)); 00246 else if (name == "max_memory") 00247 search.SetMaxNodes(cmd.SizeTypeArg(1, 1) / sizeof(SgUctNode) / 2); 00248 else if (name == "max_time") 00249 m_player.SetMaxTime(cmd.FloatArg(1)); 00250 else if (name == "max_nodes") 00251 search.SetMaxNodes(cmd.SizeTypeArg(1, 1)); 00252 else if (name == "num_threads") 00253 search.SetNumberThreads(cmd.IntArg(1, 0)); 00254 else if (name == "playout_update_radius") 00255 search.SetPlayoutUpdateRadius(cmd.IntArg(1, 0)); 00256 else if (name == "rave_weight_final") 00257 search.SetRaveWeightFinal(cmd.IntArg(1, 0)); 00258 else if (name == "rave_weight_initial") 00259 search.SetRaveWeightInitial(cmd.IntArg(1, 0)); 00260 else if (name == "weight_rave_updates") 00261 search.SetWeightRaveUpdates(cmd.BoolArg(1)); 00262 else if (name == "tree_update_radius") 00263 search.SetTreeUpdateRadius(cmd.IntArg(1, 0)); 00264 else if (name == "search_singleton") 00265 m_player.SetSearchSingleton(cmd.BoolArg(1)); 00266 else if (name == "use_parallel_solver") 00267 m_useParallelSolver = cmd.BoolArg(1); 00268 else if (name == "use_time_management") 00269 m_player.SetUseTimeManagement(cmd.BoolArg(1)); 00270 else if (name == "virtual_loss") 00271 search.SetVirtualLoss(cmd.BoolArg(1)); 00272 else 00273 throw HtpFailure() << "Unknown parameter: " << name; 00274 } 00275 else 00276 throw HtpFailure("Expected 0 or 2 arguments"); 00277 } 00278 00279 /** Saves the search tree from the previous search to the specified 00280 file. The optional second parameter sets the max depth to 00281 output. If not given, entire tree is saved. 00282 */ 00283 void MoHexEngine::SaveTree(HtpCommand& cmd) 00284 { 00285 HexUctSearch& search = m_player.Search(); 00286 00287 cmd.CheckNuArg(1); 00288 std::string filename = cmd.Arg(0); 00289 int maxDepth = -1; 00290 std::ofstream file(filename.c_str()); 00291 if (!file) 00292 throw HtpFailure() << "Could not open '" << filename << "'"; 00293 if (cmd.NuArg() == 2) 00294 maxDepth = cmd.IntArg(1, 0); 00295 search.SaveTree(file, maxDepth); 00296 } 00297 00298 /** Saves games from last search to a SGF. */ 00299 void MoHexEngine::SaveGames(HtpCommand& cmd) 00300 { 00301 HexUctSearch& search = m_player.Search(); 00302 cmd.CheckNuArg(1); 00303 std::string filename = cmd.Arg(0); 00304 search.SaveGames(filename); 00305 } 00306 00307 void MoHexEngine::Values(HtpCommand& cmd) 00308 { 00309 HexUctSearch& search = m_player.Search(); 00310 const SgUctTree& tree = search.Tree(); 00311 for (SgUctChildIterator it(tree, tree.Root()); it; ++it) 00312 { 00313 const SgUctNode& child = *it; 00314 SgPoint p = child.Move(); 00315 std::size_t count = child.MoveCount(); 00316 float mean = 0.0; 00317 if (count > 0) 00318 mean = child.Mean(); 00319 cmd << ' ' << static_cast<HexPoint>(p) 00320 << ' ' << std::fixed << std::setprecision(3) << mean 00321 << '@' << count; 00322 } 00323 } 00324 00325 void MoHexEngine::RaveValues(HtpCommand& cmd) 00326 { 00327 HexUctSearch& search = m_player.Search(); 00328 const SgUctTree& tree = search.Tree(); 00329 for (SgUctChildIterator it(tree, tree.Root()); it; ++it) 00330 { 00331 const SgUctNode& child = *it; 00332 SgPoint p = child.Move(); 00333 if (p == SG_PASS || ! child.HasRaveValue()) 00334 continue; 00335 cmd << ' ' << static_cast<HexPoint>(p) 00336 << ' ' << std::fixed << std::setprecision(3) << child.RaveValue(); 00337 } 00338 } 00339 00340 void MoHexEngine::Bounds(HtpCommand& cmd) 00341 { 00342 HexUctSearch& search = m_player.Search(); 00343 const SgUctTree& tree = search.Tree(); 00344 for (SgUctChildIterator it(tree, tree.Root()); it; ++it) 00345 { 00346 const SgUctNode& child = *it; 00347 SgPoint p = child.Move(); 00348 if (p == SG_PASS || ! child.HasRaveValue()) 00349 continue; 00350 float bound = search.GetBound(search.Rave(), tree.Root(), child); 00351 cmd << ' ' << static_cast<HexPoint>(p) 00352 << ' ' << std::fixed << std::setprecision(3) << bound; 00353 } 00354 } 00355 00356 //---------------------------------------------------------------------------- 00357 // Pondering 00358 00359 #if GTPENGINE_PONDER 00360 00361 void MoHexEngine::InitPonder() 00362 { 00363 SgSetUserAbort(false); 00364 } 00365 00366 void MoHexEngine::Ponder() 00367 { 00368 if (!m_player.Ponder()) 00369 return; 00370 if (!m_player.ReuseSubtree()) 00371 { 00372 LogWarning() << "Pondering requires reuse_subtree.\n"; 00373 return; 00374 } 00375 // Call genmove() after 0.2 seconds delay to avoid calls 00376 // in very short intervals between received commands 00377 boost::xtime time; 00378 boost::xtime_get(&time, boost::TIME_UTC); 00379 for (int i = 0; i < 200; ++i) 00380 { 00381 if (SgUserAbort()) 00382 return; 00383 time.nsec += 1000000; // 1 msec 00384 boost::thread::sleep(time); 00385 } 00386 LogInfo() << "MoHexEngine::Ponder: start\n"; 00387 // Search for at most 10 minutes 00388 DoSearch(m_game.Board().WhoseTurn(), 600); 00389 } 00390 00391 void MoHexEngine::StopPonder() 00392 { 00393 SgSetUserAbort(true); 00394 } 00395 00396 #endif // GTPENGINE_PONDER 00397 00398 //---------------------------------------------------------------------------- 00399