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