Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

MoHexPlayer.cpp

Go to the documentation of this file.
00001 //----------------------------------------------------------------------------
00002 /** @file MoHexPlayer.cpp
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 /** Returns true if one is a prefix of the other. */
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     // Do presearch and abort if win found. Allow it to take 20% of
00116     // total time.
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     // Create the initial state data
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     // Reuse the old subtree
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     // Do the search
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     // Output stats
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     // Return move recommended by HexUctSearch
00196     if (sequence.size() > 0) 
00197         return static_cast<HexPoint>(sequence[0]);
00198 
00199     // It is possible that HexUctSearch did only 1 simulation (probably
00200     // because it ran out of time to do more); in this case, the move
00201     // sequence is empty and so we give a warning and return a random
00202     // move.
00203     LogWarning() << "**** HexUctSearch returned empty sequence!\n"
00204          << "**** Returning random move!\n";
00205     return BoardUtils::RandomEmptyCell(brd.GetPosition());
00206 }
00207 
00208 /** Returns INVALID_POINT if history is empty, otherwise last move
00209     played to the board, ie, skips swap move. */
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 /** Does a 1-ply search.
00226 
00227     For each move in the consider set, if the move is a win, returns
00228     true and the move. If the move is a loss, prune it out of the
00229     consider set if there are non-losing moves in the consider set.
00230     If all moves are losing, perform no pruning, search will resist.
00231 
00232     Returns true if there is a win, false otherwise. 
00233 
00234     @todo Is it true that MoHex will resist in the strongest way
00235     possible?
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)) // Check for winning move
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     // Abort if we found a one-move win
00274     if (foundWin)
00275         return true;
00276 
00277     // Backing up cannot cause this to happen, right? 
00278     HexAssert(!EndgameUtils::IsDeterminedState(brd, color));
00279 
00280     // Use the backed-up ice info to shrink the moves to consider
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     // Subtract any losing moves from the set we consider, unless all of them
00295     // are losing (in which case UCT search will find which one resists the
00296     // loss well).
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 /** Extracts relevant portion of old tree for use in upcoming search.
00326     Returns valid pointer to new tree on success, 0 on failure. */
00327 SgUctTree* MoHexPlayer::TryReuseSubtree(const HexUctSharedData& oldData,
00328                                         HexUctSharedData& newData)
00329 {
00330     // Must have knowledge on to reuse subtrees, since root has fillin
00331     // knowledge which affects the tree below.
00332     if (m_search.KnowledgeThreshold().empty())
00333     {
00334         LogInfo() << "ReuseSubtree: knowledge is off.\n";
00335         return 0;
00336     }
00337 
00338     // Board size must be the same. This also catches the case where
00339     // no previous search has been performed.
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     // If no old knowledge for the new root in the old tree, then we
00370     // cannot reuse the tree (since the root is given its knowledge
00371     // and using this knowledge would require pruning the trees under
00372     // the root's children). 
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         // Check that the old knowledge is equal to the new knowledge
00384         // in the would-be root node.
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     // Ensure alternating colors and extract suffix
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     // Extract the tree
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         // Fix root's children to be those in the consider set
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     // This check will fail in the root if we are reusing the
00458     // entire tree, so only do it when not in the root.
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 //----------------------------------------------------------------------------


6 Jan 2011 Doxygen 1.6.3