00001 //---------------------------------------------------------------------------- 00002 /** @file GoSafetyUtil.cpp 00003 See GoSafetyUtil.h. 00004 */ 00005 //---------------------------------------------------------------------------- 00006 00007 #include "SgSystem.h" 00008 #include "GoSafetyUtil.h" 00009 00010 #include "GoBlock.h" 00011 #include "GoBoard.h" 00012 #include "GoBoardUtil.h" 00013 #include "GoEyeUtil.h" 00014 #include "GoRegion.h" 00015 #include "GoRegionBoard.h" 00016 #include "SgBWSet.h" 00017 #include "SgVector.h" 00018 #include "SgPointSet.h" 00019 #include "SgPointSetUtil.h" 00020 #include "SgWrite.h" 00021 00022 //---------------------------------------------------------------------------- 00023 00024 namespace { 00025 const bool DEBUG_SAFETY = false; 00026 const bool DEBUG_MIGHT_MAKE_LIFE = false; 00027 const bool DEBUG_EXTENDED_MIGHT_MAKE_LIFE = false; 00028 00029 /** find 2 libs which would connect block to safe. 00030 if found, update libs and safe to indicate that the block is safe now: 00031 add block to safe, add block libs to libs, remove the two libs. 00032 */ 00033 bool Find2Connections(const GoBoard& bd, SgPoint block, SgPointSet* libs, 00034 SgPointSet* usedLibs, SgPointSet* safe) 00035 { 00036 SG_ASSERT(libs->Disjoint(*usedLibs)); 00037 00038 int nuLibs(0); 00039 SgVector<SgPoint> blockLibs; 00040 for (GoBoard::LibertyIterator it(bd, block); it; ++it) 00041 { 00042 if (libs->Contains(*it)) 00043 { 00044 blockLibs.PushBack(*it); 00045 ++nuLibs; 00046 if (nuLibs >= 2) 00047 break; 00048 } 00049 } 00050 if (nuLibs >= 2) // found it! 00051 { 00052 for (GoBoard::StoneIterator it(bd, block); it; ++it) 00053 safe->Include(*it); 00054 for (GoBoard::LibertyIterator it(bd, block); it; ++it) 00055 libs->Include(*it); 00056 for (SgVectorIterator<SgPoint> it(blockLibs); it; ++it) 00057 usedLibs->Include(*it); 00058 *libs -= *usedLibs; // can never re-use such a lib. 00059 } 00060 00061 return nuLibs >= 2; 00062 } 00063 00064 /** Find connections for unsafe boundary and interior stones, 00065 and interior empty points. 00066 00067 Can omit maxNuOmissions (0 or 1) empty points from checking. 00068 maxNuOmissions = 1 if testing whether opponent can make 2 eyes here, 00069 0 otherwise. Returns bool whether connections were found. 00070 */ 00071 bool Find2ConnectionsForAll(const GoBoard& bd, const SgPointSet& pts, 00072 const SgPointSet& inSafe, SgBlackWhite color, 00073 int maxNuOmissions = 0) 00074 { 00075 if (DEBUG_SAFETY) 00076 SgDebug() << "Find2ConnectionsForAll " << pts 00077 << "safe points - input: " << inSafe; 00078 SgPointSet safe(inSafe); 00079 SgVector<SgPoint> unsafe; 00080 const int size = bd.Size(); 00081 GoSafetyUtil::ReduceToAnchors(bd, pts.Border(size) - safe, &unsafe); 00082 // AR sort by # empty nbs in pts 00083 00084 if (DEBUG_SAFETY) 00085 SgDebug() << SgWritePointList(unsafe, "unsafe anchors: "); 00086 00087 SgPointSet libs = pts & bd.AllEmpty() & safe.Border(size); 00088 SgPointSet interior = pts - libs; 00089 interior -= bd.All(SgOppBW(color)); // remove opp. stones. 00090 SgVector<SgPoint> unsafeInterior; 00091 GoSafetyUtil::ReduceToAnchors(bd, interior & bd.All(color), 00092 &unsafeInterior); 00093 unsafe.Concat(&unsafeInterior); 00094 00095 SgPointSet usedLibs; 00096 bool change = true; 00097 while (change && unsafe.NonEmpty() && libs.MinSetSize(2)) 00098 { 00099 SgVector<SgPoint> newSafe; 00100 for (SgVectorIterator<SgPoint> it(unsafe); it; ++it) 00101 if (Find2Connections(bd, *it, &libs, &usedLibs, &safe)) 00102 { 00103 newSafe.PushBack(*it); 00104 } 00105 00106 unsafe.Exclude(newSafe); 00107 change = newSafe.NonEmpty(); 00108 } 00109 00110 if (unsafe.NonEmpty()) // could not connect everything. 00111 { 00112 if (DEBUG_SAFETY) 00113 SgDebug() 00114 << SgWritePointList(unsafe, "could not connect unsafe: "); 00115 return false; 00116 } 00117 // now we know all blocks are safe. try to prove territory safe, too. 00118 // AR if terr. safety fails, still declare blocks safe, but put 00119 // miai strategy commitment on region. 00120 00121 interior = (pts & bd.AllEmpty()) - safe.Border(size); 00122 // new safe set after Find2Connections. 00123 00124 // try to prove opp. can't live inside. 00125 if (maxNuOmissions == 1) 00126 { 00127 SgBlackWhite opp(SgOppBW(color)); 00128 if (! GoSafetyUtil::MightMakeLife(bd, interior, safe, opp)) 00129 /* */ return true; /* */ 00130 } 00131 00132 // now try to find miai-paths to remaining interior empty points 00133 // AR shortcut failure if maxNuOmissions = 0 and opp has eye: 00134 // then this will never find anything. 00135 00136 for (SgSetIterator it(interior); it; ++it) 00137 { 00138 if (! GoSafetyUtil::Find2Libs(*it, &libs)) 00139 { 00140 if (--maxNuOmissions < 0) 00141 return false; 00142 } 00143 } 00144 00145 return true; 00146 } 00147 00148 void TestLiberty(SgPoint lib, const SgPointSet& libs, 00149 SgVector<SgPoint>* foundLibs, 00150 int* nuLibs) 00151 { 00152 if (libs.Contains(lib)) 00153 { 00154 foundLibs->PushBack(lib); 00155 ++(*nuLibs); 00156 } 00157 } 00158 00159 /** write part and total and rounded percentage of part in total */ 00160 void WriteSafeTotal(std::ostream& stream, std::string text, 00161 int partCount, int totalCount) 00162 { 00163 stream << partCount << " / " << totalCount 00164 << " (" 00165 << (partCount * 100 + totalCount / 2) / totalCount 00166 << "%) " << text << '\n'; 00167 } 00168 00169 } // namespace 00170 00171 //---------------------------------------------------------------------------- 00172 00173 void GoSafetyUtil::AddToSafe(const GoBoard& board, const SgPointSet& pts, 00174 SgBlackWhite color, SgBWSet* safe, 00175 const char* reason, int depth, bool addBoundary) 00176 { 00177 SG_UNUSED(reason); 00178 SG_UNUSED(depth); 00179 00180 (*safe)[color] |= pts; 00181 safe->AssertDisjoint(); 00182 SgPointSet empty = board.AllEmpty(); 00183 00184 const int size = board.Size(); 00185 if (addBoundary) 00186 { 00187 SgPointSet dep(pts.Border(size) - empty); // pts can be zone (open!) 00188 GoBoardUtil::ExpandToBlocks(board, dep); 00189 SG_ASSERT(dep.SubsetOf(board.All(color))); 00190 (*safe)[color] |= dep; 00191 safe->AssertDisjoint(); 00192 } 00193 00194 if (DEBUG_SAFETY) 00195 SgDebug() << "AddToSafe " << reason 00196 << " depth = " << depth << " points = " 00197 << SgWritePointSetID(pts) << '\n'; 00198 } 00199 00200 bool GoSafetyUtil::ExtendedMightMakeLife(const GoBoard& board, 00201 GoRegionBoard* regions, 00202 const SgPointSet& area, 00203 const SgPointSet& safe, 00204 SgBlackWhite color) 00205 { 00206 const GoRegion* nakadeRegion = 0; 00207 00208 if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE) 00209 SgDebug() << "ExtendedMightMakeLife for " << SgBW(color) 00210 << " area " << area << '\n'; 00211 00212 // Check if region is a nakade shape that fills all potential eye space 00213 for (SgVectorIteratorOf<GoRegion> it(regions->AllRegions(color)); 00214 it; ++it) 00215 { 00216 if ( area.SupersetOf((*it)->Points()) 00217 && area.SupersetOf((*it)->BlocksPoints()) 00218 ) 00219 { 00220 if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE) 00221 { 00222 SgDebug() << "contains region "; 00223 (*it)->WriteID(SgDebug()); 00224 SgDebug() << '\n'; 00225 } 00226 00227 if (! (*it)->ComputedFlag(GO_REGION_COMPUTED_NAKADE)) 00228 (*it)->DoComputeFlag(GO_REGION_COMPUTED_NAKADE); 00229 // sets MaxPotEyes() 00230 if ((*it)->MaxPotEyes() > 1) 00231 return true; 00232 else if (nakadeRegion == 0) 00233 nakadeRegion = *it; 00234 else // at least 2 regions - might make eyes 00235 return true; 00236 } 00237 } 00238 00239 if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE) 00240 SgDebug() << "case 2\n"; 00241 SgPointSet rest = area; 00242 if (nakadeRegion == 0) // classical case. Call previous function 00243 return GoSafetyUtil::MightMakeLife(board, area, safe, color); 00244 else 00245 { 00246 if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE) 00247 SgDebug() << "ExtendedMightMakeLife for " << area 00248 << ": inside opp region " 00249 << *nakadeRegion << '\n'; 00250 if (nakadeRegion->MaxPotEyes() <= 1) 00251 // what if 0 eyes??? Can allow 1 eye elsewhere? 00252 { 00253 rest -= nakadeRegion->Points(); 00254 rest -= nakadeRegion->BlocksPoints(); 00255 } 00256 } 00257 00258 const int size = board.Size(); 00259 rest -= safe.Border(size); 00260 rest -= board.All(color); 00261 00262 if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE) 00263 SgDebug() << "rest = " << rest << "\n"; 00264 for (SgSetIterator it(rest); it; ++it) 00265 { 00266 SgPoint p(*it); 00267 if (GoEyeUtil::CanBecomeSinglePointEye(board, p, safe)) 00268 return true; 00269 } 00270 00271 return false; 00272 } 00273 00274 SgPointSet GoSafetyUtil::FindDamePoints(const GoBoard& bd, 00275 const SgPointSet& empty, 00276 const SgBWSet& safe) 00277 { 00278 SgPointSet dame, unsurroundable; 00279 FindDameAndUnsurroundablePoints(bd, empty, safe, &dame, &unsurroundable); 00280 return dame; 00281 } 00282 00283 void GoSafetyUtil::FindDameAndUnsurroundablePoints(const GoBoard& bd, 00284 const SgPointSet& empty, 00285 const SgBWSet& safe, 00286 SgPointSet* dame, 00287 SgPointSet* unsurroundable) 00288 { 00289 SG_ASSERT(dame->IsEmpty()); 00290 SG_ASSERT(unsurroundable->IsEmpty()); 00291 const int size = bd.Size(); 00292 *unsurroundable = safe[SG_BLACK].Border(size) 00293 & safe[SG_WHITE].Border(size) 00294 & empty; 00295 SgPointSet maybeDame(*unsurroundable); 00296 SgBWSet unsafe; // must exclude these 00297 unsafe[SG_BLACK] = bd.All(SG_BLACK) - safe[SG_BLACK]; 00298 unsafe[SG_WHITE] = bd.All(SG_WHITE) - safe[SG_WHITE]; 00299 maybeDame -= unsafe[SG_BLACK].Border(size); 00300 maybeDame -= unsafe[SG_WHITE].Border(size); 00301 for (SgSetIterator it(maybeDame); it; ++it) 00302 { 00303 SgPoint p(*it); 00304 bool isDame = true; 00305 for (SgNb4Iterator it(p); it; ++it) 00306 { 00307 SgPoint nb(*it); 00308 if (empty[nb] && ! unsurroundable->Contains(nb)) 00309 { 00310 // can use unsurroundable instead of smaller set maybeDame 00311 isDame = false; 00312 break; 00313 } 00314 } 00315 if (isDame) 00316 dame->Include(p); 00317 } 00318 } 00319 00320 bool GoSafetyUtil::MightMakeLife(const GoBoard& board, 00321 const SgPointSet& area, 00322 const SgPointSet& safe, SgBlackWhite color) 00323 { 00324 const int size = board.Size(); 00325 SgPointSet eyePts = (area - safe.Border(size)) - board.All(color); 00326 if (eyePts.MaxSetSize(1)) 00327 return false; 00328 00329 if (DEBUG_MIGHT_MAKE_LIFE) 00330 SgDebug() << "GoSafetyUtil::MightMakeLife\n"; 00331 00332 SgPoint eye(SG_NULLPOINT), adjToEye(SG_NULLPOINT); 00333 for (SgSetIterator it(eyePts); it; ++it) 00334 { 00335 const SgPoint p(*it); 00336 if (GoEyeUtil::CanBecomeSinglePointEye(board, p, safe)) 00337 { 00338 if (eye == SG_NULLPOINT) 00339 { 00340 eye = p; 00341 if (DEBUG_MIGHT_MAKE_LIFE) 00342 SgDebug() << "eye = " << SgWritePoint(eye) << "\n"; 00343 } 00344 else if ( adjToEye == SG_NULLPOINT 00345 && SgPointUtil::AreAdjacent(eye, p) 00346 ) 00347 adjToEye = p; 00348 else 00349 { 00350 if (DEBUG_MIGHT_MAKE_LIFE) 00351 SgDebug() << "second eye = " << SgWritePoint(p) << "\n"; 00352 /* */ return true; /* */ 00353 } 00354 } 00355 } 00356 00357 return false; 00358 } 00359 00360 bool GoSafetyUtil::Find2Libs(SgPoint p, SgPointSet* libs) 00361 { 00362 int nuLibs = 0; 00363 SgVector<SgPoint> foundLibs; 00364 TestLiberty(p + SG_NS, *libs, &foundLibs, &nuLibs); 00365 TestLiberty(p + SG_WE, *libs, &foundLibs, &nuLibs); 00366 if (nuLibs < 2) 00367 { 00368 TestLiberty(p - SG_NS, *libs, &foundLibs, &nuLibs); 00369 if (nuLibs < 2) 00370 TestLiberty(p - SG_WE, *libs, &foundLibs, &nuLibs); 00371 } 00372 if (nuLibs >= 2) 00373 { 00374 SG_ASSERT(nuLibs == 2 && foundLibs.IsLength(2)); 00375 libs->Exclude(foundLibs.Front()); 00376 libs->Exclude(foundLibs.Back()); 00377 } 00378 00379 return nuLibs >= 2; 00380 } 00381 00382 bool GoSafetyUtil::Find2BestLibs(SgPoint p, const SgPointSet& libs, 00383 SgPointSet interior, SgMiaiPair* miaiPair) 00384 { 00385 int nuLibs = 0; 00386 SgVector<SgPoint> allLibs; // liberties of point p 00387 00388 TestLiberty(p + SG_NS, libs, &allLibs, &nuLibs); 00389 TestLiberty(p + SG_WE, libs, &allLibs, &nuLibs); 00390 TestLiberty(p - SG_NS, libs, &allLibs, &nuLibs); 00391 TestLiberty(p - SG_WE, libs, &allLibs, &nuLibs); 00392 00393 if (allLibs.MaxLength(1)) 00394 return false; 00395 else if (allLibs.IsLength(2)) 00396 { 00397 SG_ASSERT(nuLibs == 2 && allLibs.IsLength(2)); 00398 miaiPair->first = allLibs[0]; 00399 miaiPair->second = allLibs[1]; 00400 /* */ return true; /* */ 00401 } 00402 else 00403 { 00404 SgVector<SgPoint> shared, not_shared; 00405 SgPointSet others = interior; 00406 others.Exclude(p); 00407 00408 for (SgVectorIterator<SgPoint> it(allLibs); it; ++it) 00409 { 00410 bool share = false; 00411 for (SgSetIterator it2(others); it2; ++it2) 00412 { 00413 if (SgPointUtil::AreAdjacent(*it, *it2)) 00414 { 00415 share = true; 00416 break; 00417 } 00418 } 00419 if (share) 00420 shared.PushBack(*it); 00421 // this lib is shared with other interior points 00422 else 00423 not_shared.PushBack(*it); 00424 } 00425 // if has >= 2 not_shared libs, then try to find 2 original libs (not 00426 // new-created libs (because the new one might be ip points) 00427 if (not_shared.MinLength(2)) 00428 { 00429 miaiPair->first = not_shared[0]; 00430 miaiPair->second = not_shared[1]; 00431 } 00432 // if only 1 not_shared lib, use this first, then another shared lib 00433 else if (not_shared.IsLength(1)) 00434 { 00435 miaiPair->first = not_shared[0]; 00436 miaiPair->second = shared[0]; 00437 } 00438 // zero not_shared libs, then use two shared libs 00439 else 00440 { 00441 miaiPair->first = shared[0]; 00442 miaiPair->second = shared[1]; 00443 } 00444 return true; 00445 } 00446 } 00447 00448 bool GoSafetyUtil::ExtendedIsTerritory(const GoBoard& board, 00449 GoRegionBoard* regions, 00450 const SgPointSet& pts, 00451 const SgPointSet& safe, 00452 SgBlackWhite color) 00453 { 00454 SG_ASSERT(! pts.Overlaps(safe)); 00455 const int size = board.Size(); 00456 SgPointSet boundary(pts.Border(size)); 00457 if (boundary.SubsetOf(safe)) 00458 { 00459 SgBlackWhite opp = SgOppBW(color); 00460 if (! ExtendedMightMakeLife(board, regions, pts, safe, opp)) 00461 /* */ return true; /* */ 00462 } 00463 00464 return IsTerritory(board, pts, safe, color); 00465 } 00466 00467 bool GoSafetyUtil::IsTerritory(const GoBoard& board, const SgPointSet& pts, 00468 const SgPointSet& safe, SgBlackWhite color) 00469 { 00470 SG_ASSERT(! pts.Overlaps(safe)); 00471 const int size = board.Size(); 00472 SgPointSet boundary(pts.Border(size)); 00473 if (boundary.SubsetOf(safe)) 00474 { 00475 SgBlackWhite opp = SgOppBW(color); 00476 if (! GoSafetyUtil::MightMakeLife(board, pts, safe, opp)) 00477 /* */ return true; /* */ 00478 } 00479 00480 if ( boundary.SubsetOf(board.All(color)) 00481 && Find2ConnectionsForAll(board, pts, safe, color, 1) 00482 ) 00483 /* */ return true; /* */ 00484 return false; 00485 } 00486 00487 void GoSafetyUtil::ReduceToAnchors(const GoBoard& board, 00488 const SgPointSet& stones, 00489 SgVector<SgPoint>* anchors) 00490 { 00491 SG_ASSERT(anchors->IsEmpty()); 00492 for (SgSetIterator it(stones); it; ++it) 00493 { 00494 SG_ASSERT(board.Occupied(*it)); 00495 anchors->Insert(board.Anchor(*it)); 00496 } 00497 } 00498 00499 void GoSafetyUtil::WriteStatistics(const std::string& heading, 00500 const GoRegionBoard* regions, 00501 const SgBWSet* safe) 00502 { 00503 const SgPointSet allSafe = safe->Both(); 00504 int totalRegions = 0; 00505 int safeRegions = 0; 00506 int totalBlocks = 0; 00507 int safeBlocks = 0; 00508 00509 for (SgBWIterator it; it; ++it) 00510 { 00511 SgBlackWhite color(*it); 00512 for (SgVectorIteratorOf<GoRegion> it(regions->AllRegions(color)); 00513 it; ++it) 00514 { 00515 ++totalRegions; 00516 if ((*it)->Points().SubsetOf(allSafe)) 00517 ++safeRegions; 00518 } 00519 for (SgVectorIteratorOf<GoBlock> it(regions->AllBlocks(color)); 00520 it; ++it) 00521 { 00522 ++totalBlocks; 00523 if (allSafe.Overlaps((*it)->Stones())) 00524 ++safeBlocks; 00525 } 00526 } 00527 00528 const int bdSize = regions->Board().Size(); 00529 SgDebug() << heading << "\n"; 00530 WriteSafeTotal(SgDebug(), "points", allSafe.Size(), bdSize * bdSize); 00531 WriteSafeTotal(SgDebug(), "regions", safeRegions, totalRegions); 00532 WriteSafeTotal(SgDebug(), "blocks", safeBlocks, totalBlocks); 00533 } 00534 00535 00536 //----------------------------------------------------------------------------