Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

GoEyeUtil.cpp

Go to the documentation of this file.
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 }


17 Jun 2010 Doxygen 1.4.7