00001 //---------------------------------------------------------------------------- 00002 /** @file EndgameUtils.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "EndgameUtils.hpp" 00007 #include "BitsetIterator.hpp" 00008 #include "BoardUtils.hpp" 00009 #include "VCSet.hpp" 00010 #include "VCUtils.hpp" 00011 00012 using namespace benzene; 00013 00014 //---------------------------------------------------------------------------- 00015 00016 /** @page playingdeterminedstates Playing in Determined States 00017 00018 A determined state is defined as a state were one player has 00019 a winning semi/full connection. 00020 00021 In a winning state, returns key of smallest semi connection, 00022 if one exists. If no semi connection, plays move that overlaps 00023 the maximum number of full connections. 00024 00025 In a losing state, returns move overlapping the most SCs (instead 00026 of VCs) since any winning SC still remaining on our opponent's 00027 next turn will allow them to win. Thus, we want to eliminate those 00028 winning SCs that are shortest/easiest to find. It is also possible 00029 that our opponent has winning VCs and yet no winning SCs. In this 00030 case, we just perform the overlap with the VCs. 00031 00032 @bug It is possible our opponent has winning VCs that are not 00033 derived from the winning SCs in our list. Thus, we may want to 00034 consider overlapping the winning VCs as well. 00035 */ 00036 00037 /** @page computingmovestoconsider Computing the set of moves to consider 00038 00039 The set of moves to consider is defined as the mustplay minus the 00040 inferior cells minus cells that create states that are mirrors of 00041 themselves (these are losing via the strategy stealing argument) 00042 minus any cells that are rotations of other cells (if the state is 00043 a rotation of itself). This set can never be empty, because 00044 IsLostGame() detects such states and reports them as losing (these 00045 states will be handled by PlayDeterminedState()). 00046 */ 00047 00048 //---------------------------------------------------------------------------- 00049 00050 /** Local functions. */ 00051 namespace { 00052 00053 bitset_t ComputeLossesViaStrategyStealingArgument(const StoneBoard& brd, 00054 HexColor color) 00055 { 00056 bitset_t ret; 00057 if ((brd.Width() == brd.Height()) 00058 && (brd.GetPlayed(!color).count() - brd.GetPlayed(color).count() == 1)) 00059 { 00060 bitset_t mirror1 00061 = BoardUtils::Mirror(brd.Const(), brd.GetPlayed(!color)) 00062 - brd.GetPlayed(color); 00063 if (mirror1.count() == 1) 00064 ret |= mirror1; 00065 bitset_t mirror2 = 00066 BoardUtils::Mirror(brd.Const(), BoardUtils::Rotate(brd.Const(), 00067 brd.GetPlayed(!color))) 00068 - brd.GetPlayed(color); 00069 if (mirror2.count() == 1) 00070 ret |= mirror2; 00071 ret &= brd.GetEmpty(); 00072 } 00073 return ret; 00074 } 00075 00076 bitset_t RemoveRotations(const StoneBoard& brd, const bitset_t& consider) 00077 { 00078 bitset_t ret; 00079 for (BitsetIterator it(consider); it; ++it) 00080 { 00081 HexPoint rotated = BoardUtils::Rotate(brd.Const(), *it); 00082 if (!ret.test(rotated)) 00083 ret.set(*it); 00084 } 00085 return ret; 00086 } 00087 00088 bitset_t ComputeConsiderSet(const HexBoard& brd, HexColor color) 00089 { 00090 bitset_t consider = VCUtils::GetMustplay(brd, color); 00091 const InferiorCells& inf = brd.GetInferiorCells(); 00092 consider = consider - inf.Vulnerable() 00093 - inf.Reversible() 00094 - inf.Dominated() 00095 - ComputeLossesViaStrategyStealingArgument(brd.GetPosition(), color); 00096 if (brd.GetPosition().IsSelfRotation()) 00097 consider = RemoveRotations(brd.GetPosition(), consider); 00098 return consider; 00099 } 00100 00101 bitset_t StonesInProof(const HexBoard& brd, HexColor color) 00102 { 00103 return brd.GetPosition().GetPlayed(color) 00104 | brd.GetInferiorCells().DeductionSet(color); 00105 } 00106 00107 /** Priority is given to eliminating the most easily-answered 00108 moves first (i.e. dead cells require no answer, answering 00109 vulnerable plays only requires knowledge of local adjacencies, 00110 etc.) */ 00111 void TightenMoveBitset(bitset_t& moveBitset, const InferiorCells& inf) 00112 { 00113 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Dead()); 00114 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Vulnerable()); 00115 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Captured(BLACK)); 00116 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Captured(WHITE)); 00117 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Reversible()); 00118 BitsetUtil::SubtractIfLeavesAny(moveBitset, inf.Dominated()); 00119 HexAssert(moveBitset.any()); 00120 } 00121 00122 /** Intersects as many of the smallest connections as possible. Then, 00123 subject to that restriction, tries to be a non-inferior move (using 00124 the member variables), and then to overlap as many other connections 00125 as possible. */ 00126 HexPoint MostOverlappingMove(const std::vector<VC>& VClist, 00127 const InferiorCells& inf) 00128 { 00129 bitset_t intersectSmallest; 00130 intersectSmallest.flip(); 00131 00132 // compute intersection of smallest until next one makes it empty 00133 std::vector<VC>::const_iterator it; 00134 for (it = VClist.begin(); it != VClist.end(); ++it) 00135 { 00136 bitset_t carrier = it->carrier(); 00137 if ((carrier & intersectSmallest).none()) 00138 break; 00139 intersectSmallest &= carrier; 00140 } 00141 LogFine() << "Intersection of smallest set is:\n" 00142 << HexPointUtil::ToString(intersectSmallest) << '\n'; 00143 00144 // remove as many inferior moves as possible from this intersection 00145 TightenMoveBitset(intersectSmallest, inf); 00146 00147 LogFine() << "After elimination of inferior cells:\n" 00148 << HexPointUtil::ToString(intersectSmallest) << '\n'; 00149 00150 // determine which of the remaining cells performs best with 00151 // regards to other connections 00152 int numHits[BITSETSIZE]; 00153 memset(numHits, 0, sizeof(numHits)); 00154 for (it = VClist.begin(); it != VClist.end(); ++it) 00155 { 00156 bitset_t carrier = it->carrier(); 00157 for (int i = 0; i < BITSETSIZE; i++) 00158 if (intersectSmallest.test(i) && carrier.test(i)) 00159 numHits[i]++; 00160 } 00161 00162 int curBestMove = -1; 00163 int curMostHits = 0; 00164 for (int i = 0; i < BITSETSIZE; i++) 00165 { 00166 if (numHits[i] > curMostHits) 00167 { 00168 HexAssert(intersectSmallest.test(i)); 00169 curMostHits = numHits[i]; 00170 curBestMove = i; 00171 } 00172 } 00173 00174 if (curMostHits == 0) 00175 LogWarning() << "curMostHits == 0!!\n"; 00176 HexAssert(curMostHits > 0); 00177 return (HexPoint)curBestMove; 00178 } 00179 00180 /** Returns best winning move. */ 00181 HexPoint PlayWonGame(const HexBoard& brd, HexColor color) 00182 { 00183 HexAssert(EndgameUtils::IsWonGame(brd, color)); 00184 00185 // If we have a winning SC, then play in the key of the smallest one 00186 VC winningVC; 00187 if (brd.Cons(color).SmallestVC(HexPointUtil::colorEdge1(color), 00188 HexPointUtil::colorEdge2(color), 00189 VC::SEMI, winningVC)) 00190 { 00191 LogInfo() << "Winning SC.\n"; 00192 return winningVC.key(); 00193 } 00194 00195 // If instead we have a winning VC, then play best move in its carrier set 00196 if (brd.Cons(color).Exists(HexPointUtil::colorEdge1(color), 00197 HexPointUtil::colorEdge2(color), 00198 VC::FULL)) 00199 { 00200 LogFine() << "Winning VC.\n"; 00201 std::vector<VC> vcs; 00202 brd.Cons(color).VCs(HexPointUtil::colorEdge1(color), 00203 HexPointUtil::colorEdge2(color), 00204 VC::FULL, vcs); 00205 return MostOverlappingMove(vcs, brd.GetInferiorCells()); 00206 } 00207 // Should never get here! 00208 HexAssert(false); 00209 return INVALID_POINT; 00210 } 00211 00212 /** Returns most blocking (ie, the "best") losing move. */ 00213 HexPoint PlayLostGame(const HexBoard& brd, HexColor color) 00214 { 00215 HexAssert(EndgameUtils::IsLostGame(brd, color)); 00216 00217 // Determine if color's opponent has guaranteed win 00218 HexColor other = !color; 00219 HexPoint otheredge1 = HexPointUtil::colorEdge1(other); 00220 HexPoint otheredge2 = HexPointUtil::colorEdge2(other); 00221 00222 LogInfo() << "Opponent has won; playing most blocking move.\n"; 00223 00224 /** Uses semi-connections. 00225 @see @ref playingdeterminedstates 00226 */ 00227 bool connected = brd.Cons(other).Exists(otheredge1, otheredge2, VC::SEMI); 00228 std::vector<VC> vcs; 00229 brd.Cons(other).VCs(otheredge1, otheredge2, 00230 ((connected) ? VC::SEMI : VC::FULL), vcs); 00231 00232 return MostOverlappingMove(vcs, brd.GetInferiorCells()); 00233 } 00234 00235 } // anonymous namespace 00236 00237 //---------------------------------------------------------------------------- 00238 00239 bool EndgameUtils::IsWonGame(const HexBoard& brd, HexColor color, 00240 bitset_t& proof) 00241 { 00242 if (brd.GetGroups().GetWinner() == color) 00243 { 00244 proof = StonesInProof(brd, color); 00245 return true; 00246 } 00247 VC vc; 00248 const HexPoint edge1 = HexPointUtil::colorEdge1(color); 00249 const HexPoint edge2 = HexPointUtil::colorEdge2(color); 00250 if (brd.Cons(color).SmallestVC(edge1, edge2, VC::SEMI, vc)) 00251 { 00252 proof = vc.carrier() | StonesInProof(brd, color); 00253 return true; 00254 } 00255 if (brd.Cons(color).SmallestVC(edge1, edge2, VC::FULL, vc)) 00256 { 00257 proof = vc.carrier() | StonesInProof(brd, color); 00258 return true; 00259 } 00260 return false; 00261 } 00262 00263 bool EndgameUtils::IsLostGame(const HexBoard& brd, HexColor color, 00264 bitset_t& proof) 00265 { 00266 if (brd.GetGroups().GetWinner() == !color) 00267 { 00268 proof = StonesInProof(brd, !color); 00269 return true; 00270 } 00271 VC vc; 00272 if (brd.Cons(!color).SmallestVC(HexPointUtil::colorEdge1(!color), 00273 HexPointUtil::colorEdge2(!color), 00274 VC::FULL, vc)) 00275 { 00276 proof = vc.carrier() | StonesInProof(brd, !color); 00277 return true; 00278 } 00279 if (ComputeConsiderSet(brd, color).none()) 00280 { 00281 proof = brd.GetPosition().GetEmpty() | StonesInProof(brd, !color); 00282 return true; 00283 } 00284 return false; 00285 } 00286 00287 bool EndgameUtils::IsDeterminedState(const HexBoard& brd, HexColor color, 00288 HexEval& score, bitset_t& proof) 00289 { 00290 score = 0; 00291 if (IsWonGame(brd, color, proof)) 00292 { 00293 score = IMMEDIATE_WIN; 00294 return true; 00295 } 00296 if (IsLostGame(brd, color, proof)) 00297 { 00298 score = IMMEDIATE_LOSS; 00299 return true; 00300 } 00301 return false; 00302 } 00303 00304 HexPoint EndgameUtils::PlayDeterminedState(const HexBoard& brd, HexColor color) 00305 00306 { 00307 HexAssert(HexColorUtil::isBlackWhite(color)); 00308 HexAssert(IsDeterminedState(brd, color)); 00309 HexAssert(!brd.GetGroups().IsGameOver()); 00310 00311 if (IsWonGame(brd, color)) 00312 return PlayWonGame(brd, color); 00313 00314 HexAssert(IsLostGame(brd, color)); 00315 return PlayLostGame(brd, color); 00316 } 00317 00318 bitset_t EndgameUtils::MovesToConsider(const HexBoard& brd, HexColor color) 00319 { 00320 HexAssert(HexColorUtil::isBlackWhite(color)); 00321 HexAssert(!IsDeterminedState(brd, color)); 00322 00323 bitset_t consider = ComputeConsiderSet(brd, color); 00324 HexAssert(consider.any()); 00325 00326 LogFine() << "Moves to consider for " << color << ":" 00327 << brd.Write(consider) << '\n'; 00328 return consider; 00329 } 00330 00331 //----------------------------------------------------------------------------