00001 //---------------------------------------------------------------------------- 00002 /** @file PerfectPlayer.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgSystem.h" 00007 #include "SgRandom.h" 00008 #include "BitsetIterator.hpp" 00009 #include "PerfectPlayer.hpp" 00010 00011 using namespace benzene; 00012 00013 //---------------------------------------------------------------------------- 00014 00015 namespace 00016 { 00017 00018 HexPoint RandomBit(const bitset_t& bs, SgRandom& random) 00019 { 00020 HexAssert(bs.any()); 00021 int index = random.Int(bs.count()); 00022 for (BitsetIterator it(bs); it; ++it) 00023 { 00024 if (index == 0) 00025 return *it; 00026 --index; 00027 } 00028 HexAssert(false); 00029 return static_cast<HexPoint>(BitsetUtil::FirstSetBit(bs)); 00030 } 00031 00032 } 00033 00034 //---------------------------------------------------------------------------- 00035 00036 PerfectPlayer::PerfectPlayer(DfpnSolver& solver, DfpnStates& positions) 00037 : BenzenePlayer(), 00038 m_solver(solver), 00039 m_positions(positions), 00040 m_maxTime(60.0), 00041 m_propagateBackwards(true) 00042 { 00043 } 00044 00045 PerfectPlayer::~PerfectPlayer() 00046 { 00047 } 00048 00049 //---------------------------------------------------------------------------- 00050 00051 HexPoint PerfectPlayer::Search(const HexState& state, const Game& game, 00052 HexBoard& brd, const bitset_t& consider, 00053 double maxTime, double& score) 00054 { 00055 SG_UNUSED(consider); 00056 SG_UNUSED(score); 00057 LogInfo() << "PerfectPlayer::Search()\n"; 00058 LogInfo() << state.Position() << '\n'; 00059 if (FillinCausedWin()) 00060 { 00061 LogInfo() << "PerfectPlayer: Fillin caused win!\n"; 00062 HexColor color = state.ToPlay(); 00063 brd.GetPosition().SetPosition(state.Position()); 00064 brd.ComputeAll(color); 00065 const InferiorCells& inf = brd.GetInferiorCells(); 00066 if (m_fillinWinner == color && inf.Captured(color).any()) 00067 { 00068 LogInfo() << "PerfectPlayer: Playing into our fillin...\n"; 00069 return RandomBit(inf.Captured(color), SgRandom::Global()); 00070 } 00071 else if (m_fillinWinner == !color && inf.Captured(!color).any()) 00072 { 00073 LogInfo() << "PerfectPlayer: Playing into opponent fillin...\n"; 00074 return RandomBit(inf.Captured(color), SgRandom::Global()); 00075 } 00076 LogInfo() << "PerfectPlayer: Playing random empty cell...\n"; 00077 return RandomBit(state.Position().GetEmpty(), SgRandom::Global()); 00078 } 00079 double timeForMove = std::min(m_maxTime, maxTime); 00080 LogInfo() << "TimeForMove=" << timeForMove << '\n'; 00081 double oldTimelimit = m_solver.Timelimit(); 00082 m_solver.SetTimelimit(timeForMove); 00083 PointSequence pv; 00084 HexColor winner = m_solver.StartSearch(state, brd, m_positions, pv); 00085 m_solver.SetTimelimit(oldTimelimit); 00086 if (m_propagateBackwards) 00087 m_solver.PropagateBackwards(game, m_positions); 00088 // Return winning/best losing move. 00089 if (winner != EMPTY && !pv.empty()) 00090 return pv[0]; 00091 // NOTE: This can happen if the current state is a terminal state 00092 // under a rotation, but it is not detected as terminal here 00093 // (there can be slight differences in vcs between rotated 00094 // states). In this case, DFPN does not have a move stored and we 00095 // are stuck if we continue to use the current set of stored 00096 // positions. So we create a new empty DfpnPositions object with a 00097 // small hashtable to use for this (hopefully really small) search 00098 // to find the winning move. 00099 if (winner != EMPTY && pv.empty()) 00100 { 00101 boost::scoped_ptr<DfpnHashTable> myTable(new DfpnHashTable(10)); 00102 boost::scoped_ptr<DfpnDB> myDB(0); 00103 SolverDBParameters myParam; 00104 DfpnStates myStates(myTable, myDB, myParam); 00105 LogInfo() << "PerfectPlayer: Found win with empty pv at this state:\n"; 00106 LogInfo() << brd << '\n'; 00107 LogInfo() << "PerfectPlayer: Re-solving with temporary hash table.\n"; 00108 winner = m_solver.StartSearch(state, brd, myStates, pv); 00109 HexAssert(winner != EMPTY); 00110 HexAssert(!pv.empty()); 00111 return pv[0]; 00112 } 00113 // Didn't prove it, find non-losing move with most work. 00114 DfpnData data; 00115 if (!m_positions.Get(state, data)) 00116 throw BenzeneException("Root node not in database!?!"); 00117 std::size_t maxWork = 0; 00118 HexPoint bestMove = pv[0]; 00119 HexState myState(state); 00120 for (std::size_t i = 0; i < data.m_children.Size(); ++i) 00121 { 00122 data.m_children.PlayMove(i, myState); 00123 DfpnData child; 00124 if (m_positions.Get(myState, child)) 00125 { 00126 // We're assuming no children are losing (ie, no move is winning), 00127 // so we just need to avoid losing moves (winning children). 00128 if (!child.m_bounds.IsWinning() && child.m_work > maxWork) 00129 { 00130 bestMove = data.m_children.FirstMove(i); 00131 maxWork = child.m_work; 00132 } 00133 } 00134 data.m_children.UndoMove(i, myState); 00135 } 00136 LogInfo() << "bestMove=" << bestMove << " (" << maxWork << ")\n"; 00137 return bestMove; 00138 } 00139 00140 //----------------------------------------------------------------------------