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