/** List of safe points. If no color is given, safe points of both colors are listed. Arguments: benson|static [black|white]<br> Returns: number of point followed bu list of points in one line. */ void GoSafetyCommands::CmdSafe(GtpCommand& cmd) { cmd.CheckNuArgLessEqual(2); string type = cmd.Arg(0); int totalRegions = 0; SgBWSet safe = GetSafe(totalRegions, type); SgPointSet set = (cmd.NuArg() == 2 ? safe[BlackWhiteArg(cmd, 1)] : safe.Both()); cmd << set.Size(); for (SgSetIterator it(set); it; ++it) cmd << ' ' << SgWritePoint(*it); }
bool GoEyeUtil::IsNakadeShape(const SgPointSet& area) { switch (area.Size()) { case 1: case 2: case 3: return true; case 4: return IsBulkyFour(area) || IsTShape(area); case 5: return IsBulkyFive(area) || IsCross(area); case 6: return IsRabbitySix(area); default: // too big return false; } }
/** Information about safe points optimized for graphical display in GoGui. This command is compatible with GoGui's analyze command type "gfx". Arguments: benson|static <br> Returns: GoGui gfx commands to display safe points and additional information in the status line - black and white territory: safe points - Color Magenta (#980098): dame points - Color Red: safe for black and white (should not happen) - Circle: unsurroundable - Status line: point counts, percentage of safe points */ void GoSafetyCommands::CmdGfx(GtpCommand& cmd) { cmd.CheckNuArg(1); string type = cmd.Arg(0); int totalRegions = 0; SgBWSet safe = GetSafe(totalRegions, type); SgPointSet dame; SgPointSet unsurroundable; GoSafetyUtil::FindDameAndUnsurroundablePoints(m_bd, m_bd.AllEmpty(), safe, &dame, &unsurroundable); cmd << "BLACK"; for (SgSetIterator it(safe[SG_BLACK]); it; ++it) cmd << ' ' << SgWritePoint(*it); cmd << '\n'; cmd << "WHITE"; for (SgSetIterator it(safe[SG_WHITE]); it; ++it) cmd << ' ' << SgWritePoint(*it); cmd << '\n'; cmd << "COLOR #980098"; for (SgSetIterator it(dame); it; ++it) cmd << ' ' << SgWritePoint(*it); cmd << '\n'; cmd << "CIRCLE"; for (SgSetIterator it(unsurroundable); it; ++it) cmd << ' ' << SgWritePoint(*it); cmd << '\n'; SgPointSet blackAndWhite = safe[SG_WHITE] & safe[SG_BLACK]; if (blackAndWhite.Size() > 0) { // Shouldn't happen cmd << "COLOR red "; for (SgSetIterator it(blackAndWhite); it; ++it) cmd << ' ' << SgWritePoint(*it); cmd << '\n'; } int nuBlack = safe[SG_BLACK].Size(); int nuWhite = safe[SG_WHITE].Size(); int nuPoints = m_bd.AllPoints().Size(); cmd << "TEXT Solver: " << cmd.Arg(0) << " B: " << nuBlack << " (" << (100 * nuBlack / nuPoints) << " %)" << " W: " << nuWhite << " (" << (100 * nuWhite / nuPoints) << " %)" << " Both: " << (nuBlack + nuWhite) << " (" << (100 * (nuBlack + nuWhite) / nuPoints) << " %)" << " Regions: " << totalRegions; }
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; } }
bool GoEyeUtil::NumberOfMoveToEye2(const GoBoard& board, SgBlackWhite color, SgPoint p, int& nummoves) { nummoves = 0; bool capturing = false; SgVector<SgPoint> usedpoints; usedpoints.PushBack(p); SgPointSet counted; // Can never turn own stone into an eye if (board.IsColor(p, color)) return false; // If opponent stone then it must be captured to make eye if (board.IsColor(p, SgOppBW(color))) { capturing = true; // If it is obviously safe then it can never be an eye if (SinglePointSafe2(board, p)) // Quick, naive safety test return false; for (GoBoard::LibertyIterator libit(board, p); libit; ++libit) counted.Include(*libit); } // Count immediate adjacencies for (SgNb4Iterator nb(p); nb; ++nb) { SgPoint adj = *nb; // Empty points must be filled if (board.IsColor(adj, SG_EMPTY)) { counted.Include(adj); } // If adjacent opponent then can never be an eye else if (board.IsColor(adj, SgOppBW(color))) { if (capturing) counted.Include(adj); // must capture and then fill else return false; } } // Up to one diagonal can be ignored: estimate most costly SgPoint toignore = SG_NULLPOINT; int maxcost = 0; int infcost = 1000; if (board.Line(p) > 1) { for (SgNb4DiagIterator nbd(p); nbd; ++nbd) { SgPoint diag = *nbd; int cost = 0; if ( board.IsColor(diag, SG_EMPTY) && ! IsSinglePointEye2(board, diag, color, usedpoints)) { cost = 1; } else if (board.IsColor(diag, SgOppBW(color))) { // quick safety test if (SinglePointSafe2(board, diag)) cost = infcost; else cost = board.NumLiberties(diag); } if (cost > maxcost) { maxcost = cost; toignore = diag; } } } // Now mark points that must be played to secure diagonals for (SgNb4DiagIterator nbd(p); nbd; ++nbd) { SgPoint diag = *nbd; if (diag == toignore) continue; // Empty points must be filled (unless they are eyes) if ( board.IsColor(diag, SG_EMPTY) && ! IsSinglePointEye2(board, diag, color, usedpoints)) { counted.Include(diag); } // Opponent stones on diagonals must be captured and filled else if (board.IsColor(diag, SgOppBW(color))) { if (SinglePointSafe2(board, diag)) return false; else { counted.Include(diag); for (GoBoard::LibertyIterator libit(board, diag); libit; ++libit) counted.Include(*libit); } } } nummoves = counted.Size(); return true; }