00001
00002
00003
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
00033
00034 void AddLibertiesAsMoves(const GoBoard& bd,
00035 const SgBWSet& safe,
00036 SgBWArray<int>& nuSafe)
00037 {
00038
00039
00040
00041
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)
00048 ++nu;
00049 nuSafe[*it] += nu / 2;
00050 }
00051
00052
00053 }
00054
00055
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
00067
00068
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)
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;
00095 }
00096
00097 return nuLibs >= 2;
00098 }
00099
00100
00101
00102
00103
00104
00105
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
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));
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())
00146 {
00147 if (DEBUG_SAFETY)
00148 SgDebug()
00149 << SgWritePointList(unsafe, "could not connect unsafe: ");
00150 return false;
00151 }
00152
00153
00154
00155
00156 interior = (pts & bd.AllEmpty()) - safe.Border(size);
00157
00158
00159
00160 if (maxNuOmissions == 1)
00161 {
00162 SgBlackWhite opp(SgOppBW(color));
00163 if (! GoSafetyUtil::MightMakeLife(bd, interior, safe, opp))
00164 return true;
00165 }
00166
00167
00168
00169
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;
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
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
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 }
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);
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
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
00300 if ((*it)->MaxPotEyes() > 1)
00301 return true;
00302 else if (nakadeRegion == 0)
00303 nakadeRegion = *it;
00304 else
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)
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
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;
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
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;
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
00492 else
00493 not_shared.PushBack(*it);
00494 }
00495
00496
00497 if (not_shared.MinLength(2))
00498 {
00499 miaiPair->first = not_shared[0];
00500 miaiPair->second = not_shared[1];
00501 }
00502
00503 else if (not_shared.IsLength(1))
00504 {
00505 miaiPair->first = not_shared[0];
00506 miaiPair->second = shared[0];
00507 }
00508
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
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