SgPoint GoUctSearchUtil::TrompTaylorPassCheck(SgPoint move,
                                              const GoUctSearch& search)
{
    const GoBoard& bd = search.Board();
    bool isFirstPass = (bd.GetLastMove() != SG_PASS);
    bool isTrompTaylorRules = bd.Rules().CaptureDead();
    if (move != SG_PASS || ! isTrompTaylorRules || ! isFirstPass)
        return move;
    float komi = bd.Rules().Komi().ToFloat();
    float trompTaylorScore = GoBoardUtil::TrompTaylorScore(bd, komi);
    if (search.ToPlay() != SG_BLACK)
        trompTaylorScore *= -1;
    const SgUctTree& tree = search.Tree();
    const SgUctNode& root = tree.Root();
    SgUctValue value = root.Mean();
    SgUctValue trompTaylorWinValue = (trompTaylorScore > 0 ? 1 : 0);
    if (value < trompTaylorWinValue)
        return move;
    SgDebug() << "GoUctSearchUtil::TrompTaylorPassCheck: bad pass move value="
              << value << " trompTaylorScore=" << trompTaylorScore << '\n';
    std::vector<SgMove> excludeMoves;
    excludeMoves.push_back(SG_PASS);
    const SgUctNode* bestChild = search.FindBestChild(root, &excludeMoves);
    if (bestChild == 0)
    {
        SgDebug() <<
            "GoUctSearchUtil::TrompTaylorPassCheck: "
            "(no second best move found)\n";
        return move;
    }
    return bestChild->Move();
}
bool DfpnSolver::CheckAbort()
{
    if (! m_aborted)
    {
        if (SgUserAbort()) 
        {
            m_aborted = true;
            SgDebug() << "DfpnSolver::CheckAbort(): Abort flag!\n";
        }
        else if (m_timelimit > 0)
        {
            if (m_checkTimerAbortCalls == 0)
            {
                double elapsed = m_timer.GetTime();
                if (elapsed > m_timelimit)
                {
                    m_aborted = true;
                    SgDebug() << "DfpnSolver::CheckAbort(): Timelimit!\n";
                }
                else if (m_numMIDcalls < 100)
                    m_checkTimerAbortCalls = 10;
                else
                {
                    size_t midsPerSec = static_cast<size_t>
                        (double(m_numMIDcalls) / elapsed);
                    m_checkTimerAbortCalls = midsPerSec / 2;
                }
            }
            else
                --m_checkTimerAbortCalls;
        }
    }
    return m_aborted;
}
void GoAutoBook::Merge(const GoAutoBook& other)
{
    SgDebug() << "GoAutoBook::Merge()\n";
    std::size_t newLeafs = 0;
    std::size_t newInternal = 0;
    std::size_t leafsInCommon = 0;
    std::size_t internalInCommon = 0;
    std::size_t leafToInternal = 0;
    for (Map::const_iterator it = other.m_data.begin();
         it != other.m_data.end(); ++it)
    {
        Map::iterator mine = m_data.find(it->first);
        SgBookNode newNode(it->second);
        if (mine == m_data.end())
        {
            m_data[it->first] = it->second;
            if (newNode.IsLeaf())
                newLeafs++;
            else
                newInternal++;
        }
        else
        {
            SgBookNode oldNode(mine->second);
            if (newNode.IsLeaf() && oldNode.IsLeaf())
            {
                newNode.m_heurValue = 0.5f * (newNode.m_heurValue 
                                             + oldNode.m_heurValue);
                m_data[it->first] = newNode;
                leafsInCommon++;
            }
            else if (! newNode.IsLeaf())
            {
                // Take the max of the count; can't just add them
                // together because then merging a book with itself
                // doubles the counts of everything, which doesn't
                // make sense. Need the parent of these books and do a
                // three-way merge if we want the counts to be
                // accurate after the merge.  I don't think it matters
                // that much.
                newNode.m_count = std::max(newNode.m_count, oldNode.m_count);
                m_data[it->first] = newNode;
                if (! oldNode.IsLeaf())
                    internalInCommon++;
                else 
                    leafToInternal++;
            }
        }
    }
    SgDebug() << "Statistics\n"
              << "New Leafs        " << newLeafs << '\n'
              << "New Internal     " << newInternal << '\n'
              << "Common Leafs     " << leafsInCommon << '\n'
              << "Common Internal  " << internalInCommon << '\n'
              << "Leaf to Internal " << leafToInternal << '\n';
}
GoAutoBook::GoAutoBook(const std::string& filename,
                       const GoAutoBookParam& param)
    : m_param(param), 
      m_filename(filename)
{
    std::ifstream is(filename.c_str());
    if (! is)
    {
        std::ofstream of(filename.c_str());
        if (! of)
            throw SgException("Invalid file name!");
        of.close();
    }
    else
    {
        while (is)
        {
            std::string line;
            std::getline(is, line);
            if (line.size() < 19)
                continue;
            std::string str;
            std::istringstream iss(line);
            iss >> str;
            SgHashCode hash;
            hash.FromString(str);
            SgBookNode node(line.substr(19));
            m_data[hash] = node;
        }
        SgDebug() << "GoAutoBook: Parsed " << m_data.size() << " lines.\n";
    }
}
void GoAutoBook::ImportHashValuePairs(std::istream& in)
{
    std::size_t count = 0;
    while (in)
    {
        SgHashCode hash;
        std::string hashStr;
        in >> hashStr;
        hash.FromString(hashStr);
        float value;
        if (! in) 
            break;
        in >> value;
        if (m_data.count(hash) == 0)
        {
            std::ostringstream os;
            os << "Unknown hash: " << hash << '\n';
            throw SgException(os.str());
        }
        SgBookNode node(m_data[hash]);
        node.m_heurValue = value;
        node.m_value = value;
        m_data[hash] = node;
        count++;
    }
    SgDebug() << "GoAutoBook::ImportHashValue: imported " 
              << count << " values.\n";
}
void DfpnSolver::PrintStatistics(SgEmptyBlackWhite winner,
                                 const PointSequence& pv) const
{
    std::ostringstream os;
    os << '\n'
       << SgWriteLabel("MID calls") << m_numMIDcalls << '\n'
       << SgWriteLabel("Generate moves") << m_generateMoves << '\n'
       << SgWriteLabel("Terminal") << m_numTerminal << '\n'
       << SgWriteLabel("Work") << m_numMIDcalls + m_numTerminal << '\n'
       << SgWriteLabel("Wasted Work") << m_totalWastedWork
       << " (" << (double(m_totalWastedWork) * 100.0 
                   / double(m_numMIDcalls + m_numTerminal)) << "%)\n"
       << SgWriteLabel("Elapsed Time") << m_timer.GetTime() << '\n'
       << SgWriteLabel("MIDs/sec") 
       << double(m_numMIDcalls) / m_timer.GetTime() << '\n'
       << SgWriteLabel("generates/sec")
       << double(m_generateMoves) / m_timer.GetTime() << '\n'
       << SgWriteLabel("Cnt prune sib") << m_prunedSiblingStats.Count() << '\n'
       << SgWriteLabel("Avg prune sib");
    m_prunedSiblingStats.Write(os);
    os << '\n' << SgWriteLabel("Move Index");
    m_moveOrderingIndex.Write(os);
    os << '\n' << SgWriteLabel("Move Percent");
    m_moveOrderingPercent.Write(os);
    os << '\n' << SgWriteLabel("Delta Increase");
    m_deltaIncrease.Write(os);
    os << '\n'
       << SgWriteLabel("Winner") << SgEBW(winner) << '\n';
    WriteMoveSequence(os, pv);
    os << '\n';
    if (m_hashTable)
        os << '\n' << *m_hashTable << '\n';
    SgDebug() << os.str();
}
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();
            }
        }
    }
}
std::vector< std::vector<SgMove> > GoAutoBook::ParseWorkList(std::istream& in)
{
    std::vector< std::vector<SgMove> > ret;
    while (in)
    {
        std::string line;
        std::getline(in, line);
        if (line == "")
            continue;
        std::vector<SgMove> var;
        std::istringstream in2(line);
        while (true)
        {
            std::string s;
            in2 >> s;
            if (! in2 || s == "|")
                break;
            std::istringstream in3(s);
            SgPoint p;
            in3 >> SgReadPoint(p);
            if (! in3)
                throw SgException("Invalid point");
            var.push_back(p);
        }
        ret.push_back(var);
    }
    SgDebug() << "GoAutoBook::ParseWorkList: Read " << ret.size() 
              << " variations.\n";
    return ret;
}
SgEmptyBlackWhite DfpnSolver::StartSearch(DfpnHashTable& hashTable, 
                                          PointSequence& pv,
                                          const DfpnBounds& maxBounds)
{
    m_aborted = false;
    m_hashTable = &hashTable;
    m_numTerminal = 0;
    m_numMIDcalls = 0;
    m_generateMoves = 0;
    m_totalWastedWork = 0;
    m_prunedSiblingStats.Clear();
    m_moveOrderingPercent.Clear();
    m_moveOrderingIndex.Clear();
    m_deltaIncrease.Clear();
    m_checkTimerAbortCalls = 0;

    // Skip search if already solved
    DfpnData data;
    if (TTRead(data) && data.m_bounds.IsSolved())
    {
        SgDebug() << "Already solved!\n";
        const SgEmptyBlackWhite toPlay = GetColorToMove();
        SgEmptyBlackWhite w = Winner(data.m_bounds.IsWinning(), toPlay);
        GetPVFromHash(pv);
        SgDebug() << SgEBW(w) << " wins!\n";
        WriteMoveSequence(SgDebug(), pv);
        return w;
    }

    m_timer.Start();
    DfpnHistory history;
    MID(maxBounds, history);
    m_timer.Stop();

    GetPVFromHash(pv);
    SgEmptyBlackWhite winner = SG_EMPTY;
    if (TTRead(data) && data.m_bounds.IsSolved())
    {
        const SgEmptyBlackWhite toPlay = GetColorToMove();
        winner = Winner(data.m_bounds.IsWinning(), toPlay);
    }
    PrintStatistics(winner, pv);

    if (m_aborted)
        SgWarning() << "Search aborted.\n";
    return winner;
}
Exemple #10
0
SgMove GoAutoBook::FindBestChild(GoAutoBookState& state) const
{
    std::size_t bestCount = 0;
    SgMove bestMove = SG_NULLMOVE;
    float bestScore = 100.0f;
    SgBookNode node;
    if (!Get(state, node))
        return SG_NULLMOVE;
    if (node.IsLeaf())
        return SG_NULLMOVE;
    for (GoBoard::Iterator it(state.Board()); it; ++it)
    {
        if (state.Board().IsLegal(*it))
        {
            state.Play(*it);
            if (m_disabled.count(state.GetHashCode()) > 0)
                SgDebug() << "Ignoring disabled move " 
                          << SgWritePoint(*it) << '\n';
            // NOTE: Terminal nodes aren't supported at this time, so 
            // we ignore them here.
            else if (Get(state, node) 
                     && !node.IsTerminal() 
                     && node.m_count >= m_param.m_usageCountThreshold)
            {
                if (m_param.m_selectType == GO_AUTOBOOK_SELECT_COUNT)
                {
                    // Select by count, tiebreak by value.
                    if (node.m_count > bestCount)
                    {
                        bestCount = node.m_count;
                        bestMove = *it;
                        bestScore = node.m_value;
                    }
                    // NOTE: do not have access to inverse function,
                    // so we're minimizing here as a temporary solution. 
                    else if (node.m_count == bestCount
                             && node.m_value < bestScore)
                    {
                        bestMove = *it;
                        bestScore = node.m_value;
                    }
                }
                else if (m_param.m_selectType == GO_AUTOBOOK_SELECT_VALUE)
                {
                    // NOTE: do not have access to inverse function,
                    // so we're minimizing here as a temporary solution. 
                    if (node.m_value < bestScore)
                    {
                        bestMove = *it;
                        bestScore = node.m_value;
                    }
                }
            }
            state.Undo();
        }
    }
    return bestMove;
}
void GoRegionBoard::ExecuteMovePrologue()
{
    if (! UpToDate()) // full recomputation
    {
        if (DEBUG_REGION_BOARD)
            SgDebug() << "recompute everything\n";
        GenBlocksRegions();
    }
}
void GoUctFeatureKnowledgeFactory::ReadWeights()
{
    m_weights = FeFeatureWeights::ReadDefaultWeights();
    SgDebug() << "GoUctFeatureKnowledgeFactory Read weights for "
              << m_weights.m_nuFeatures
              << " features with k = " << m_weights.m_k
              << ", minID = " << m_weights.m_minID
              << ", maxID = " << m_weights.m_maxID
              << '\n';
}
GoBlock* GoRegionBoard::GetBlock(const SgPointSet& boundary,
                                 SgBlackWhite color) const
{
    SG_ASSERT(UpToDate());

    for (SgVectorIteratorOf<GoBlock> it(AllBlocks(color)); it; ++it)
    {   if (boundary.SubsetOf((*it)->Stones()))
        /* */ return *it; /* */
    }

    SgDebug() << "ERROR: no block on set";
    const int size = Board().Size();
    boundary.Write(SgDebug(), size);
    SgDebug() << "blocks:";
    for (SgVectorIteratorOf<GoBlock> it(AllBlocks(color));it; ++it)
        (*it)->Stones().Write(SgDebug(), size);

    SG_ASSERT(false);
    return 0;
}
void GoSafetySolver::Merge(GoChain* c1, GoChain* c2,
                           GoRegion* r, bool bySearch)
{
    SG_ASSERT(! r->GetFlag(GO_REGION_USED_FOR_MERGE));
    r->SetFlag(GO_REGION_USED_FOR_MERGE, true);
    
    GoChainCondition* c = 0;
    if (bySearch)
        c = new GoChainCondition(GO_CHAIN_BY_SEARCH);
    else
    {
        SgPoint lib1, lib2;
        r->Find2FreeLibs(c1, c2, &lib1, &lib2);
        c = new GoChainCondition(GO_CHAIN_TWO_LIBERTIES_IN_REGION,
                                 lib1, lib2);
    }
    
    GoChain* m = new GoChain(c1, c2, c);

    SgBlackWhite color = c1->Color();
    bool found = Regions()->AllChains(color).Exclude(c1);
    SG_DEBUG_ONLY(found);
    SG_ASSERT(found);
    found = Regions()->AllChains(color).Exclude(c2);
    SG_ASSERT(found);
    Regions()->AllChains(color).Include(m);
    SG_ASSERT(Regions()->AllChains(color).UniqueElements());

    for (SgVectorIteratorOf<GoRegion>
         it(Regions()->AllRegions(color)); it; ++it)
    {
        GoRegion* r = *it;
        bool replace1 = r->ReplaceChain(c1, m);
        bool replace2 = r->ReplaceChain(c2, m);
        if (replace1 || replace2)
        {
            r->ReInitialize();
            r->ComputeFlag(GO_REGION_STATIC_1VITAL);
        }
    }

    if (DEBUG_MERGE_CHAINS)
    {
        SgDebug() << "\nmerge:";
        c1->WriteID(SgDebug());
        SgDebug() << " + ";
        c2->WriteID(SgDebug());
        SgDebug() << " = ";
        m->WriteID(SgDebug());
        SgDebug() << '\n';
    }

    delete c1;
    delete c2;
}
void GoUctSearch::DisplayGfx()
{
    SgDebug() << "gogui-gfx:\n";
    switch (m_liveGfx)
    {
    case GOUCT_LIVEGFX_COUNTS:
        GoUctUtil::GfxBestMove(*this, m_toPlay, SgDebug());
        GoUctUtil::GfxMoveValues(*this, m_toPlay, SgDebug());
        GoUctUtil::GfxCounts(Tree(), SgDebug());
        GoUctUtil::GfxStatus(*this, SgDebug());
        break;
    case GOUCT_LIVEGFX_SEQUENCE:
        GoUctUtil::GfxSequence(*this, m_toPlay, SgDebug());
        GoUctUtil::GfxStatus(*this, SgDebug());
        break;
    case GOUCT_LIVEGFX_NONE:
        SG_ASSERT(false); // Should only be called when LiveGfx is enabled
        break;
    }
    SgDebug() << '\n';
}
void GoRegionBoard::Clear()
{
    if (DEBUG_REGION_BOARD)
        SgDebug() << "Clear\n";
    for (SgBWIterator it; it; ++it)
    {
        SgBlackWhite color(*it);
        for (SgVectorIteratorOf<GoBlock> it1(AllBlocks(color)); it1; ++it1)
            delete *it1;
        for (SgVectorIteratorOf<GoRegion> it2(AllRegions(color)); it2; ++it2)
            delete *it2;
        for (SgVectorIteratorOf<GoChain> it3(AllChains(color)); it3; ++it3)
            delete *it3;
    }
    m_allBlocks[SG_BLACK].Clear();
    m_allBlocks[SG_WHITE].Clear();
    m_allRegions[SG_BLACK].Clear();
    m_allRegions[SG_WHITE].Clear();
    m_allChains[SG_BLACK].Clear();
    m_allChains[SG_WHITE].Clear();
    m_stack.Clear();
    m_code.Invalidate();
    m_invalid = true;
    m_computedHealthy = false;
    m_boardSize = m_board.Size();

    for (SgBWIterator it; it; ++it)
    {
        SgBlackWhite color(*it);
        for (SgPoint p = SgPointUtil::Pt(1, 1);
             p <= SgPointUtil::Pt(SG_MAX_SIZE, SG_MAX_SIZE);
             ++p)
        {
            m_region[color][p] = 0;
        }
    }
    for (SgPoint p = SgPointUtil::Pt(1, 1);
         p <= SgPointUtil::Pt(SG_MAX_SIZE, SG_MAX_SIZE); ++p)
        m_block[p] = 0;
}
void GoRegionBoard::OnUndoneMove()
// Called after a move has been undone. The board is guaranteed to be in
// a legal state.
{
    //SG_ASSERT(false); // incremental code is incomplete, do not call
    if (DEBUG_REGION_BOARD)
        SgDebug() << "OnUndoneMove " << '\n';

    const bool IS_UNDO = false;
    SgVectorOf<GoRegion> changed;

    for (int val = m_stack.PopEvent(); val != SG_NEXTMOVE;
         val = m_stack.PopEvent())
    {

        switch (val)
        {
            case REGION_REMOVE:
            {   GoRegion* r = static_cast<GoRegion*>(m_stack.PopPtr());
                AddRegion(r, IS_UNDO);
                changed.Insert(r);
            }
            break;
            case REGION_ADD:
            {   GoRegion* r = static_cast<GoRegion*>(m_stack.PopPtr());
                RemoveRegion(r, IS_UNDO);
            }
            break;
            case REGION_REMOVE_BLOCK:
            {   GoBlock* b = static_cast<GoBlock*>(m_stack.PopPtr());
                AddBlock(b, IS_UNDO);
                for (int nu = m_stack.PopInt(); nu > 0; --nu)
                {
                    GoRegion* r = static_cast<GoRegion*>(m_stack.PopPtr());
                    if (CHECK)
                        SG_ASSERT(! r->Blocks().Contains(b));
                    r->BlocksNonConst().PushBack(b);
                    changed.Insert(r);
                }
            }
            break;
            case REGION_ADD_BLOCK:
            {   GoBlock* b = static_cast<GoBlock*>(m_stack.PopPtr());
                RemoveBlock(b, IS_UNDO, true);
            }
            break;
            case REGION_ADD_STONE:
            {   GoRegion* r = static_cast<GoRegion*>(m_stack.PopPtr());
                SgPoint p = m_stack.PopInt();
                r->OnRemoveStone(p);
                m_region[r->Color()][p] = r;
                changed.Insert(r);
            }
            break;
            case REGION_ADD_STONE_TO_BLOCK:
            {   GoBlock* b = static_cast<GoBlock*>(m_stack.PopPtr());
                SgPoint p = m_stack.PopInt();
                b->RemoveStone(p);
                m_block[p] = 0;
            }
            break;
            default:
                SG_ASSERT(false);
        }
    }

    for (SgVectorIteratorOf<GoRegion> it(changed); it; ++it)
    {
        (*it)->ResetNonBlockFlags();
        (*it)->ComputeBasicFlags();
    }

    if (HEAVYCHECK)
    {
        for (SgBWIterator it; it; ++it)
        {
            SgBlackWhite color(*it);
            for (SgVectorIteratorOf<GoRegion> it(AllRegions(color)); it; ++it)
            {
                const GoRegion* r = *it;
                SG_UNUSED(r);
                SG_ASSERT(r->IsValid());
            }
        }
    }

    m_code = Board().GetHashCode();
    if (HEAVYCHECK)
        CheckConsistency();
}
void GoUctState::AssertionHandler::Run()
{
    m_state.Dump(SgDebug());
}
SgMove GoAutoBook::FindBestChild(GoAutoBookState& state) const
{
    std::size_t bestCount = 0;
    SgMove bestMove = SG_NULLMOVE;
    SgMove bestIgnoredMove = SG_NULLMOVE;
    float bestScore = 100.0f;
    float bestIgnoredMoveScore = 100.0f;
    SgBookNode node;
    // Check for forced moves first
    // Note this will check for forced moves even if the current
    // state is not in the book.
    for (GoBoard::Iterator it(state.Board()); it; ++it)
    {
        if (state.Board().IsLegal(*it))
        {
            state.Play(*it);
            if (m_forced.count(state.GetHashCode()) > 0)
            {
                SgDebug() << "Playing forced move " 
                          << SgWritePoint(*it) << '\n';
                return *it;
            }
            state.Undo();
        }
    }
    if (! Get(state, node))
        return SG_NULLMOVE;
    if (node.IsLeaf())
        return SG_NULLMOVE;
    for (GoBoard::Iterator it(state.Board()); it; ++it)
    {
        if (state.Board().IsLegal(*it))
        {
            state.Play(*it);
            if (m_disabled.count(state.GetHashCode()) > 0)
                SgDebug() << "Ignoring disabled move " 
                          << SgWritePoint(*it) << '\n';
            // NOTE: Terminal nodes aren't supported at this time, so 
            // we ignore them here.
            else if (  Get(state, node) 
                    && ! node.IsTerminal() 
                    )
            {
            	if (node.m_count >= m_param.m_usageCountThreshold)
                {
                    if (m_param.m_selectType == GO_AUTOBOOK_SELECT_COUNT)
                    {
                        // Select by count, tiebreak by value.
                        if (node.m_count > bestCount)
                        {
                            bestCount = node.m_count;
                            bestMove = *it;
                            bestScore = node.m_value;
                        }
                        // NOTE: do not have access to inverse function,
                        // so we're minimizing here as a temporary solution. 
                        else if (node.m_count == bestCount
                                 && node.m_value < bestScore)
                        {
                            bestMove = *it;
                            bestScore = node.m_value;
                        }
                    }
                    else if (m_param.m_selectType == GO_AUTOBOOK_SELECT_VALUE)
                    {
                        // NOTE: do not have access to inverse function,
                        // so we're minimizing here as a temporary solution. 
                        if (node.m_value < bestScore)
                        {
                            bestMove = *it;
                            bestScore = node.m_value;
                        }
                    }
                }
                else // node.m_count < m_param.m_usageCountThreshold
                if (  m_param.m_selectType == GO_AUTOBOOK_SELECT_VALUE
                   && node.m_value < bestIgnoredMoveScore
                   )
                {
                   bestIgnoredMove = *it;
                   bestIgnoredMoveScore = node.m_value;
                }
            }
            state.Undo();
        }
    }
    if (bestMove != SG_NULLMOVE && bestIgnoredMoveScore < bestScore)
    {
        SgDebug() << "Ignoring autobook move "
        << SgWritePoint(bestMove)
        << " since best ignored inverse value " 
        << std::setprecision(5)
        << bestIgnoredMoveScore
        << " of move " 
        << SgWritePoint(bestIgnoredMove)
        << " is better than best value above usage threshold " 
        << bestScore << '\n';
        
    	return SG_NULLMOVE;
    }
    return bestMove;
}