00001
00002
00003
00004
00005
00006 #include "SgSystem.h"
00007
00008 #include "SgTimer.h"
00009 #include "SgUctTreeUtil.h"
00010
00011 #include "BitsetIterator.hpp"
00012 #include "BoardUtils.hpp"
00013 #include "VCSet.hpp"
00014 #include "HexUctUtil.hpp"
00015 #include "HexUctKnowledge.hpp"
00016 #include "HexUctSearch.hpp"
00017 #include "HexUctPolicy.hpp"
00018 #include "MoHexPlayer.hpp"
00019 #include "EndgameUtils.hpp"
00020 #include "Resistance.hpp"
00021 #include "SequenceHash.hpp"
00022 #include "Time.hpp"
00023 #include "VCUtils.hpp"
00024
00025 using namespace benzene;
00026
00027
00028
00029 namespace {
00030
00031
00032 bool IsPrefixOf(const MoveSequence& a, const MoveSequence& b)
00033 {
00034 for (std::size_t i = 0; i < a.size() && i < b.size(); ++i)
00035 {
00036 if (a[i] != b[i])
00037 return false;
00038 }
00039 return true;
00040 }
00041
00042
00043 void SortConsiderSet(const bitset_t& consider, const Resistance& resist,
00044 std::vector<HexPoint>& moves)
00045 {
00046 std::vector<std::pair<HexEval, HexPoint> > mvsc;
00047 for (BitsetIterator it(consider); it; ++it)
00048 mvsc.push_back(std::make_pair(-resist.Score(*it), *it));
00049 stable_sort(mvsc.begin(), mvsc.end());
00050 moves.clear();
00051 for (std::size_t i = 0; i < mvsc.size(); ++i)
00052 moves.push_back(mvsc[i].second);
00053 }
00054
00055 }
00056
00057
00058
00059 MoHexPlayer::MoHexPlayer()
00060 : BenzenePlayer(),
00061 m_shared_policy(),
00062 m_search(new HexThreadStateFactory(&m_shared_policy),
00063 HexUctUtil::ComputeMaxNumMoves()),
00064 m_backup_ice_info(true),
00065 m_max_games(99999999),
00066 m_max_time(10),
00067 m_useTimeManagement(false),
00068 m_reuse_subtree(false),
00069 m_ponder(false),
00070 m_performPreSearch(true)
00071 {
00072 }
00073
00074 MoHexPlayer::~MoHexPlayer()
00075 {
00076 }
00077
00078 void MoHexPlayer::CopySettingsFrom(const MoHexPlayer& other)
00079 {
00080 SetBackupIceInfo(other.BackupIceInfo());
00081 Search().SetLockFree(other.Search().LockFree());
00082 Search().SetLiveGfx(other.Search().LiveGfx());
00083 Search().SetRave(other.Search().Rave());
00084 Search().SetBiasTermConstant(other.Search().BiasTermConstant());
00085 Search().SetExpandThreshold(other.Search().ExpandThreshold());
00086 Search().SetLiveGfxInterval(other.Search().LiveGfxInterval());
00087 SetMaxGames(other.MaxGames());
00088 SetMaxTime(other.MaxTime());
00089 SetPerformPreSearch(other.PerformPreSearch());
00090 SetUseTimeManagement(other.UseTimeManagement());
00091 Search().SetMaxNodes(other.Search().MaxNodes());
00092 Search().SetNumberThreads(other.Search().NumberThreads());
00093 Search().SetPlayoutUpdateRadius(other.Search().PlayoutUpdateRadius());
00094 Search().SetRandomizeRaveFrequency
00095 (other.Search().RandomizeRaveFrequency());
00096 Search().SetRaveWeightFinal(other.Search().RaveWeightFinal());
00097 Search().SetRaveWeightInitial(other.Search().RaveWeightInitial());
00098 Search().SetWeightRaveUpdates(other.Search().WeightRaveUpdates());
00099 Search().SetTreeUpdateRadius(other.Search().TreeUpdateRadius());
00100 Search().SetKnowledgeThreshold(other.Search().KnowledgeThreshold());
00101 }
00102
00103
00104
00105 HexPoint MoHexPlayer::Search(const HexState& state, const Game& game,
00106 HexBoard& brd, const bitset_t& given_to_consider,
00107 double maxTime, double& score)
00108 {
00109 HexAssert(!brd.GetGroups().IsGameOver());
00110 HexColor color = state.ToPlay();
00111
00112 SgTimer totalElapsed;
00113 PrintParameters(color, maxTime);
00114
00115
00116
00117 SgTimer timer;
00118 bitset_t consider = given_to_consider;
00119 PointSequence winningSequence;
00120 if (m_performPreSearch && PerformPreSearch(brd, color, consider,
00121 maxTime * 0.2, winningSequence))
00122 {
00123 LogInfo() << "Winning sequence found before UCT search!\n"
00124 << "Sequence: " << winningSequence[0] << '\n';
00125 score = IMMEDIATE_WIN;
00126 return winningSequence[0];
00127 }
00128 timer.Stop();
00129 LogInfo() << "Time for PreSearch: "
00130 << Time::Formatted(timer.GetTime()) << '\n';
00131
00132 maxTime -= timer.GetTime();
00133 maxTime = std::max(1.0, maxTime);
00134
00135
00136 HexUctSharedData data;
00137 data.board_width = brd.Width();
00138 data.board_height = brd.Height();
00139 data.root_to_play = color;
00140 data.game_sequence = game.History();
00141 data.root_last_move_played = LastMoveFromHistory(game.History());
00142 data.root_stones = HexUctStoneData(brd.GetPosition());
00143 data.root_consider = consider;
00144
00145
00146 SgUctTree* initTree = 0;
00147 if (m_reuse_subtree)
00148 {
00149 HexUctSharedData oldData = m_search.SharedData();
00150 initTree = TryReuseSubtree(oldData, data);
00151 if (!initTree)
00152 LogInfo() << "No subtree to reuse.\n";
00153 }
00154 m_search.SetSharedData(data);
00155
00156 brd.GetPatternState().ClearPatternCheckStats();
00157 int old_radius = brd.GetPatternState().UpdateRadius();
00158 brd.GetPatternState().SetUpdateRadius(m_search.TreeUpdateRadius());
00159
00160
00161 std::vector<SgMove> sequence;
00162 std::vector<SgMove> rootFilter;
00163 m_search.SetBoard(brd);
00164 score = m_search.Search(m_max_games, maxTime, sequence,
00165 rootFilter, initTree, 0);
00166
00167 brd.GetPatternState().SetUpdateRadius(old_radius);
00168
00169
00170 std::ostringstream os;
00171 os << '\n';
00172 #if COLLECT_PATTERN_STATISTICS
00173 os << m_shared_policy.DumpStatistics() << '\n';
00174 os << brd.DumpPatternCheckStats() << '\n';
00175 #endif
00176 os << "Elapsed Time " << Time::Formatted(totalElapsed.GetTime()) << '\n';
00177 m_search.WriteStatistics(os);
00178 os << "Score " << std::setprecision(2) << score << "\n"
00179 << "Sequence ";
00180 for (std::size_t i = 0; i < sequence.size(); i++)
00181 os << " " << HexUctUtil::MoveString(sequence[i]);
00182 os << '\n';
00183
00184 LogInfo() << os.str() << '\n';
00185
00186 #if 0
00187 if (m_save_games)
00188 {
00189 std::string filename = "uct-games.sgf";
00190 uct.SaveGames(filename);
00191 LogInfo() << "Games saved in '" << filename << "'.\n";
00192 }
00193 #endif
00194
00195
00196 if (sequence.size() > 0)
00197 return static_cast<HexPoint>(sequence[0]);
00198
00199
00200
00201
00202
00203 LogWarning() << "**** HexUctSearch returned empty sequence!\n"
00204 << "**** Returning random move!\n";
00205 return BoardUtils::RandomEmptyCell(brd.GetPosition());
00206 }
00207
00208
00209
00210 HexPoint MoHexPlayer::LastMoveFromHistory(const MoveSequence& history)
00211 {
00212 HexPoint lastMove = INVALID_POINT;
00213 if (!history.empty())
00214 {
00215 lastMove = history.back().point();
00216 if (lastMove == SWAP_PIECES)
00217 {
00218 HexAssert(history.size() == 2);
00219 lastMove = history.front().point();
00220 }
00221 }
00222 return lastMove;
00223 }
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237 bool MoHexPlayer::PerformPreSearch(HexBoard& brd, HexColor color,
00238 bitset_t& consider, float maxTime,
00239 PointSequence& winningSequence)
00240 {
00241
00242 bitset_t losing;
00243 HexColor other = !color;
00244 PointSequence seq;
00245 bool foundWin = false;
00246
00247 SgTimer elapsed;
00248 Resistance resist;
00249 resist.Evaluate(brd);
00250 std::vector<HexPoint> moves;
00251 SortConsiderSet(consider, resist, moves);
00252 for (std::size_t i = 0; i < moves.size() && !foundWin; ++i)
00253 {
00254 if (elapsed.GetTime() > maxTime)
00255 {
00256 LogInfo() << "PreSearch: max time reached "
00257 << '(' << i << '/' << moves.size() << ").\n";
00258 break;
00259 }
00260 brd.PlayMove(color, moves[i]);
00261 seq.push_back(moves[i]);
00262 if (EndgameUtils::IsLostGame(brd, other))
00263 {
00264 winningSequence = seq;
00265 foundWin = true;
00266 }
00267 else if (EndgameUtils::IsWonGame(brd, other))
00268 losing.set(moves[i]);
00269 seq.pop_back();
00270 brd.UndoMove();
00271 }
00272
00273
00274 if (foundWin)
00275 return true;
00276
00277
00278 HexAssert(!EndgameUtils::IsDeterminedState(brd, color));
00279
00280
00281 if (m_backup_ice_info)
00282 {
00283 bitset_t new_consider
00284 = EndgameUtils::MovesToConsider(brd, color) & consider;
00285
00286 if (new_consider.count() < consider.count())
00287 {
00288 consider = new_consider;
00289 LogFine() << "$$$$$$ new moves to consider $$$$$$"
00290 << brd.Write(consider) << '\n';
00291 }
00292 }
00293
00294
00295
00296
00297 if (losing.any())
00298 {
00299 if (BitsetUtil::IsSubsetOf(consider, losing))
00300 LogInfo() << "************************************\n"
00301 << " All UCT root children are losing!!\n"
00302 << "************************************\n";
00303 else
00304 {
00305 LogFine() << "Removed losing moves: " << brd.Write(losing) << '\n';
00306 consider = consider - losing;
00307 }
00308 }
00309 HexAssert(consider.any());
00310 LogInfo()<< "Moves to consider:\n" << brd.Write(consider) << '\n';
00311 return false;
00312 }
00313
00314 void MoHexPlayer::PrintParameters(HexColor color, double timeForMove)
00315 {
00316 LogInfo() << "--- MoHexPlayer::Search() ---\n"
00317 << "Color: " << color << '\n'
00318 << "MaxGames: " << m_max_games << '\n'
00319 << "NumberThreads: " << m_search.NumberThreads() << '\n'
00320 << "MaxNodes: " << m_search.MaxNodes()
00321 << " (" << sizeof(SgUctNode)*m_search.MaxNodes() << " bytes)\n"
00322 << "TimeForMove: " << timeForMove << '\n';
00323 }
00324
00325
00326
00327 SgUctTree* MoHexPlayer::TryReuseSubtree(const HexUctSharedData& oldData,
00328 HexUctSharedData& newData)
00329 {
00330
00331
00332 if (m_search.KnowledgeThreshold().empty())
00333 {
00334 LogInfo() << "ReuseSubtree: knowledge is off.\n";
00335 return 0;
00336 }
00337
00338
00339
00340 if (oldData.board_width != newData.board_width ||
00341 oldData.board_height != newData.board_height)
00342 return 0;
00343
00344 const MoveSequence& oldSequence = oldData.game_sequence;
00345 const MoveSequence& newSequence = newData.game_sequence;
00346
00347 LogInfo() << "Old: " << oldSequence << '\n';
00348 LogInfo() << "New: "<< newSequence << '\n';
00349
00350 if (oldSequence.size() > newSequence.size())
00351 {
00352 LogInfo() << "ReuseSubtree: Backtracked to an earlier state.\n";
00353 return 0;
00354 }
00355 if (!IsPrefixOf(oldSequence, newSequence))
00356 {
00357 LogInfo() << "ReuseSubtree: Not a continuation.\n";
00358 return 0;
00359 }
00360
00361 bool samePosition = (oldSequence == newSequence
00362 && oldData.root_to_play == newData.root_to_play
00363 && oldData.root_consider == newData.root_consider
00364 && oldData.root_stones == newData.root_stones);
00365
00366 if (samePosition)
00367 LogInfo() << "ReuseSubtree: in same position as last time!\n";
00368
00369
00370
00371
00372
00373 if (!samePosition)
00374 {
00375 HexUctStoneData oldStateData;
00376 hash_t hash = SequenceHash::Hash(newSequence);
00377 if (!oldData.stones.get(hash, oldStateData))
00378 {
00379 LogInfo() << "ReuseSubtree: No knowledge for state in old tree.\n";
00380 return 0;
00381 }
00382
00383
00384
00385 if (!(oldStateData == newData.root_stones))
00386 {
00387 StoneBoard brd(11);
00388 brd.SetColor(BLACK, oldStateData.black);
00389 brd.SetColor(WHITE, oldStateData.white);
00390 brd.SetPlayed(oldStateData.played);
00391 LogWarning() << "FILLIN DOES NOT MATCH\n";
00392 LogWarning() << brd << '\n';
00393 brd.StartNewGame();
00394 brd.SetColor(BLACK, newData.root_stones.black);
00395 brd.SetColor(WHITE, newData.root_stones.white);
00396 brd.SetPlayed(newData.root_stones.played);
00397 LogWarning() << brd << '\n';
00398 }
00399 HexAssert(oldStateData == newData.root_stones);
00400 }
00401
00402
00403 MoveSequence suffix;
00404 std::vector<SgMove> sequence;
00405 for (std::size_t i = oldSequence.size(); i < newSequence.size(); ++i)
00406 {
00407 if (i && newSequence[i-1].color() == newSequence[i].color())
00408 {
00409 LogInfo() << "ReuseSubtree: Colors do not alternate.\n";
00410 return 0;
00411 }
00412 suffix.push_back(newSequence[i]);
00413 sequence.push_back(newSequence[i].point());
00414 }
00415 LogInfo() << "MovesPlayed: " << suffix << '\n';
00416
00417
00418 SgUctTree& tree = m_search.GetTempTree();
00419 SgUctTreeUtil::ExtractSubtree(m_search.Tree(), tree, sequence, true, 10.0);
00420
00421 std::size_t newTreeNodes = tree.NuNodes();
00422 std::size_t oldTreeNodes = m_search.Tree().NuNodes();
00423
00424 if (oldTreeNodes > 1 && newTreeNodes > 1)
00425 {
00426
00427 std::vector<SgMove> moves;
00428 for (BitsetIterator it(newData.root_consider); it; ++it)
00429 moves.push_back(static_cast<SgMove>(*it));
00430 tree.SetChildren(0, tree.Root(), moves);
00431
00432 float reuse = static_cast<float>(newTreeNodes) / oldTreeNodes;
00433 int reusePercent = static_cast<int>(100 * reuse);
00434 LogInfo() << "MoHexPlayer: Reusing " << newTreeNodes
00435 << " nodes (" << reusePercent << "%)\n";
00436
00437 MoveSequence moveSequence = newSequence;
00438 CopyKnowledgeData(tree, tree.Root(), newData.root_to_play,
00439 moveSequence, oldData, newData);
00440 float kReuse = static_cast<float>(newData.stones.count())
00441 / oldData.stones.count();
00442 int kReusePercent = static_cast<int>(100 * kReuse);
00443 LogInfo() << "MoHexPlayer: Reusing "
00444 << newData.stones.count() << " knowledge states ("
00445 << kReusePercent << "%)\n";
00446 return &tree;
00447 }
00448 return 0;
00449 }
00450
00451 void MoHexPlayer::CopyKnowledgeData(const SgUctTree& tree,
00452 const SgUctNode& node,
00453 HexColor color, MoveSequence& sequence,
00454 const HexUctSharedData& oldData,
00455 HexUctSharedData& newData) const
00456 {
00457
00458
00459 if (sequence != oldData.game_sequence)
00460 {
00461 hash_t hash = SequenceHash::Hash(sequence);
00462 HexUctStoneData stones;
00463 if (!oldData.stones.get(hash, stones))
00464 return;
00465 newData.stones.put(hash, stones);
00466 }
00467 if (!node.HasChildren())
00468 return;
00469 for (SgUctChildIterator it(tree, node); it; ++it)
00470 {
00471 sequence.push_back(Move(color, static_cast<HexPoint>((*it).Move())));
00472 CopyKnowledgeData(tree, *it, !color, sequence, oldData, newData);
00473 sequence.pop_back();
00474 }
00475 }
00476
00477