void GoRegionBoard::MergeAdjacentAndAddBlock(SgPoint move, SgBlackWhite capturedColor) { SgVector<SgPoint> nb; for (GoNbIterator it(Board(), move); it; ++it) if (Board().IsEmpty(*it)) nb.PushBack(*it); SgVectorOf<GoBlock> captures; PreviousBlocksAt(nb, capturedColor, &captures); SG_ASSERT(captures.NonEmpty()); SgPointSet captured; {for (SgVectorIteratorOf<GoBlock> it(captures); it; ++it) captured |= (*it)->Stones(); } SgVectorOf<GoRegion> adj; const int size = Board().Size(); RegionsAt(captured.Border(size), capturedColor, &adj); SG_ASSERT(adj.NonEmpty()); GoRegion* r = MergeAll(adj, captured, capturedColor); SG_UNUSED(r); for (SgVectorIteratorOf<GoBlock> it(captures); it; ++it) RemoveBlock(*it, true, false); // don't remove from regions; already gone. }
bool GoEyeUtil::IsSinglePointEye2(const GoBoard& board, SgPoint p, SgBlackWhite color, SgVector<SgPoint>& eyes) { // Must be an empty point if (! board.IsColor(p, SG_EMPTY)) return false; // All adjacent neighbours must be friendly SgBoardColor opp = SgOppBW(color); for (SgNb4Iterator adj(p); adj; ++adj) { SgBoardColor adjColor = board.GetColor(*adj); if (adjColor == opp || adjColor == SG_EMPTY) return false; } // All diagonals (with up to one exception) must be friendly or an eye int baddiags = 0; int maxbaddiags = (board.Line(p) == 1 ? 0 : 1); for (SgNb4DiagIterator it(p); it; ++it) { if (board.IsColor(*it, opp)) ++baddiags; if (board.IsColor(*it, SG_EMPTY) && ! eyes.Contains(*it)) { // Assume this point is an eye and recurse eyes.PushBack(p); if (! IsSinglePointEye2(board, *it, color, eyes)) ++baddiags; eyes.PopBack(); } if (baddiags > maxbaddiags) return false; } return true; }
SgVector<SgPoint> GoGtpCommandUtil::PointListArg(const GtpCommand& cmd, std::size_t number, const GoBoard& board) { SgVector<SgPoint> result; for (size_t i = number; i < cmd.NuArg(); ++i) result.PushBack(PointArg(cmd, i, board)); return result; }
SgVector<SgPoint> GoGtpCommandUtil::GetHandicapStones(int size, int n) { SgVector<SgPoint> stones; if (n == 0) return stones; // GTP locations are defined up to size 25, but SG_MAX_SIZE could be // smaller if (size > SG_MAX_SIZE || size > 25) throw GtpFailure("no standard handicap locations defined"); int line1 = -1; int line2 = -1; int line3 = -1; if (size >= 13) { line1 = 4; line3 = size - 3; } else if (size >= 7) { line1 = 3; line3 = size - 2; } if (size >= 9 && size % 2 != 0) line2 = size / 2 + 1; if (line1 < 0 || n == 1 || n > 9 || (n > 4 && line2 < 0)) throw GtpFailure("no standard handicap locations defined"); if (n >= 1) stones.PushBack(Pt(line1, line1)); if (n >= 2) stones.PushBack(Pt(line3, line3)); if (n >= 3) stones.PushBack(Pt(line1, line3)); if (n >= 4) stones.PushBack(Pt(line3, line1)); if (n >= 5 && n % 2 != 0) { stones.PushBack(Pt(line2, line2)); --n; } if (n >= 5) stones.PushBack(Pt(line1, line2)); if (n >= 6) stones.PushBack(Pt(line3, line2)); if (n >= 7) stones.PushBack(Pt(line2, line1)); if (n >= 8) stones.PushBack(Pt(line2, line3)); return stones; }
void SgEvaluatedMovesArray::BestMoves(SgVector<SgPoint>& best, int nuMoves) const { best.Clear(); while (--nuMoves >= 0) { int nextBest = SelectNextBest(best); best.PushBack(nextBest); } }
void GoGtpCommandUtil::ParseMultiStoneArgument(GtpCommand& cmd, const GoBoard& board, SgBlackWhite& toPlay, SgBlackWhite& defender, SgVector<SgPoint>& crucial) { toPlay = GoGtpCommandUtil::BlackWhiteArg(cmd, 0); SgDebug() << "Set " << SgBW(toPlay) << " to play\n"; SgPoint point = GoGtpCommandUtil::StoneArg(cmd, 1, board); defender = board.GetColor(point); SG_ASSERT(defender == SG_BLACK || defender == SG_WHITE); crucial.PushBack(point); for (size_t i = 2; i < cmd.NuArg(); ++i) { SgPoint p = GoGtpCommandUtil::StoneArg(cmd, i, board); if (board.GetColor(p) != defender) throw GtpFailure("Crucial stones must be same color"); else crucial.PushBack(p); } }
void SgVectorUtil::Intersection(SgVector<int>* vector, const SgVector<int>& vector2) { SgVector<int> newVector; for (SgVectorIterator<int> it(*vector); it; ++it) { // @todo speed up by hash tags, if used in time-critical code if (vector2.Contains(*it)) newVector.PushBack(*it); } newVector.SwapWith(vector); }
void GoLadderUtil::FindLadderEscapeMoves(const GoBoard& bd, SgPoint prey, SgVector<SgPoint>& escapeMoves) { SG_ASSERT(bd.NumLiberties(prey) == 1); SG_ASSERT(escapeMoves.IsEmpty()); SgPoint p = bd.TheLiberty(prey); SgVector<SgPoint> candidates; candidates.PushBack(p); if (IsLadderEscapeMove(bd, prey, p)) escapeMoves.PushBack(p); for (GoAdjBlockIterator<GoBoard> it(bd, prey, 1); it; ++it) { // check if prey can escape by capturing *it on p. SgPoint p = bd.TheLiberty(*it); if (! candidates.Contains(p)) { candidates.PushBack(p); if (IsLadderEscapeMove(bd, prey, p)) escapeMoves.PushBack(p); } } }
void GoRegion::GetDivideMiaiPairs(SgVector<SgMiaiPair>& pairs) const { SgVector<SgPoint> divPs; for (SgVectorIteratorOf<GoBlock> it(Blocks()); it; ++it) { SgVector<SgPoint> libs, temp; InsideLibs(*it, &libs); SgMiaiPair p1; SgPoint a = -1; // only find miaipairs from libs (empty points) for (SgVectorIterator<SgPoint> it2(libs); it2; ++it2) { if (IsSplitPt(*it2, Points())) temp.PushBack(*it2); } temp.Sort(); divPs.PushBackList(temp); for (SgVectorIterator<SgPoint> it2(temp); it2; ++it2) { if (a == -1) a = (*it2); else { if (SgPointUtil::AreAdjacent(a, *it2)) { p1.first = a; p1.second = *it2; pairs.PushBack(p1); } a = *it2; } } } // found miaipairs for each block if (WRITEDEBUG) { SgDebug() << SgWritePointList(divPs, "divPs: ", true); for (SgVectorIterator<SgMiaiPair> it(pairs); it; ++it) { SgDebug() << "Pair(1: " << SgWritePoint((*it).first) << " 2: " << SgWritePoint((*it).second) << ")\n"; } } }
void GoRegionBoard::FindNewNeighborRegions(SgPoint move, BlackWhite moveColor) { // move was capture -> new region for each captured block. SgVector<SgPoint> nb; for (Nb4Iterator it(move); it; ++it) if (Board().IsEmpty(*it)) nb.PushBack(*it); SgVectorOf<GoBlock> captures; PreviousBlocksAt(nb, OppBW(moveColor), &captures); SG_ASSERT(captures.NonEmpty()); for (SgVectorIteratorOf<GoBlock> it(captures); it; ++it) BlockToRegion(*it); }
bool SgSearch::TrySpecialMove(SgMove move, SgVector<SgMove>& specialMoves, const int depth, const int alpha, const int beta, int& loValue, int& hiValue, SgSearchStack& stack, bool& allExact, bool& isCutoff) { if (specialMoves.Contains(move)) return false; bool executed = TryMove(move, specialMoves, depth, alpha, beta, loValue, hiValue, stack, allExact, isCutoff); specialMoves.PushBack(move); return executed; }
void GoGame::InitHandicap(const GoRules& rules, SgNode* root) { // TODO: Use PlaceHandicap() in implementation of InitHandicap() to // avoid redundancy // Add handicap properties. if (2 <= rules.Handicap()) { SgPropInt* handicap = new SgPropInt(SG_PROP_HANDICAP, rules.Handicap()); root->Add(handicap); if (rules.JapaneseHandicap()) { if (9 <= m_board.Size()) { int h = rules.Handicap(); int half = (m_board.Size()+1) / 2; SgVector<SgPoint> stones; if ((4 < h) && (h % 2 != 0)) { stones.PushBack(SgPointUtil::Pt(half, half)); --h; } if (13 <= m_board.Size()) { AddHandicap(m_board.Size(), 4, 4, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), half, 4, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), 3, 3, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), 7, 7, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), half, 3, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), half - (half - 4) / 2, 4, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), half + (half - 4) / 2, 4, &h, &stones); } else { AddHandicap(m_board.Size(), 3, 3, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), half, 3, &h, &stones); if (0 < h) AddHandicap(m_board.Size(), 4, 4, &h, &stones); } SgPropAddStone* addBlack = new SgPropAddStone(SG_PROP_ADD_BLACK, stones); root->Add(addBlack); // White to play. SgPropPlayer* player = new SgPropPlayer(SG_PROP_PLAYER, SG_WHITE); root->Add(player); } } else { // Chinese handicap. SgPropInt* chinese = new SgPropInt(SG_PROP_CHINESE, rules.Handicap()); root->Add(chinese); } } }
// improved by using recursive extension to find 2-conn paths. bool GoRegion::Find2ConnForAllInterior(SgMiaiStrategy* miaiStrategy, SgVector<SgPoint>& usedLibs) const { SgVector<SgMiaiPair> myStrategy; const int size = m_bd.Size(); SgPointSet interior = AllInsideLibs(); if (interior.IsEmpty()) { return true; } //if (GetFlag(GO_REGION_SINGLE_BLOCK_BOUNDARY)) { SgPointSet testSet = interior; SgPointSet originalLibs = testSet.Border(size) & Dep().Border(size) & m_bd.AllEmpty() & Points(); SgPointSet updateLibs = originalLibs; // now try to find miai-paths to remaining interior points recursively bool changed = true; while (changed) { changed = false; if (testSet.IsEmpty()) { SgVector<SgPoint> jlibs; JointLibs(&jlibs); SgVector<SgPoint> ips; GetIPs(&ips); SgVector<SgMiaiPair> updateStrg; for (SgSetIterator it(interior); it; ++it) { SgPoint p = *it; SgPointSet s1; s1.Include(p); SgPointSet rest = s1.Border(size) & updateLibs; if (! rest.IsEmpty()) { for (SgVectorIterator<SgMiaiPair> it2(myStrategy); it2; ++it2) { SgMiaiPair x = *it2; if ( SgPointUtil::AreAdjacent(p, x.first) && SgPointUtil::AreAdjacent(p, x.second) ) { if (ips.Contains(x.first)) { updateLibs.Include(x.first); usedLibs.Exclude(x.first); SgPoint t = rest.PointOf(); x.first = t; updateLibs.Exclude(t); rest.Exclude(t); usedLibs.Include(t); } if ( ips.Contains(x.second) && ! rest.IsEmpty() ) { updateLibs.Include(x.second); usedLibs.Exclude(x.second); SgPoint t = rest.PointOf(); x.second = t; updateLibs.Exclude(t); rest.Exclude(t); usedLibs.Include(t); } updateStrg.Include(x); } } } } miaiStrategy->SetStrategy(updateStrg); /* */ return true; /* */ } for (SgSetIterator it(interior); it; ++it) { SgMiaiPair miaiPair; if (Find2BestLibs(*it, updateLibs, testSet, &miaiPair)) { if (miaiPair.first == miaiPair.second) { SgDebug() <<"\nmiaipair are same: " << SgWritePoint(miaiPair.first) << SgWritePoint(miaiPair.second); SgDebug() <<"\ncurrent region is:\n"; Points().Write(SgDebug(), size); SG_ASSERT(false); } myStrategy.PushBack(miaiPair); usedLibs.PushBack(miaiPair.first); usedLibs.PushBack(miaiPair.second); updateLibs.Exclude(miaiPair.first); updateLibs.Exclude(miaiPair.second); updateLibs.Include(*it); testSet.Exclude(*it); changed = true; } } } // while loop for recursive finding } miaiStrategy->Clear(); return false; }
int SgSearch::SearchEngine(int depth, int alpha, int beta, SgSearchStack& stack, bool* isExactValue, bool lastNullMove) { SG_ASSERT(stack.IsEmpty() || stack.Top() != SG_NULLMOVE); SG_ASSERT(alpha < beta); // Only place we check whether the search has been newly aborted. In all // other places, just check whether search was aborted before. // AR: what to return here? // if - (SG_INFINITY-1), then will be positive on next level? if (AbortSearch()) { *isExactValue = false; return alpha; } // Null move pruning if ( m_useNullMove && depth > 0 && ! lastNullMove && NullMovePrune(depth, DEPTH_UNIT * (1 + m_nullMoveDepth), beta) ) { *isExactValue = false; return beta; } // ProbCut if (m_probcut && m_probcut->IsEnabled()) { int probCutValue; if (m_probcut->ProbCut(*this, depth, alpha, beta, stack, isExactValue, &probCutValue) ) return probCutValue; } m_stat.IncNumNodes(); bool hasMove = false; // true if a move has been executed at this level int loValue = -(SG_INFINITY - 1); m_reachedDepthLimit = m_reachedDepthLimit || (depth <= 0); // check whether position is solved from hash table. SgSearchHashData data; // initialized to ! data.IsValid() if (LookupHash(data)) { if (data.IsExactValue()) // exact value: stop search { *isExactValue = true; stack.Clear(); if (data.BestMove() != SG_NULLMOVE) stack.Push(data.BestMove()); if (TraceIsOn()) m_tracer->TraceValue(data.Value(), GetToPlay(), "exact-hash", true); return data.Value(); } } bool allExact = true; // Do all moves have exact evaluation? if (depth > 0 && ! EndOfGame()) { // Check whether current position has already been encountered. SgMove tryFirst = SG_NULLMOVE; SgMove opponentBest = SG_NULLMOVE; if (data.IsValid()) { if (data.Depth() > 0) { tryFirst = data.BestMove(); SG_ASSERT(tryFirst != SG_NULLMOVE); } // If data returned from hash table is based on equal or deeper // search than what we plan to do right now, just use that data. // The hash table may have deeper data for the current position // since the same number of moves may result in more 'depth' // left if the 'delta' for the moves has been smaller, which // will happen when most moves came from the cache. if (depth <= data.Depth()) { // Rely on value returned from hash table to be for the // current position. In Go, it can happen that the move is // not legal (ko recapture) int delta = DEPTH_UNIT; bool canExecute = CallExecute(tryFirst, &delta, depth); if (canExecute) CallTakeBack(); else tryFirst = SG_NULLMOVE; if (tryFirst != SG_NULLMOVE || data.IsExactValue()) { // getting a deep enough hash hit or an exact value // is as good as reaching the depth limit by search. m_reachedDepthLimit = true; // Update bounds with data from cache. data.AdjustBounds(&alpha, &beta); if (alpha >= beta) { *isExactValue = data.IsExactValue(); stack.Clear(); if (tryFirst != SG_NULLMOVE) stack.Push(tryFirst); if (TraceIsOn()) m_tracer->TraceValue(data.Value(), GetToPlay(), "Hash hit", *isExactValue); return data.Value(); } } } int delta = DEPTH_UNIT; if ( tryFirst != SG_NULLMOVE && CallExecute(tryFirst, &delta, depth) ) { bool childIsExact = true; loValue = -SearchEngine(depth-delta, -beta, -alpha, stack, &childIsExact); if (TraceIsOn()) m_tracer->TraceComment("tryFirst"); CallTakeBack(); hasMove = true; if (m_aborted) { if (TraceIsOn()) m_tracer->TraceComment("aborted"); *isExactValue = false; return (1 < m_currentDepth) ? alpha : loValue; } if (stack.NonEmpty()) { opponentBest = stack.Top(); SG_ASSERT(opponentBest != SG_NULLMOVE); } stack.Push(tryFirst); if (! childIsExact) allExact = false; if (loValue >= beta) { if (TraceIsOn()) m_tracer->TraceValue(loValue, GetToPlay()); // store in hash table. Known to be exact only if // solved for one player. bool isExact = SgSearchValue::IsSolved(loValue); StoreHash(depth, loValue, tryFirst, false /*isUpperBound*/, true /*isLowerBound*/, isExact); *isExactValue = isExact; if (TraceIsOn()) m_tracer->TraceValue(loValue, GetToPlay(), "b-cut", isExact); return loValue; } } } // 'hiValue' is equal to 'beta' for alpha-beta algorithm, and gets set // to alpha+1 for Scout, except for the first move. int hiValue = (hasMove && m_useScout) ? max(loValue, alpha) + 1 : beta; bool foundCutoff = false; SgVector<SgMove> specialMoves; // Don't execute 'tryFirst' again. if (tryFirst != SG_NULLMOVE) specialMoves.PushBack(tryFirst); // Heuristic: "a good move for my opponent is a good move for me" if ( ! foundCutoff && m_useOpponentBest && opponentBest != SG_NULLMOVE && TrySpecialMove(opponentBest, specialMoves, depth, alpha, beta, loValue, hiValue, stack, allExact, foundCutoff) ) hasMove = true; if ( ! foundCutoff && m_useKillers && m_currentDepth <= MAX_KILLER_DEPTH ) { SgMove killer1 = m_killers[m_currentDepth].GetKiller1(); if ( killer1 != SG_NULLMOVE && TrySpecialMove(killer1, specialMoves, depth, alpha, beta, loValue, hiValue, stack, allExact, foundCutoff) ) hasMove = true; SgMove killer2 = m_killers[m_currentDepth].GetKiller2(); if ( ! foundCutoff && killer2 != SG_NULLMOVE && TrySpecialMove(killer2, specialMoves, depth, alpha, beta, loValue, hiValue, stack, allExact, foundCutoff) ) hasMove = true; } // Generate the moves for this position. SgVector<SgMove> moves; if (! foundCutoff && ! m_aborted) { CallGenerate(&moves, depth); // Iterate through all the moves to find the best move and // correct value for this position. for (SgVectorIterator<SgMove> it(moves); it && ! foundCutoff; ++it) { if (TryMove(*it, specialMoves, depth, alpha, beta, loValue, hiValue, stack, allExact, foundCutoff) ) hasMove = true; if (! foundCutoff && m_aborted) { if (TraceIsOn()) m_tracer->TraceComment("ABORTED"); *isExactValue = false; return (1 < m_currentDepth) ? alpha : loValue; } } } // Make sure the move added to the hash table really got generated. #ifndef NDEBUG if (hasMove && stack.NonEmpty() && ! m_aborted) { SgMove bestMove = stack.Top(); SG_ASSERT(bestMove != SG_NULLMOVE); SG_ASSERT( specialMoves.Contains(bestMove) || moves.Contains(bestMove) ); } #endif } bool isSolved = ! m_aborted; if (! m_aborted) { // Evaluate position if terminal node (either no moves generated, or // none of the generated moves were legal). bool solvedByEval = false; if (! hasMove) { m_stat.IncNumEvals(); stack.Clear(); loValue = CallEvaluate(depth, &solvedByEval); } // Save data about current position in the hash table. isSolved = solvedByEval || SgSearchValue::IsSolved(loValue) || (hasMove && allExact); // || EndOfGame(); bug: cannot store exact score after two passes. if ( m_hash && ! m_aborted && (isSolved || stack.NonEmpty()) ) { SgMove bestMove = SG_NULLMOVE; if (stack.NonEmpty()) { bestMove = stack.Top(); SG_ASSERT(bestMove != SG_NULLMOVE); } SG_ASSERT(alpha <= beta); StoreHash(depth, loValue, bestMove, (loValue <= alpha) /* upper */, (beta <= loValue) /* lower*/, isSolved); } } // If aborted search and didn't find any values, just return alpha. // Can't return best found so far, since may not have tried the optimal // counter-move yet. However, return best value found so far on top // level, since assuming hash move will have been tried first. if (m_aborted && (1 < m_currentDepth || loValue < alpha)) loValue = alpha; *isExactValue = isSolved; if (TraceIsOn()) m_tracer->TraceValue(loValue, GetToPlay(), 0, isSolved); SG_ASSERT(stack.IsEmpty() || stack.Top() != SG_NULLMOVE); return loValue; }
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; }
/** Main ladder routine */ int GoLadder::Ladder(const GoBoard& bd, SgPoint prey, SgBlackWhite toPlay, SgVector<SgPoint>* sequence, bool twoLibIsEscape) { GoModBoard modBoard(bd); m_bd = &modBoard.Board(); InitMaxMoveNumber(); if (sequence) sequence->Clear(); if (! m_bd->Occupied(prey)) return 0; if (CheckMoveOverflow()) return GOOD_FOR_PREY; int result = 0; m_preyColor = m_bd->GetStone(prey); m_hunterColor = SgOppBW(m_preyColor); int numLib = m_bd->NumLiberties(prey); if (2 < numLib) result = GOOD_FOR_PREY; else { GoBoard::LibertyIterator libit(*m_bd, prey); SgPoint lib1 = *libit; m_partOfPrey.Clear(); MarkStonesAsPrey(prey); GoPointList adjBlk = GoBoardUtil::AdjacentStones(*m_bd, prey); FilterAdjacent(adjBlk); if (toPlay == m_preyColor) { if (numLib == 1) result = PreyLadder(0, lib1, adjBlk, sequence); else if (twoLibIsEscape) // prey to play, numLib >= 2 // For example, Explorer cannot treat this case as a ladder, // it messes up the logic result = GOOD_FOR_PREY; else { // Prey to play, two liberties. This is usually good, but // need to prove that there is some move that really // escapes laddercapture. // Try liberties of adjacent blocks with at most two // liberties, try lib1 and lib2, and try moves one away // from the two liberties. // Good example with three blocks that test this case: // (;GM[1]SZ[19]FF[3] // AB[qa][pa][pb][pd][pc][qe][re][rd][rc][se] // AW[pe][pf][qf][qd][qc][rb][qb][sa][sc][rf][rg][sg]) SgVector<SgPoint> movesToTry; // Liberties of adj. blocks with at most two liberties. adjBlk = GoBoardUtil::AdjacentStones(*m_bd, prey); ReduceToBlocks(adjBlk); for (GoPointList::Iterator iterAdj(adjBlk); iterAdj; ++iterAdj) { SgPoint block = *iterAdj; SG_ASSERT(m_bd->IsColor(block, m_hunterColor)); SG_ASSERT(BlockIsAdjToPrey(block, 1)); if (m_bd->NumLiberties(block) <= 2) for (GoBoard::LibertyIterator it(*m_bd, block); it; ++it) movesToTry.PushBack(*it); } // Liberties of blocks. ++libit; SgPoint lib2 = *libit; movesToTry.PushBack(lib1); movesToTry.PushBack(lib2); // Moves one away from liberties. SgVector<SgPoint> neighbors; NeighborsOfColor(*m_bd, lib1, SG_EMPTY, &neighbors); movesToTry.Concat(&neighbors); NeighborsOfColor(*m_bd, lib2, SG_EMPTY, &neighbors); movesToTry.Concat(&neighbors); // Try whether any of these moves lead to escape. for (SgVectorIterator<SgPoint> it(movesToTry); it; ++it) { if (PlayIfLegal(*m_bd, *it, m_preyColor)) { if (Ladder(bd, prey, m_hunterColor, 0, twoLibIsEscape) > 0) { if (sequence) sequence->PushBack(*it); result = GOOD_FOR_PREY; } m_bd->Undo(); } if (result != 0) break; } // If none of those moves worked, prey can't escape. // This is a bit pessimistic, there may be other moves // that do lead to escape (e.g. approach moves), but // ladder algorithm doesn't know about those. if (result == 0) result = GOOD_FOR_HUNTER; } } else { if (IsSnapback(prey)) result = GOOD_FOR_PREY; else { ++libit; if (libit) // two liberties result = HunterLadder(0, lib1, *libit, adjBlk, sequence); else // one liberty result = HunterLadder(0, lib1, adjBlk, sequence); } } } if (sequence) sequence->Reverse(); // built as a stack, with first move at end. return result; }
/** Play prey move and update all the relevant information. Extend the prey by playing at its only liberty, or capture a block adjacent to the prey. */ int GoLadder::PlayPreyMove(int depth, SgPoint move, SgPoint lib1, const GoPointList& adjBlk, SgVector<SgPoint>* sequence) { int result = 0; GoPointList newAdj(adjBlk); SgVector<SgPoint> newLib; SgVector<SgPoint> newStones; SgVector<SgPoint> neighbors; if (move == lib1) { NeighborsOfColor(*m_bd, move, m_preyColor, &neighbors); for (SgVectorIterator<SgPoint> iter(neighbors); iter; ++iter) { SgPoint block = *iter; if (! m_partOfPrey[block]) { MarkStonesAsPrey(block, &newStones); GoPointList temp = GoBoardUtil::AdjacentStones(*m_bd, block); newAdj.PushBackList(temp); for (GoBoard::LibertyIterator it(*m_bd, block); it; ++it) newLib.Include(*it); } } m_partOfPrey.Include(move); } if (PlayIfLegal(*m_bd, move, m_preyColor)) { if (move == lib1) { NeighborsOfColor(*m_bd, move, SG_EMPTY, &neighbors); for (SgVectorIterator<SgPoint> iter(newLib); iter; ++iter) { SgPoint point = *iter; // Test for Empty is necessary because newLib will include // the move just played. if (m_bd->IsEmpty(point)) neighbors.Include(point); } } else { neighbors.PushBack(lib1); } if (m_bd->CapturingMove()) { // Add the points at the captured stones that are adjacent to the // prey to the liberties, at least if exactly one stone captured. for (GoPointList::Iterator it(m_bd->CapturedStones()); it; ++it) { SgPoint stone = *it; if (PointIsAdjToPrey(stone)) neighbors.Include(stone); } } SG_ASSERT(! neighbors.IsEmpty()); lib1 = neighbors[0]; SG_ASSERT(m_bd->IsEmpty(lib1)); SgArrayList<SgPoint,4> temp = NeighborsOfColor(*m_bd, move, m_hunterColor); newAdj.PushBackList(temp); FilterAdjacent(newAdj); if (neighbors.Length() == 1) result = HunterLadder(depth + 1, lib1, newAdj, sequence); else if (neighbors.Length() == 2) { SgPoint lib2 = neighbors[1]; SG_ASSERT(m_bd->IsEmpty(lib2)); result = HunterLadder(depth + 1, lib1, lib2, newAdj, sequence); } else // 3 <= numLib { if (sequence) sequence->Clear(); result = GOOD_FOR_PREY - (depth + 1); } if (sequence) sequence->PushBack(move); m_bd->Undo(); } else { if (sequence) sequence->Clear(); result = GOOD_FOR_HUNTER + depth; } m_partOfPrey.Exclude(move); m_partOfPrey.Exclude(newStones); return result; }