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; }
bool GoRegion::AdjacentToSomeBlock(const SgVector<SgPoint>& anchors) const { for (SgVectorIteratorOf<GoBlock> it(m_blocks); it; ++it) { if (anchors.Contains((*it)->Anchor())) /* */ return true; /* */ } return false; }
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); }
SgPoint SgEvaluatedMovesArray::SelectNextBest(SgVector<SgPoint>& bestSoFar) const { int bestValue = s_minValue; SgPoint best = 0; for (SgPoint p = 0; p < SG_MAXPOINT; ++p) { if ((m_value[p] > bestValue) && ! bestSoFar.Contains(p)) { bestValue = m_value[p]; best = p; } } return best; }
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; }
bool GoRegion::Has2IntersectionPoints(const SgVector<SgPoint>& usedLibs) const { SgVector<SgPoint> jointLibs; JointLibs(&jointLibs); if (jointLibs.MinLength(2)) { // check if libs are intersection pts int nuIPs = 0; // doesn't have to adjacent to all interior points! 2005/08 for (SgVectorIterator<SgPoint> it(jointLibs); it; ++it) { if (IsSplitPt(*it, Points()) && ! usedLibs.Contains(*it)) { if (++nuIPs >= 2) return true; } } } return false; }
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); } } }
// 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 SgSearch::TryMove(SgMove move, const 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)) // already tried move before return false; int delta = DEPTH_UNIT; if (! CallExecute(move, &delta, depth)) return false; bool childIsExact = true; SgSearchStack newStack; int merit = -SearchEngine(depth - delta, -hiValue, -max(loValue, alpha), newStack, &childIsExact); if (loValue < merit && ! m_aborted) // new best move { loValue = merit; if (m_useScout) { // If getting a move that's better than what we have // so far, not good enough to cause a cutoff, was // searched with a narrow window, and doesn't // immediately lead to a terminal node, then search // again with a wide window to get a more precise // value. if ( alpha < merit && merit < beta && delta < depth ) { childIsExact = true; loValue = -SearchEngine(depth-delta, -beta, -merit, newStack, &childIsExact); } hiValue = max(loValue, alpha) + 1; } stack.CopyFrom(newStack); stack.Push(move); SG_ASSERT(move != SG_NULLMOVE); if (m_currentDepth == 1 && ! m_aborted) m_foundNewBest = true; } if (! childIsExact) allExact = false; CallTakeBack(); if (loValue >= beta) { // Move generated a cutoff: add this move to the list of // killers. if (m_useKillers && m_currentDepth <= MAX_KILLER_DEPTH) m_killers[m_currentDepth].MarkKiller(move); if (TraceIsOn()) m_tracer->TraceComment("b-cut"); isCutoff = true; } return true; }