コード例 #1
0
bool GoBoardHistory::
IsAlternatePlayFollowUpOf(const GoBoardHistory& other,
                          std::vector<SgPoint>& sequence) const
{
    if (m_boardSize != other.m_boardSize
        || m_rules != other.m_rules
        || m_setup != other.m_setup
        || m_moves.Length() < other.m_moves.Length())
        return false;
    for (int i = 0; i < other.m_moves.Length(); ++i)
        if (m_moves[i] != other.m_moves[i])
            return false;
    sequence.clear();
    SgBlackWhite toPlay = other.m_toPlay;
    for (int i = other.m_moves.Length(); i < m_moves.Length(); ++i)
    {
        GoPlayerMove m = m_moves[i];
        if (m.Color() != toPlay)
            return false;
        sequence.push_back(m.Point());
        toPlay = SgOppBW(toPlay);
    }
    if (toPlay != m_toPlay)
        return false;
    return true;
}
コード例 #2
0
void GoRegionBoard::OnExecutedUncodedMove(int move, SgBlackWhite moveColor)
{
    if (DEBUG_REGION_BOARD)
        SgDebug() << "OnExecutedUncodedMove " << SgWritePoint(move) << '\n';
    {
        m_stack.StartMoveInfo();
        if (move != SG_PASS)
        {
            SG_ASSERT(! Board().LastMoveInfo(GO_MOVEFLAG_SUICIDE));
            // can't handle yet,
            // should be forbidden anyway. @todo allowed in Chinese rules.
            bool fWasCapture = Board().LastMoveInfo(GO_MOVEFLAG_CAPTURING);

            UpdateBlock(move, moveColor);

            {
                GoRegion* r = PreviousRegionAt(move, moveColor);
                bool split = GoEyeUtil::IsSplitPt(move, r->Points());

                r->OnAddStone(move);
                PushStone(r, move);
                SgPointSet points = r->Points();
                // needed even after RemoveRegion(r).
                if (split || points.IsEmpty())
                // must remove old region before generating new ones,
                // because removing clears m_anchor[]
                    RemoveRegion(r);

                if (split) // find new regions
                {
                    for (SgConnCompIterator it(points, Board().Size());
                         it; ++it)
                        GenRegion(*it, moveColor);
                }
            }

            if (fWasCapture)
            {
            //  FindNewNeighborRegions(move, moveColor);
                MergeAdjacentAndAddBlock(move, SgOppBW(moveColor));
            }

            m_code = Board().GetHashCode();
            if (HEAVYCHECK)
                CheckConsistency();
        }
    }

    {
        for (SgBWIterator it; it; ++it)
        {
            SgBlackWhite color(*it);
            for (SgVectorIteratorOf<GoRegion> it(AllRegions(color)); it; ++it)
            {   GoRegion* r1 = *it;
                if (! r1->IsValid())
                    r1->ComputeBasicFlags();
            }
        }
    }
}
コード例 #3
0
ファイル: GoInfluence.cpp プロジェクト: Nopik/dragongoclient
void GoInfluence::FindInfluence(const GoBoard& board,
                    int nuExpand,
                    int nuShrink,
                    SgBWSet* influence)
{   
    SgBWSet result = SgBWSet(board.All(SG_BLACK), board.All(SG_WHITE));
    SgBWSet next;
    const int size = board.Size();
    for (int i = 1; i <= nuExpand; ++i)
    {
        for (SgBlackWhite c = SG_BLACK; c <= SG_WHITE; ++c)
        {
            SgBlackWhite opp = SgOppBW(c);
            next[c] = result[c].Border(size) - result[opp];
        }
        result[SG_BLACK] |= (next[SG_BLACK] - next[SG_WHITE]);
        result[SG_WHITE] |= (next[SG_WHITE] - next[SG_BLACK]);
    }

    for (int i = 1; i <= nuShrink; ++i)
    {
        result[SG_BLACK] = result[SG_BLACK].Kernel(size);
        result[SG_WHITE] = result[SG_WHITE].Kernel(size);
    }
    
    *influence = result;
}
コード例 #4
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
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;
}
コード例 #5
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
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;
        }
    }
    
}
コード例 #6
0
// @todo: as a first cut, this simply checks if the regions has any
// stones in it. It does not check if the move is suitable for capturing the
// stones.
bool GoSafetySolver::PotentialCaptureMove(SgPoint p,
                                          SgBlackWhite regionColor) const
{
    SG_ASSERT(UpToDate());
    SG_ASSERT(Board().ToPlay() == regionColor);
    const GoRegion* r = Regions()->RegionAt(p, regionColor);
    SG_ASSERT(r);
    return r->Points().Overlaps(Board().All(SgOppBW(regionColor)));
}
コード例 #7
0
int SpAverageMoveGenerator::Evaluate()
{
    // We are Opponent since this is after executing our move
    SgBlackWhite player = m_board.Opponent();
    int score = LibertyAveragex10(m_board, player)
              - LibertyAveragex10(m_board, SgOppBW(player));
        
    return score;
}
コード例 #8
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
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;
}
コード例 #9
0
int SpMinLibMoveGenerator::Evaluate()
{    
    // We are Opponent since this is after executing our move
    SgBlackWhite player = m_board.Opponent();
    int minlibscore = LibertyMinimum(player);
 
    int avlibscore = 
          ::LibertyAveragex10(m_board, player)
        - ::LibertyAveragex10(m_board, SgOppBW(player));

    // Select moves in priority order according to:
    // a) Minimum liberties (for either player)
    // b) Average liberties x 10
    
    // Note that the 1/x scaling of minlibs means that avlibs can matter more
    // when |minlibs| is very high. Which is probably a good thing!
    int score = minlibscore * 100 + avlibscore;
    // roughly in range [-10000,10000]
    return score;
}
コード例 #10
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
bool GoEyeUtil::IsPossibleEye(const GoBoard& board, SgBlackWhite color,
                              SgPoint p)
{
    bool isPossibleEye = false;
    SG_ASSERT(board.GetColor(p) != color);
    const SgBlackWhite opp = SgOppBW(color);
    if (board.Line(p) == 1) // corner or edge
    {
        const int nuOwn = (board.Pos(p) == 1) ? 2 : 4;
        if ( board.Num8Neighbors(p, color) == nuOwn
             && board.Num8EmptyNeighbors(p) == 1
           )
        {     
            isPossibleEye = true;
        }
    }
    else // in center
    {
        // have all neighbors, and 2 diagonals, and can get a third
        if (    board.NumNeighbors(p, color) == 4
             && board.NumDiagonals(p, color) == 2
             && board.NumEmptyDiagonals(p) > 0
           )
        {     
            isPossibleEye = true;
        }
        // have 3 of 4 neighbors, can get the 4th, and have enough diagonals
        else if (   board.NumNeighbors(p, color) == 3
                 && board.NumNeighbors(p, opp) == 0
                 && board.NumDiagonals(p, color) >= 3 
                )
        {
            isPossibleEye = true;
        }
    }
    
    return isPossibleEye;
}
コード例 #11
0
ファイル: SgMiaiMap.cpp プロジェクト: ernest-galbrun/fuegoia
/** See SgStrategy::ExecuteMove */
void SgMiaiMap::ExecuteMove(SgPoint p, SgBlackWhite player)
{
    if (m_forcedMove != SG_NULLPOINT)
    {
        SG_ASSERT(m_forcedMove == p);
        m_forcedMove = SG_NULLPOINT;
    }
    else if (m_map[player][p] != SG_NULLPOINT)
    {
        m_forcedMove = m_map[player][p];
        m_map[player][p] = SG_NULLPOINT;
        m_map[player][m_forcedMove] = SG_NULLPOINT; // too early???

        SgBlackWhite opp = SgOppBW(player);
        SgPoint temp = m_map[opp][p];
        if (temp != SG_NULLPOINT)
        {
            SG_ASSERT(temp != SG_NULLPOINT);
            m_map[opp][p] = SG_NULLPOINT;
            SG_ASSERT(m_map[opp][temp] == p);
            m_map[opp][temp] = SG_NULLPOINT;
        }
    }
}
コード例 #12
0
size_t DfpnSolver::MID(const DfpnBounds& maxBounds, DfpnHistory& history)
{
    maxBounds.CheckConsistency();
    SG_ASSERT(maxBounds.phi > 1);
    SG_ASSERT(maxBounds.delta > 1);

    ++m_numMIDcalls;
    size_t prevWork = 0;
    SgEmptyBlackWhite colorToMove = GetColorToMove();

    DfpnData data;
    if (TTRead(data)) 
    {
        prevWork = data.m_work;
        if (! maxBounds.GreaterThan(data.m_bounds))
            // Estimated bounds are larger than we had
            // anticipated. The calling state must have computed
            // the max bounds with out of date information, so just
            // return here without doing anything: the caller will
            // now update to this new info and carry on.
            return 0;
    }
    else
    {
        SgEmptyBlackWhite winner = SG_EMPTY;
        if (TerminalState(colorToMove, winner))
        {
            ++m_numTerminal;
            DfpnBounds terminal;
            if (colorToMove == winner)
                DfpnBounds::SetToWinning(terminal);
            else
            {
                SG_ASSERT(SgOppBW(colorToMove) == winner);
                DfpnBounds::SetToLosing(terminal);
            }
            TTWrite(DfpnData(terminal, SG_NULLMOVE, 1));
            return 1;
        }
    }
    
    ++m_generateMoves;
    DfpnChildren children;
    GenerateChildren(children.Children());

    // Not thread safe: perhaps move into while loop below later...
    std::vector<DfpnData> childrenData(children.Size());
    for (size_t i = 0; i < children.Size(); ++i)
        LookupData(childrenData[i], children, i);
    // Index used for progressive widening
    size_t maxChildIndex = ComputeMaxChildIndex(childrenData);

    SgHashCode currentHash = Hash();
    SgMove bestMove = SG_NULLMOVE;
    DfpnBounds currentBounds;
    size_t localWork = 1;
    do
    {
        UpdateBounds(currentBounds, childrenData, maxChildIndex);
        if (! maxBounds.GreaterThan(currentBounds))
            break;

        // Select most proving child
        std::size_t bestIndex = 999999;
        DfpnBoundType delta2 = DfpnBounds::INFTY;
        SelectChild(bestIndex, delta2, childrenData, maxChildIndex);
        bestMove = children.MoveAt(bestIndex);

        // Compute maximum bound for child
        const DfpnBounds childBounds(childrenData[bestIndex].m_bounds);
        DfpnBounds childMaxBounds;
        childMaxBounds.phi = maxBounds.delta 
            - (currentBounds.delta - childBounds.phi);
        childMaxBounds.delta = delta2 == DfpnBounds::INFTY ? maxBounds.phi :
            std::min(maxBounds.phi,
                     std::max(delta2 + 1, DfpnBoundType(delta2 * (1.0 + m_epsilon))));
        SG_ASSERT(childMaxBounds.GreaterThan(childBounds));
        if (delta2 != DfpnBounds::INFTY)
            m_deltaIncrease.Add(float(childMaxBounds.delta-childBounds.delta));

        // Recurse on best child
        PlayMove(bestMove);
        history.Push(bestMove, currentHash);
        localWork += MID(childMaxBounds, history);
        history.Pop();
        UndoMove();

        // Update bounds for best child
        LookupData(childrenData[bestIndex], children, bestIndex);

        // Compute some stats when find winning move
        if (childrenData[bestIndex].m_bounds.IsLosing())
        {
            m_moveOrderingIndex.Add(float(bestIndex));
            m_moveOrderingPercent.Add(float(bestIndex) 
                                      / (float)childrenData.size());
            m_totalWastedWork += prevWork + localWork
                - childrenData[bestIndex].m_work;
        }
        else if (childrenData[bestIndex].m_bounds.IsWinning())
            maxChildIndex = ComputeMaxChildIndex(childrenData);

    } while (! CheckAbort());

    // Find the most delaying move for losing states, and the smallest
    // winning move for winning states.
    if (currentBounds.IsSolved())
    {
        if (currentBounds.IsLosing())
        {
            std::size_t maxWork = 0;
            for (std::size_t i = 0; i < children.Size(); ++i)
            {
                if (childrenData[i].m_work > maxWork)
                {
                    maxWork = childrenData[i].m_work;
                    bestMove = children.MoveAt(i);
                }
            }
        }
        else
        {
            std::size_t minWork = DfpnBounds::INFTY;
            for (std::size_t i = 0; i < children.Size(); ++i)
            {
                if (childrenData[i].m_bounds.IsLosing() 
                    && childrenData[i].m_work < minWork)
                {
                    minWork = childrenData[i].m_work;
                    bestMove = children.MoveAt(i);
                }
            }
        }
    }
    
    // Store search results
    TTWrite(DfpnData(currentBounds, bestMove, localWork + prevWork));
    return localWork;
}
コード例 #13
0
vector<SgPoint> GoUctDefaultMoveFilter::Get()
{
    vector<SgPoint> rootFilter;
    const SgBlackWhite toPlay = m_bd.ToPlay();
    const SgBlackWhite opp = SgOppBW(toPlay);

    // Safe territory
    if (m_param.m_checkSafety)
    {
        SgBWSet alternateSafe;
        // Alternate safety is used to prune moves only in opponent territory
        // and only if everything is alive under alternate play. This ensures that
        // capturing moves that are not liberties of dead blocks and ko threats
        // will not be pruned. This alternate safety pruning is not going to
        // improve or worsen playing strength, but may cause earlier passes,
        // which is nice in games against humans
        GoSafetySolver safetySolver(m_bd);
        safetySolver.FindSafePoints(&alternateSafe);

        // Benson solver guarantees that capturing moves of dead blocks are
        // liberties of the dead blocks and that no move in Benson safe territory
        // is a ko threat
        GoBensonSolver bensonSolver(m_bd);
        SgBWSet unconditionalSafe;
        bensonSolver.FindSafePoints(&unconditionalSafe);

        for (GoBoard::Iterator it(m_bd); it; ++it)
        {
            const SgPoint p = *it;
            if (m_bd.IsLegal(p))
            {
                bool isUnconditionalSafe = unconditionalSafe[toPlay].Contains(p);
                bool isUnconditionalSafeOpp = unconditionalSafe[opp].Contains(p);
                bool isAlternateSafeOpp = alternateSafe[opp].Contains(p);
                bool hasOppNeighbors = m_bd.HasNeighbors(p, opp);
                // Always generate capturing moves in own safe territory, even
                // if current rules do no use CaptureDead(), because the UCT
                // player always scores with Tromp-Taylor after two passes in the
                // in-tree phase
                // if (  (isAllAlternateSafe && isAlternateSafeOpp)
                if (  isAlternateSafeOpp
                   || isUnconditionalSafeOpp
                   || (isUnconditionalSafe && ! hasOppNeighbors)
                   || (  alternateSafe[toPlay].Contains(p)
                      && ! safetySolver.PotentialCaptureMove(p, toPlay)
                      )
                   )
                    rootFilter.push_back(p);
            }
        }
    }

    // Losing ladder defense moves
    if (m_param.m_checkLadders)
    {
        for (GoBlockIterator it(m_bd); it; ++it)
        {
            const SgPoint p = *it;
            if (m_bd.GetStone(p) == toPlay && m_bd.InAtari(p))
            {
                if (m_ladder.Ladder(m_bd, p, toPlay, &m_ladderSequence,
                                    false/*twoLibIsEscape*/) < 0)
                {
                    if (m_ladderSequence.Length() >= m_param.m_minLadderLength)
                        rootFilter.push_back(m_bd.TheLiberty(p));
                }
            }

        }
    }

    if (m_param.m_checkOffensiveLadders)
    {
        for (GoBlockIterator it(m_bd); it; ++it)
        {
            const SgPoint p = *it;
            if (m_bd.GetStone(p) == opp
                && m_bd.NumStones(p) >= 5
                && m_bd.NumLiberties(p) == 2
                && m_ladder.Ladder(m_bd, p, toPlay, &m_ladderSequence,
                                    false/*twoLibIsEscape*/) > 0
                && m_ladderSequence.Length() >= m_param.m_minLadderLength
                )
                    rootFilter.push_back(m_ladderSequence[0]);
        }
    }

    if (m_param.m_filterFirstLine)
    {
        // Moves on edge of board, if no stone is near
        const SgBoardConst& bc = m_bd.BoardConst();
        for (SgLineIterator it(bc, 1); it; ++it)
        {
            const SgPoint p = *it;
            if (m_bd.IsEmpty(p) && IsEmptyEdge(m_bd, p))
                rootFilter.push_back(p);
        }
    }

    return rootFilter;
}
コード例 #14
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
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;
    }
}
コード例 #15
0
ファイル: GoEyeUtil.cpp プロジェクト: Nopik/dragongoclient
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;
}