00001 //---------------------------------------------------------------------------- 00002 /** @file GoEyeUtil.cpp 00003 See GoEyeUtil.h */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgSystem.h" 00007 #include "GoEyeUtil.h" 00008 00009 #include "GoBoardUtil.h" 00010 #include "GoEyeCount.h" 00011 #include "SgNbIterator.h" 00012 00013 //---------------------------------------------------------------------------- 00014 namespace 00015 { 00016 00017 /** Count number of points on edge of board (Line 1) */ 00018 int NuEdgePoints(const GoBoard& bd, const SgPointSet& points) 00019 { 00020 return (points & bd.LineSet(1)).Size(); 00021 } 00022 00023 /** Recognizes 2x2 block of points. 00024 Relies on the current implementation 00025 where SgSetIterator produces set members in sorted order, 00026 such that bulky four points have values p, p+WE, p+NS, p+WE+NS */ 00027 bool IsBulkyFour(const SgPointSet& points) 00028 { 00029 SG_ASSERT(points.IsSize(4)); 00030 SgSetIterator it(points); 00031 SgPoint p1 = *it; 00032 ++it; 00033 if (*it != p1 + SG_WE) 00034 return false; 00035 ++it; 00036 if (*it != p1 + SG_NS) 00037 return false; 00038 ++it; 00039 if (*it != p1 + SG_WE + SG_NS) 00040 return false; 00041 SG_ASSERT(GoEyeUtil::DegreeCode(points) == 400); 00042 return true; 00043 } 00044 00045 bool IsTShape(const SgPointSet& block) 00046 { 00047 return GoEyeUtil::DegreeCode(block) == 1030; 00048 } 00049 00050 bool IsBulkyFive(const SgPointSet& block) 00051 { 00052 return GoEyeUtil::DegreeCode(block) == 1310; 00053 } 00054 00055 bool IsCross(const SgPointSet& block) 00056 { 00057 return GoEyeUtil::DegreeCode(block) == 10040; 00058 } 00059 00060 bool IsRabbitySix(const SgPointSet& block) 00061 { 00062 return GoEyeUtil::DegreeCode(block) == 10320; 00063 } 00064 00065 bool Is2x3Area(const SgPointSet& area) 00066 { 00067 return GoEyeUtil::DegreeCode(area) == 2400; 00068 } 00069 00070 /** Block has a shape that gives the opponent two eyes, 00071 if it gets captured by a single opponent surrounding block. 00072 @todo needs to check if all points in block 00073 are adjacent to defender's stones - to avoid bent 4, 3x2 in corner etc. */ 00074 bool IsAliveBlock(const SgPointSet& block) 00075 { 00076 const int code = GoEyeUtil::DegreeCode(block); 00077 00078 return code == 220 // stretched 4 in a row (not alive in bent four) 00079 || code == 320 // streched 5 in a row 00080 || code == 1130 // T-shape or L-plus-foot 00081 || code == 2400 // solid 2x3 block (not alive in Corner) 00082 || code == 2220 // alive 6 point - not rabbity six 00083 ; 00084 } 00085 00086 bool CheckAlwaysAlive8Code(long code) 00087 { 00088 return code == 11210 // T 00089 || code == 3110 // L with foot 00090 ; 00091 } 00092 00093 /** Block has a shape that gives the opponent two eyes, 00094 and cannot be extended into a nakade shape. 00095 @todo needs to check if all points in block 00096 are adjacent to defender's stones - to avoid bent 4, 3x2 in corner etc. */ 00097 bool IsAlwaysAliveBlock(const SgPointSet& block) 00098 { 00099 const int code = GoEyeUtil::DegreeCode(block); 00100 00101 return code == 320 // streched 5 in a row 00102 || ( code == 1130 // T-shape or L-plus-foot 00103 && CheckAlwaysAlive8Code(GoEyeUtil::DegreeCode8(block)) 00104 ) 00105 ; 00106 } 00107 00108 /** Is single block, and has one of the standard nakade shapes */ 00109 bool IsNakadeBlock(const GoBoard& bd, 00110 const SgPointSet& block) 00111 { 00112 if (! GoEyeUtil::IsNakadeShape(block)) 00113 return false; 00114 return bd.NumStones(block.PointOf()) == block.Size(); 00115 } 00116 00117 00118 /** area is all filled by stones, except for one empty point 00119 These stones are in an alive shape (two eyes for opponent). */ 00120 bool AlmostFilledByLivingShape(const GoBoard& bd, 00121 const SgPointSet& points, 00122 SgBlackWhite stoneColor) 00123 { 00124 // area must be surrounded by opponent. 00125 SG_ASSERT(points.Border(bd.Size()).SubsetOf(bd.All(SgOppBW(stoneColor)))); 00126 00127 return (points & bd.AllEmpty()).IsSize(1) 00128 && IsAliveBlock(points & bd.All(stoneColor)); 00129 } 00130 00131 /** Area contains stones in an alive shape (two eyes for opponent). 00132 It cannot be extended into a nakade shape. */ 00133 bool ContainsLivingShape(const GoBoard& bd, 00134 const SgPointSet& points, 00135 SgBlackWhite stoneColor) 00136 { 00137 // area must be surrounded by opponent. 00138 SG_ASSERT(points.Border(bd.Size()).SubsetOf(bd.All(SgOppBW(stoneColor)))); 00139 00140 return IsAlwaysAliveBlock(points & bd.All(stoneColor)); 00141 } 00142 00143 /** area is all filled by stones, except for one empty point. 00144 These stones are in a nakade shape (only one eye). */ 00145 bool AlmostFilledByNakade(const GoBoard& bd, 00146 const SgPointSet& points, 00147 SgBlackWhite stoneColor) 00148 { 00149 // area must be surrounded by opponent. 00150 SG_ASSERT(points.Border(bd.Size()).SubsetOf(bd.All(SgOppBW(stoneColor)))); 00151 00152 if ((points & bd.AllEmpty()).IsSize(1)) 00153 { 00154 return IsNakadeBlock(bd, points & bd.All(stoneColor)); 00155 } 00156 return false; 00157 } 00158 00159 /** Test for the case of a 3 stone block in a bulky 5 shape, which is not 00160 handled well by the standard heuristics. 00161 It is almost always nakade, except when one empty point is not 00162 a liberty of the block. */ 00163 GoEyeStatus BulkyFiveNakade(const GoBoard& bd, 00164 const SgPointSet& points, 00165 SgBlackWhite stoneColor) 00166 { 00167 const SgPointSet stones = points & bd.All(stoneColor); 00168 if ( IsBulkyFive(points) 00169 && stones.IsSize(3) 00170 ) 00171 { 00172 const SgPoint p = stones.PointOf(); 00173 if (bd.NumStones(p) == 3) // single block 00174 // check if both empty points adjacent to block. if yes, nakade. 00175 // if no, alive. 00176 { 00177 SG_ASSERT((points & bd.AllEmpty()).IsSize(2)); 00178 for (SgSetIterator it(points & bd.AllEmpty()); it; ++it) 00179 { 00180 if (bd.NumNeighbors(*it, stoneColor) == 0) 00181 { 00182 return EYE_ONE_AND_HALF; 00183 } 00184 } 00185 return EYE_ONE; 00186 } 00187 else // 2 blocks (2 eyes) or 3 blocks (unsettled) 00188 { 00189 return GoEyeUtil::DegreeCode(stones) == 3 ? 00190 EYE_ONE_AND_HALF : // 3x degree 0 = 3 single stones 00191 EYE_TWO; // 2 separate blocks in bulky five are alive shapes 00192 } 00193 00194 } 00195 return EYE_UNKNOWN; 00196 } 00197 00198 /** Exceptional 2x3 areas that are not handled correctly by heuristics */ 00199 GoEyeStatus Special2x3Cases(const GoBoard& bd, 00200 const SgPointSet& points, 00201 SgBlackWhite stoneColor) 00202 { 00203 const SgPointSet stones = points & bd.All(stoneColor); 00204 const int nuStones = stones.Size(); 00205 if (nuStones > 0) 00206 { 00207 const int code = GoEyeUtil::DegreeCode(stones); 00208 const long code8 = GoEyeUtil::DegreeCode8(stones); 00209 // oo. 00210 // .oo 00211 if (code == 220 && code8 == 2200) 00212 return EYE_ONE_AND_HALF; 00213 // oo. 00214 // ..o 00215 if (code == 21 && code8 == 120) 00216 return EYE_TWO; 00217 if (NuEdgePoints(bd, points) == 3) 00218 { 00219 const SgPoint p = stones.PointOf(); 00220 switch (nuStones) 00221 { 00222 case 1: // o.. Single stone on edge can kill by diagonal move 00223 // ... 00224 if (bd.Line(p) == 1 00225 && bd.HasNeighbors(p, SgOppBW(stoneColor))) 00226 return EYE_ONE_AND_HALF; 00227 break; 00228 case 2: // o.o 1.5 eyes. B can make sente seki, W can kill 00229 // ... 00230 if ( bd.Line(p) == 1 00231 && code == 2 00232 && NuEdgePoints(bd, stones) == 2 00233 ) 00234 return EYE_ONE_AND_HALF; 00235 // o.. followup from case 1: only 1 eye 00236 // .o. 00237 if ( code == 2 00238 && code8 == 20 00239 ) 00240 { 00241 const SgPoint p1 = (stones & bd.LineSet(1)).PointOf(); 00242 if (bd.HasNeighbors(p1, SgOppBW(stoneColor))) 00243 return EYE_ONE; 00244 } 00245 break; 00246 case 3 : // o.o 1 eye 00247 // .o. 00248 if ( code == 3 00249 && code8 == 120 00250 ) 00251 { 00252 const SgPoint p1 = (stones & bd.LineSet(1)).PointOf(); 00253 if (bd.HasNeighbors(p1, SgOppBW(stoneColor))) 00254 return EYE_ONE; 00255 } 00256 break; 00257 } 00258 } 00259 } 00260 return EYE_UNKNOWN; // no special case. Default rules work, just use them. 00261 } 00262 00263 /** is p+ns, p+we locally split? */ 00264 inline bool TestDiagonal(const SgPointSet& set, SgPoint p, int ns, int we) 00265 { 00266 return ! set[p+ns+we] // connected the short way 00267 && ! ( set[p+ns-we] 00268 && set[p-we] 00269 && set[p-ns-we] 00270 && set[p-ns] 00271 && set[p-ns+we] 00272 ); // connected the long way 00273 } 00274 00275 /** is p+ns, p-ns locally split? */ 00276 inline bool TestOpposite(const SgPointSet& set, SgPoint p, int ns, int we) 00277 { 00278 return ! (set[p-ns-we] && set[p-we] && set[p+ns-we]) // connected west 00279 && ! (set[p-ns+we] && set[p+we] && set[p+ns+we]); // connected east 00280 } 00281 00282 00283 /** Check for bent four in the corner. brute force check of all eight cases. 00284 @todo rewrite using the pattern symmetry functions */ 00285 bool IsBentFour(const SgPointSet& points, int boardSize, SgPoint* vital) 00286 { 00287 SG_ASSERT(points.IsSize(4)); 00288 00289 if ( points.Contains(SgPointUtil::Pt(1, 1)) 00290 && points.Contains(SgPointUtil::Pt(2, 1)) 00291 && points.Contains(SgPointUtil::Pt(1, 2)) 00292 ) 00293 { 00294 if (points.Contains(SgPointUtil::Pt(3, 1))) 00295 { 00296 *vital = SgPointUtil::Pt(2, 1); 00297 return true; 00298 } 00299 else if (points.Contains(SgPointUtil::Pt(1, 3))) 00300 { 00301 *vital = SgPointUtil::Pt(1, 2); 00302 return true; 00303 } 00304 } 00305 if ( points.Contains(SgPointUtil::Pt(1, boardSize)) 00306 && points.Contains(SgPointUtil::Pt(2, boardSize)) 00307 && points.Contains(SgPointUtil::Pt(1, boardSize - 1)) 00308 ) 00309 { 00310 if (points.Contains(SgPointUtil::Pt(3, boardSize))) 00311 { 00312 *vital = SgPointUtil::Pt(2, boardSize); 00313 return true; 00314 } 00315 else if (points.Contains(SgPointUtil::Pt(1, boardSize - 2))) 00316 { 00317 *vital = SgPointUtil::Pt(1, boardSize - 1); 00318 return true; 00319 } 00320 } 00321 if ( points.Contains(SgPointUtil::Pt(boardSize, 1)) 00322 && points.Contains(SgPointUtil::Pt(boardSize - 1, 1)) 00323 && points.Contains(SgPointUtil::Pt(boardSize, 2)) 00324 ) 00325 { 00326 if (points.Contains(SgPointUtil::Pt(boardSize - 2, 1))) 00327 { 00328 *vital = SgPointUtil::Pt(boardSize - 1, 1); 00329 return true; 00330 } 00331 else if (points.Contains(SgPointUtil::Pt(boardSize, 3))) 00332 { 00333 *vital = SgPointUtil::Pt(boardSize, 2); 00334 return true; 00335 } 00336 } 00337 if ( points.Contains(SgPointUtil::Pt(boardSize, boardSize)) 00338 && points.Contains(SgPointUtil::Pt(boardSize - 1, boardSize)) 00339 && points.Contains(SgPointUtil::Pt(boardSize, boardSize - 1)) 00340 ) 00341 { 00342 if (points.Contains(SgPointUtil::Pt(boardSize - 2, boardSize))) 00343 { 00344 *vital = SgPointUtil::Pt(boardSize - 1, boardSize); 00345 return true; 00346 } 00347 else if (points.Contains(SgPointUtil::Pt(boardSize, boardSize - 2))) 00348 { 00349 *vital = SgPointUtil::Pt(boardSize, boardSize - 1); 00350 return true; 00351 } 00352 } 00353 00354 return false; 00355 } 00356 00357 /** The pattern with 2 diagonal stones is the only one that is unsettled. 00358 All others are nakade - only 1 eye */ 00359 bool TwoDiagonalStonesInBulkyFour(const GoBoard& bd, 00360 const SgPointSet& points, 00361 SgBlackWhite stoneColor) 00362 { 00363 // bulky four area must be surrounded by opponent. 00364 SG_ASSERT(points.Border(bd.Size()).SubsetOf(bd.All(SgOppBW(stoneColor)))); 00365 00366 if ((points & bd.All(stoneColor)).IsSize(2)) 00367 { 00368 //2 stones, 2 empty 00369 SG_ASSERT((points & bd.AllEmpty()).IsSize(2)); 00370 00371 const SgPoint p = points.PointOf(); 00372 if (bd.IsEmpty(p)) 00373 return bd.NumNeighbors(p, stoneColor) == 2; 00374 else 00375 return bd.NumEmptyNeighbors(p) == 2; 00376 } 00377 return false; 00378 } 00379 00380 inline bool ProcessStatus(GoEyeStatus status, 00381 bool& isNakade, 00382 bool& makeNakade) 00383 { 00384 if (status == EYE_ONE) 00385 isNakade = true; 00386 else if (status == EYE_ONE_AND_HALF) 00387 makeNakade = true; 00388 return status != EYE_UNKNOWN; 00389 } 00390 } // namespace 00391 //---------------------------------------------------------------------------- 00392 00393 int GoEyeUtil::DegreeCode(const SgPointSet& points) 00394 { 00395 int degrees[5] = {0,0,0,0,0}; 00396 00397 for (SgSetIterator it(points); it; ++it) 00398 { 00399 const SgPoint p(*it); 00400 int nbs = 0; 00401 for (SgNb4Iterator it(p); it; ++it) 00402 { 00403 if (points.Contains(*it)) 00404 ++nbs; 00405 } 00406 ++(degrees[nbs]); 00407 } 00408 return degrees[0] 00409 + 10 * degrees[1] 00410 + 100 * degrees[2] 00411 + 1000 * degrees[3] 00412 + 10000 * degrees[4]; 00413 } 00414 00415 long GoEyeUtil::DegreeCode8(const SgPointSet& points) 00416 { 00417 int degrees[9] = {0,0,0,0,0}; 00418 00419 for (SgSetIterator it(points); it; ++it) 00420 { 00421 const SgPoint p(*it); 00422 int nbs = 0; 00423 for (SgNb8Iterator it(p); it; ++it) 00424 { 00425 if (points.Contains(*it)) 00426 ++nbs; 00427 } 00428 ++(degrees[nbs]); 00429 } 00430 return degrees[0] 00431 + 10 * degrees[1] 00432 + 100 * degrees[2] 00433 + 1000 * degrees[3] 00434 + 10000 * degrees[4] 00435 + 100000 * degrees[5] 00436 + 1000000 * degrees[6] 00437 + 10000000 * degrees[7] 00438 + 100000000 * degrees[8]; 00439 } 00440 00441 bool GoEyeUtil::IsNakadeShape(const SgPointSet& area) 00442 { 00443 switch (area.Size()) 00444 { 00445 case 1: 00446 case 2: 00447 case 3: return true; 00448 case 4: return IsBulkyFour(area) || IsTShape(area); 00449 case 5: return IsBulkyFive(area) || IsCross(area); 00450 case 6: return IsRabbitySix(area); 00451 default: // too big 00452 return false; 00453 } 00454 } 00455 00456 bool GoEyeUtil::IsSinglePointEye(const GoBoard& bd, SgPoint p, 00457 SgBlackWhite color) 00458 { 00459 SG_ASSERT(bd.IsEmpty(p)); 00460 const SgBlackWhite opp = SgOppBW(color); 00461 if (bd.HasEmptyNeighbors(p) || bd.HasNeighbors(p, opp)) 00462 return false; 00463 if (bd.Line(p) == 1) 00464 return ! (bd.HasDiagonals(p, SG_EMPTY) || bd.HasDiagonals(p, opp)); 00465 return (bd.NumDiagonals(p, SG_EMPTY) + bd.NumDiagonals(p, opp)) <= 1; 00466 } 00467 00468 bool GoEyeUtil::IsPossibleEye(const GoBoard& board, SgBlackWhite color, 00469 SgPoint p) 00470 { 00471 bool isPossibleEye = false; 00472 SG_ASSERT(board.GetColor(p) != color); 00473 const SgBlackWhite opp = SgOppBW(color); 00474 if (board.Line(p) == 1) // corner or edge 00475 { 00476 const int nuOwn = (board.Pos(p) == 1) ? 2 : 4; 00477 if ( board.Num8Neighbors(p, color) == nuOwn 00478 && board.Num8EmptyNeighbors(p) == 1 00479 ) 00480 { 00481 isPossibleEye = true; 00482 } 00483 } 00484 else // in center 00485 { 00486 // have all neighbors, and 2 diagonals, and can get a third 00487 if ( board.NumNeighbors(p, color) == 4 00488 && board.NumDiagonals(p, color) == 2 00489 && board.NumEmptyDiagonals(p) > 0 00490 ) 00491 { 00492 isPossibleEye = true; 00493 } 00494 // have 3 of 4 neighbors, can get the 4th, and have enough diagonals 00495 else if ( board.NumNeighbors(p, color) == 3 00496 && board.NumNeighbors(p, opp) == 0 00497 && board.NumDiagonals(p, color) >= 3 00498 ) 00499 { 00500 isPossibleEye = true; 00501 } 00502 } 00503 00504 return isPossibleEye; 00505 } 00506 00507 bool GoEyeUtil::NumberOfMoveToEye(const GoBoard& board, SgBlackWhite color, 00508 SgPoint p, int& number) 00509 { 00510 SG_ASSERT(board.IsEmpty(p)); 00511 SgBlackWhite att = SgOppBW(color); // attacker 00512 00513 if ( board.Line(p) == 1) // corner or edge 00514 { 00515 if ( board.Num8Neighbors(p, att) > 0 ) 00516 return false; 00517 else 00518 { 00519 number = board.Num8EmptyNeighbors(p); 00520 return true; 00521 } 00522 } 00523 else // in center 00524 { 00525 if ( board.Num8Neighbors(p, att) >= 2 ) 00526 return false; 00527 else if ( board.NumNeighbors(p, att) > 0 ) 00528 return false; 00529 else // only 0 or 1 attacker point and not in NB4 00530 { 00531 number = board.Num8EmptyNeighbors(p); 00532 return true; 00533 } 00534 } 00535 00536 } 00537 00538 bool GoEyeUtil::IsSinglePointEye2(const GoBoard& board, SgPoint p, 00539 SgBlackWhite color, SgVector<SgPoint>& eyes) 00540 { 00541 // Must be an empty point 00542 if (! board.IsColor(p, SG_EMPTY)) 00543 return false; 00544 // All adjacent neighbours must be friendly 00545 SgBoardColor opp = SgOppBW(color); 00546 for (SgNb4Iterator adj(p); adj; ++adj) 00547 { 00548 SgBoardColor adjColor = board.GetColor(*adj); 00549 if (adjColor == opp || adjColor == SG_EMPTY) 00550 return false; 00551 } 00552 // All diagonals (with up to one exception) must be friendly or an eye 00553 int baddiags = 0; 00554 int maxbaddiags = (board.Line(p) == 1 ? 0 : 1); 00555 for (SgNb4DiagIterator it(p); it; ++it) 00556 { 00557 if (board.IsColor(*it, opp)) 00558 ++baddiags; 00559 if (board.IsColor(*it, SG_EMPTY) && ! eyes.Contains(*it)) 00560 { 00561 // Assume this point is an eye and recurse 00562 eyes.PushBack(p); 00563 if (! IsSinglePointEye2(board, *it, color, eyes)) 00564 ++baddiags; 00565 eyes.PopBack(); 00566 } 00567 if (baddiags > maxbaddiags) 00568 return false; 00569 } 00570 return true; 00571 } 00572 00573 bool GoEyeUtil::IsSinglePointEye2(const GoBoard& board, SgPoint p, 00574 SgBlackWhite color) 00575 { 00576 SgVector<SgPoint> emptylist; 00577 return IsSinglePointEye2(board, p, color, emptylist); 00578 } 00579 00580 bool GoEyeUtil::NumberOfMoveToEye2(const GoBoard& board, SgBlackWhite color, 00581 SgPoint p, int& nummoves) 00582 { 00583 nummoves = 0; 00584 bool capturing = false; 00585 SgVector<SgPoint> usedpoints; 00586 usedpoints.PushBack(p); 00587 SgPointSet counted; 00588 00589 // Can never turn own stone into an eye 00590 if (board.IsColor(p, color)) 00591 return false; 00592 00593 // If opponent stone then it must be captured to make eye 00594 if (board.IsColor(p, SgOppBW(color))) 00595 { 00596 capturing = true; 00597 00598 // If it is obviously safe then it can never be an eye 00599 if (SinglePointSafe2(board, p)) // Quick, naive safety test 00600 return false; 00601 00602 for (GoBoard::LibertyIterator libit(board, p); libit; ++libit) 00603 counted.Include(*libit); 00604 } 00605 00606 // Count immediate adjacencies 00607 for (SgNb4Iterator nb(p); nb; ++nb) 00608 { 00609 SgPoint adj = *nb; 00610 00611 // Empty points must be filled 00612 if (board.IsColor(adj, SG_EMPTY)) 00613 { 00614 counted.Include(adj); 00615 } 00616 00617 // If adjacent opponent then can never be an eye 00618 else if (board.IsColor(adj, SgOppBW(color))) 00619 { 00620 if (capturing) 00621 counted.Include(adj); // must capture and then fill 00622 else 00623 return false; 00624 } 00625 } 00626 00627 // Up to one diagonal can be ignored: estimate most costly 00628 SgPoint toignore = SG_NULLPOINT; 00629 int maxcost = 0; 00630 int infcost = 1000; 00631 if (board.Line(p) > 1) 00632 { 00633 for (SgNb4DiagIterator nbd(p); nbd; ++nbd) 00634 { 00635 SgPoint diag = *nbd; 00636 int cost = 0; 00637 00638 if ( board.IsColor(diag, SG_EMPTY) 00639 && ! IsSinglePointEye2(board, diag, color, usedpoints)) 00640 { 00641 cost = 1; 00642 } 00643 00644 else if (board.IsColor(diag, SgOppBW(color))) 00645 { 00646 // quick safety test 00647 if (SinglePointSafe2(board, diag)) 00648 cost = infcost; 00649 else 00650 cost = board.NumLiberties(diag); 00651 } 00652 00653 if (cost > maxcost) 00654 { 00655 maxcost = cost; 00656 toignore = diag; 00657 } 00658 } 00659 } 00660 00661 // Now mark points that must be played to secure diagonals 00662 for (SgNb4DiagIterator nbd(p); nbd; ++nbd) 00663 { 00664 SgPoint diag = *nbd; 00665 if (diag == toignore) 00666 continue; 00667 00668 // Empty points must be filled (unless they are eyes) 00669 if ( board.IsColor(diag, SG_EMPTY) 00670 && ! IsSinglePointEye2(board, diag, color, usedpoints)) 00671 { 00672 counted.Include(diag); 00673 } 00674 00675 // Opponent stones on diagonals must be captured and filled 00676 else if (board.IsColor(diag, SgOppBW(color))) 00677 { 00678 if (SinglePointSafe2(board, diag)) 00679 return false; 00680 else 00681 { 00682 counted.Include(diag); 00683 for (GoBoard::LibertyIterator libit(board, diag); libit; 00684 ++libit) 00685 counted.Include(*libit); 00686 } 00687 } 00688 } 00689 00690 nummoves = counted.Size(); 00691 return true; 00692 } 00693 00694 int GoEyeUtil::CountSinglePointEyes2(const GoBoard& board, SgPoint p) 00695 { 00696 if (! board.Occupied(p)) 00697 return 0; 00698 00699 SgBlackWhite color = board.GetColor(p); 00700 int numeyes = 0; 00701 00702 for (GoBoard::LibertyIterator lib(board, p); lib; ++lib) 00703 { 00704 if (IsSinglePointEye2(board, *lib, color)) 00705 numeyes++; 00706 } 00707 00708 return numeyes; 00709 } 00710 00711 bool GoEyeUtil::SinglePointSafe2(const GoBoard& board, SgPoint p) 00712 { 00713 int numeyes = CountSinglePointEyes2(board, p); 00714 return numeyes >= 2; 00715 } 00716 00717 bool GoEyeUtil::IsLocalSplitPt(SgPoint p, const SgPointSet& set) 00718 { 00719 int nuNb = 0; 00720 for (SgNb4Iterator it(p); it; ++it) 00721 { 00722 if (set.Contains(*it)) 00723 { 00724 ++nuNb; 00725 if (nuNb >= 2) 00726 break; 00727 } 00728 } 00729 if (nuNb >= 2) 00730 { 00731 if (set[p - SG_NS]) 00732 { 00733 if (set[p - SG_WE] && TestDiagonal(set, p, -SG_NS, -SG_WE)) 00734 return true; 00735 if (set[p + SG_NS] && TestOpposite(set, p, SG_NS, SG_WE)) 00736 return true; 00737 if (set[p + SG_WE] && TestDiagonal(set, p, -SG_NS, +SG_WE)) 00738 return true; 00739 } 00740 if (set[p + SG_NS]) 00741 { 00742 if (set[p - SG_WE] && TestDiagonal(set, p, +SG_NS, -SG_WE)) 00743 return true; 00744 if (set[p + SG_WE] && TestDiagonal(set, p, +SG_NS, +SG_WE)) 00745 return true; 00746 } 00747 if (set[p - SG_WE] && set[p + SG_WE] 00748 && TestOpposite(set, p, SG_WE, SG_NS)) 00749 return true; 00750 } 00751 return false; // no local split found. 00752 } 00753 00754 bool GoEyeUtil::IsSplitPt(SgPoint p, const SgPointSet& points) 00755 { 00756 SG_ASSERT(points[p]); 00757 if (! IsLocalSplitPt(p, points)) 00758 return false; 00759 SgPointSet s(points); 00760 s.Exclude(p); 00761 return ! s.IsConnected(); 00762 } 00763 00764 bool GoEyeUtil::CanBecomeSinglePointEye (const GoBoard& board, SgPoint p, 00765 const SgPointSet& oppSafe) 00766 { 00767 SG_ASSERT(! oppSafe[p]); 00768 00769 for (SgNb4Iterator it(p); it; ++it) 00770 { 00771 if (oppSafe[*it]) 00772 return false; 00773 } 00774 00775 int nu = 0; 00776 for (SgNb4DiagIterator dit(p); dit; ++dit) 00777 { 00778 if (oppSafe[*dit]) 00779 { 00780 if (board.Line(p) == 1) 00781 return false; 00782 ++nu; 00783 if (nu > 1) 00784 return false; 00785 } 00786 } 00787 00788 return true; 00789 } 00790 00791 00792 void GoEyeUtil::TestNakade(const SgPointSet& points, 00793 const GoBoard& bd, 00794 SgBlackWhite color, 00795 bool isFullyEnclosed, 00796 bool& isNakade, 00797 bool& makeNakade, 00798 bool& makeFalse, 00799 bool& maybeSeki, 00800 bool& sureSeki, 00801 SgPoint* vital) 00802 { // handles case 00803 // of more than 1 point passing vital point test. 00804 // passes back vital point if exactly one is found. 00805 // @todo handle case where vital is illegal or suicide (eye within eye?). 00806 // also test bigger, would-be-alive shapes in atari. 00807 // @todo handle seki. 00808 00809 SG_UNUSED(makeFalse); 00810 isNakade = makeNakade = maybeSeki = sureSeki = false; 00811 SgPoint vitalP(SG_NULLPOINT); 00812 const SgBlackWhite opp = SgOppBW(color); 00813 const int nuPoints = points.Size(); 00814 00815 SG_ASSERT(nuPoints >= 3); // don't call for smaller areas, no need, 00816 // and results in isNakade = false which is confusing. 00817 00818 if (nuPoints == 4) // special case 4 point areas 00819 { 00820 if (IsBulkyFour(points)) 00821 { 00822 if ( isFullyEnclosed 00823 && TwoDiagonalStonesInBulkyFour(bd, points, opp) 00824 ) 00825 makeNakade = true; 00826 else 00827 isNakade = true; 00828 /* */ return; /* */ 00829 } 00830 else if ( isFullyEnclosed 00831 && points.SubsetOf(bd.AllEmpty()) 00832 && IsBentFour(points, bd.Size(), vital) 00833 ) 00834 { 00835 makeNakade = true; 00836 /* */ return; /* */ 00837 } 00838 } 00839 else if (isFullyEnclosed && nuPoints == 5) // special case 5 point areas 00840 { 00841 const GoEyeStatus status = BulkyFiveNakade(bd, points, opp); 00842 if (ProcessStatus(status, isNakade, makeNakade)) 00843 /* */ return; /* */ 00844 } 00845 else if (isFullyEnclosed && nuPoints == 6) // special case 6 point areas 00846 { 00847 if (Is2x3Area (points)) 00848 { 00849 GoEyeStatus status = Special2x3Cases(bd, points, opp); 00850 if (ProcessStatus(status, isNakade, makeNakade)) 00851 /* */ return; /* */ 00852 } 00853 } 00854 if ( isFullyEnclosed 00855 && AlmostFilledByNakade(bd, points, opp) 00856 ) 00857 { 00858 isNakade = true; 00859 /* */ return; /* */ 00860 } 00861 00862 if ( isFullyEnclosed 00863 && ( AlmostFilledByLivingShape(bd, points, opp) 00864 || ContainsLivingShape(bd, points, opp) 00865 ) 00866 ) 00867 { // not nakade, 2 eyes 00868 /* */ return; /* */ 00869 } 00870 00871 int nuMakeNakade = 0; 00872 int nuVitalOccupied = 0; 00873 bool hasDivider = false; 00874 // counting number of nakade fixes bug with stretched 5 pt eye 00875 // that had 3 vital pts, 00876 // one of them occupied. was classified as 'isNakade7 = 1 eye. 00877 // see sgf/ld200,#20 00878 for (SgSetIterator it(points); it; ++it) 00879 { 00880 SgPoint p(*it); 00881 if (IsVitalPt(points, p, opp, bd)) 00882 { 00883 if (bd.IsEmpty(p)) 00884 { 00885 if (bd.IsLegal(p, opp)) 00886 { 00887 ++nuMakeNakade; 00888 vitalP = p; 00889 } 00890 else 00891 hasDivider = true; 00892 } 00893 else 00894 ++nuVitalOccupied; 00895 } 00896 } 00897 00898 if (hasDivider) 00899 { // alive 00900 } 00901 else if (nuMakeNakade == 1) // exactly one way to make nakade here. 00902 { 00903 makeNakade = true; 00904 *vital = vitalP; 00905 } 00906 else if (nuMakeNakade > 0) 00907 isNakade = false; 00908 else if (nuVitalOccupied < 3) 00909 isNakade = true; 00910 else 00911 { 00912 maybeSeki = true; 00913 // @todo if (IsSureSekiShape(...)) sureSeki = true; 00914 } 00915 } 00916 00917 bool GoEyeUtil::IsVitalPt(const SgPointSet& points, SgPoint p, 00918 SgBlackWhite opp, 00919 const GoBoard& bd) 00920 { 00921 // in corridors a vital point has 2 nbs, in big ones it may have 3 or 4. 00922 // but: 2 in following 00923 // example: unsettled nakade, non-flat points are all occupied by opp. 00924 // .a a is vital Point. 00925 // o. 00926 // . 00927 int numNb = bd.NumEmptyNeighbors(p) + bd.NumNeighbors(p, opp); 00928 if (numNb >= 2) 00929 { 00930 if (numNb >= 4) 00931 /* */ return true; /* */ 00932 int nu = IsTreeShape(points) ? 2 : 3; 00933 if (numNb >= nu) 00934 { 00935 if (numNb == 2 && bd.IsEmpty(p)) 00936 return IsSplitPt(p, points); 00937 else 00938 return true; 00939 } 00940 } 00941 return false; 00942 } 00943 00944 bool GoEyeUtil::CheckInterior(const GoBoard& bd, const SgPointSet& area, 00945 SgBlackWhite opp, bool checkBlocks) 00946 { 00947 bool hasSingleNbPoint = false; 00948 int nuPoints = 0; 00949 for (SgSetIterator it(area); it; ++it) 00950 { 00951 const SgPoint p(*it); 00952 if (bd.IsEmpty(p)) 00953 { 00954 int nuNbs = 0; 00955 if (area.Contains(p + SG_NS)) 00956 ++nuNbs; 00957 if (area.Contains(p - SG_NS)) 00958 ++nuNbs; 00959 if (area.Contains(p + SG_WE)) 00960 ++nuNbs; 00961 if (area.Contains(p - SG_WE)) 00962 ++nuNbs; 00963 if (nuNbs == 1) 00964 hasSingleNbPoint = true; 00965 else if (nuNbs > 2) 00966 return false; 00967 } 00968 else if (p == bd.Anchor(p)) 00969 { 00970 if (bd.GetColor(p) != opp) 00971 // if own stones on inside: not a tree shape. 00972 return false; 00973 int nuLibs = bd.NumLiberties(p); 00974 if (nuLibs == 1) 00975 hasSingleNbPoint = true; 00976 else if (checkBlocks && nuLibs > 2) 00977 return false; 00978 } 00979 ++nuPoints; 00980 } 00981 return nuPoints == 1 || hasSingleNbPoint; 00982 } 00983 00984 bool GoEyeUtil::IsTreeShape(const SgPointSet& area) 00985 { 00986 for (SgSetIterator it(area); it; ++it) 00987 { 00988 const SgPoint p(*it); 00989 if ( area.Contains(p + SG_NS) 00990 && area.Contains(p + SG_WE) 00991 && area.Contains(p + SG_NS + SG_WE) 00992 ) 00993 return false; 00994 } 00995 return true; 00996 }