bool GoEyeUtil::IsVitalPt(const SgPointSet& points, SgPoint p, SgBlackWhite opp, const GoBoard& bd) { // in corridors a vital point has 2 nbs, in big ones it may have 3 or 4. // but: 2 in following // example: unsettled nakade, non-flat points are all occupied by opp. // .a a is vital Point. // o. // . int numNb = bd.NumEmptyNeighbors(p) + bd.NumNeighbors(p, opp); if (numNb >= 2) { if (numNb >= 4) /* */ return true; /* */ int nu = IsTreeShape(points) ? 2 : 3; if (numNb >= nu) { if (numNb == 2 && bd.IsEmpty(p)) return IsSplitPt(p, points); else return true; } } return false; }
/** Return true, if point is on edge line and no stone is within a Manhattan distance of 4. */ bool IsEmptyEdge(const GoBoard& bd, SgPoint p) { SG_ASSERT (bd.IsEmpty(p)); SG_ASSERT (bd.Line(p) == 1); if (bd.Num8EmptyNeighbors(p) < 5) return false; const SgPoint pUp = p + bd.Up(p); SG_ASSERT(bd.Line(pUp) == 2); SG_ASSERT(bd.Pos(pUp) >= 2); // (1,1) goes to (2,2) if (bd.Num8EmptyNeighbors(pUp) < 8) return false; switch (bd.Pos(p)) { case 1: // (1,1) point case 2: // (1,2) point case 3: return IsEmptyOrInCorner(bd, p, bd.Left(p)) && IsEmptyOrInCorner(bd, p, bd.Right(p)); // assume in empty corner, 1st line is always // dominated bymove on 2nd line above default: // > 3, can test both sides easily return IsEmpty2x3Box(bd, p + 2*bd.Left(p)) && IsEmpty2x3Box(bd, p + 2*bd.Right(p)) ; } }
bool GoEyeUtil::NumberOfMoveToEye(const GoBoard& board, SgBlackWhite color, SgPoint p, int& number) { SG_ASSERT(board.IsEmpty(p)); SgBlackWhite att = SgOppBW(color); // attacker if ( board.Line(p) == 1) // corner or edge { if ( board.Num8Neighbors(p, att) > 0 ) return false; else { number = board.Num8EmptyNeighbors(p); return true; } } else // in center { if ( board.Num8Neighbors(p, att) >= 2 ) return false; else if ( board.NumNeighbors(p, att) > 0 ) return false; else // only 0 or 1 attacker point and not in NB4 { number = board.Num8EmptyNeighbors(p); return true; } } }
bool GoEyeUtil::IsSinglePointEye(const GoBoard& bd, SgPoint p, SgBlackWhite color) { SG_ASSERT(bd.IsEmpty(p)); const SgBlackWhite opp = SgOppBW(color); if (bd.HasEmptyNeighbors(p) || bd.HasNeighbors(p, opp)) return false; if (bd.Line(p) == 1) return ! (bd.HasDiagonals(p, SG_EMPTY) || bd.HasDiagonals(p, opp)); return (bd.NumDiagonals(p, SG_EMPTY) + bd.NumDiagonals(p, opp)) <= 1; }
bool GoEyeUtil::CheckInterior(const GoBoard& bd, const SgPointSet& area, SgBlackWhite opp, bool checkBlocks) { bool hasSingleNbPoint = false; int nuPoints = 0; for (SgSetIterator it(area); it; ++it) { const SgPoint p(*it); if (bd.IsEmpty(p)) { int nuNbs = 0; if (area.Contains(p + SG_NS)) ++nuNbs; if (area.Contains(p - SG_NS)) ++nuNbs; if (area.Contains(p + SG_WE)) ++nuNbs; if (area.Contains(p - SG_WE)) ++nuNbs; if (nuNbs == 1) hasSingleNbPoint = true; else if (nuNbs > 2) return false; } else if (p == bd.Anchor(p)) { if (bd.GetColor(p) != opp) // if own stones on inside: not a tree shape. return false; int nuLibs = bd.NumLiberties(p); if (nuLibs == 1) hasSingleNbPoint = true; else if (checkBlocks && nuLibs > 2) return false; } ++nuPoints; } return nuPoints == 1 || hasSingleNbPoint; }
void GoBoardCheckPerformance::CheckPerformance(const GoBoard& board, ostream& out) { const int NUM_REPETITIONS = 10000; int i; double startTime = SgTime::Get(); int sum1 = 0; for (i = 0; i < NUM_REPETITIONS; ++i) { for (SgPoint p = 0; p < SG_MAXPOINT; ++p) { if (board.IsEmpty(p)) sum1 += p; } } double endTime1 = SgTime::Get(); int sum2 = 0; for (i = 0; i < NUM_REPETITIONS; ++i) { for (SgPoint p = board.FirstBoardPoint(); p <= board.LastBoardPoint(); ++p) { if (board.IsEmpty(p)) sum2 += p; } } double endTime2 = SgTime::Get(); int sum3 = 0; for (i = 0; i < NUM_REPETITIONS; ++i) for (GoBoard::Iterator it(board); it; ++it) { if (board.IsEmpty(*it)) sum3 += *it; } double endTime3 = SgTime::Get(); int sum4 = 0; for (i = 0; i < NUM_REPETITIONS; ++i) { for (SgPoint p = 0; p < SG_MAXPOINT; ++p) { if (board.IsEmpty(p)) sum4 += p; } } double endTime4 = SgTime::Get(); int sum5 = 0; for (i = 0; i < NUM_REPETITIONS; ++i) { for (SgPoint p = board.FirstBoardPoint(); p <= board.LastBoardPoint(); ++p) { if (board.IsEmpty(p)) sum5 += p; } } double endTime5 = SgTime::Get(); SG_ASSERT(sum1 == sum2); SG_ASSERT(sum2 == sum3); SG_ASSERT(sum3 == sum4); SG_ASSERT(sum4 == sum5); double time1 = endTime1 - startTime; double time2 = endTime2 - endTime1; double time3 = endTime3 - endTime2; double time4 = endTime4 - endTime3; double time5 = endTime5 - endTime4; out << "Time1: " << time1 << " For 0..SG_MAXPOINT\n" << "Time2: " << time2 << " First/LastBoardPoint\n" << "Time3: " << time3 << " GoBoard::Iterator\n" << "Time4: " << time4 << " For 0..SG_MAXPOINT, no dependency\n" << "Time5: " << time5 << " First/LastBoardPoint, no dependency\n"; }
//---------------------------------------------------------------------------- inline bool IsEmpty2x3Box(const GoBoard& bd, SgPoint p) { SG_ASSERT (bd.Line(p) == 1); SG_ASSERT (bd.Pos(p) > 1); return bd.IsEmpty(p) && bd.Num8EmptyNeighbors(p) == 5; }
void GoEyeUtil::TestNakade(const SgPointSet& points, const GoBoard& bd, SgBlackWhite color, bool isFullyEnclosed, bool& isNakade, bool& makeNakade, bool& makeFalse, bool& maybeSeki, bool& sureSeki, SgPoint* vital) { // handles case // of more than 1 point passing vital point test. // passes back vital point if exactly one is found. // @todo handle case where vital is illegal or suicide (eye within eye?). // also test bigger, would-be-alive shapes in atari. // @todo handle seki. SG_UNUSED(makeFalse); isNakade = makeNakade = maybeSeki = sureSeki = false; SgPoint vitalP(SG_NULLPOINT); const SgBlackWhite opp = SgOppBW(color); const int nuPoints = points.Size(); SG_ASSERT(nuPoints >= 3); // don't call for smaller areas, no need, // and results in isNakade = false which is confusing. if (nuPoints == 4) // special case 4 point areas { if (IsBulkyFour(points)) { if ( isFullyEnclosed && TwoDiagonalStonesInBulkyFour(bd, points, opp) ) makeNakade = true; else isNakade = true; /* */ return; /* */ } else if ( isFullyEnclosed && points.SubsetOf(bd.AllEmpty()) && IsBentFour(points, bd.Size(), vital) ) { makeNakade = true; /* */ return; /* */ } } else if (isFullyEnclosed && nuPoints == 5) // special case 5 point areas { const GoEyeStatus status = BulkyFiveNakade(bd, points, opp); if (ProcessStatus(status, isNakade, makeNakade)) /* */ return; /* */ } else if (isFullyEnclosed && nuPoints == 6) // special case 6 point areas { if (Is2x3Area (points)) { GoEyeStatus status = Special2x3Cases(bd, points, opp); if (ProcessStatus(status, isNakade, makeNakade)) /* */ return; /* */ } } if ( isFullyEnclosed && AlmostFilledByNakade(bd, points, opp) ) { isNakade = true; /* */ return; /* */ } if ( isFullyEnclosed && ( AlmostFilledByLivingShape(bd, points, opp) || ContainsLivingShape(bd, points, opp) ) ) { // not nakade, 2 eyes /* */ return; /* */ } int nuMakeNakade = 0; int nuVitalOccupied = 0; bool hasDivider = false; // counting number of nakade fixes bug with stretched 5 pt eye // that had 3 vital pts, // one of them occupied. was classified as 'isNakade7 = 1 eye. // see sgf/ld200,#20 for (SgSetIterator it(points); it; ++it) { SgPoint p(*it); if (IsVitalPt(points, p, opp, bd)) { if (bd.IsEmpty(p)) { if (bd.IsLegal(p, opp)) { ++nuMakeNakade; vitalP = p; } else hasDivider = true; } else ++nuVitalOccupied; } } if (hasDivider) { // alive } else if (nuMakeNakade == 1) // exactly one way to make nakade here. { makeNakade = true; *vital = vitalP; } else if (nuMakeNakade > 0) isNakade = false; else if (nuVitalOccupied < 3) isNakade = true; else { maybeSeki = true; // @todo if (IsSureSekiShape(...)) sureSeki = true; } }