bool Decompositions::Find(const HexBoard& brd, HexColor color,
                          bitset_t& captured)
{
    // If game is over or decided, don't do any work.
    HexPoint edge1 = HexPointUtil::colorEdge1(color);
    HexPoint edge2 = HexPointUtil::colorEdge2(color);
    const VCSet& cons = brd.Cons(color);
    if (brd.GetGroups().IsGameOver() || cons.Exists(edge1, edge2, VC::FULL)) 
    {
	captured.reset();
	return false;
    }
    
    /** Compute neighbouring groups of opposite color.

        NOTE: Assumes that edges that touch are adjacent. See
        ConstBoard for more details. 
    */
    PointToBitset adjTo;
    PointToBitset adjByMiai;
    ComputeAdjacentByMiai(brd, adjByMiai);
    for (GroupIterator g(brd.GetGroups(), color); g; ++g) 
    {
	bitset_t opptNbs = adjByMiai[g->Captain()] 
            | (g->Nbs() & brd.GetPosition().GetColor(!color));
	if (opptNbs.count() >= 2)
	    adjTo[g->Captain()] = opptNbs;
    }
    // The two color edges are in the list. If no other groups are, then quit.
    BenzeneAssert(adjTo.size() >= 2);
    if (adjTo.size() == 2) 
    {
	captured.reset();
	return false;
    }
    
    // Compute graph representing board from color's perspective.
    PointToBitset graphNbs;
    GraphUtil::ComputeDigraph(brd.GetGroups(), color, graphNbs);
    
    // Find (ordered) pairs of color groups that are VC-connected and have at
    // least two adjacent opponent groups in common.
    PointToBitset::iterator g1, g2;
    for (g1 = adjTo.begin(); g1 != adjTo.end(); ++g1) {
	for (g2 = adjTo.begin(); g2 != g1; ++g2) {
	    if ((g1->second & g2->second).count() < 2) continue;
	    if (!cons.Exists(g1->first, g2->first, VC::FULL)) continue;
	    
	    // This is such a pair, so at least one of the two is not an edge.
	    // Find which color edges are not equal to either of these groups.
	    BenzeneAssert(!HexPointUtil::isEdge(g1->first) ||
                          !HexPointUtil::isEdge(g2->first));
	    bool edge1Free = (g1->first != edge1 && g2->first != edge1);
	    bool edge2Free = (g1->first != edge2 && g2->first != edge2);
	    
	    // Find the set of empty cells bounded by these two groups.
	    bitset_t stopSet = graphNbs[g1->first] | graphNbs[g2->first];
	    bitset_t decompArea;
	    decompArea.reset();
	    if (edge1Free)
		decompArea |= GraphUtil::BFS(edge1, graphNbs, stopSet);
	    if (edge2Free)
		decompArea |= GraphUtil::BFS(edge2, graphNbs, stopSet);
	    decompArea.flip();
	    decompArea &= brd.GetPosition().GetEmpty();
	    
	    // If the pair has a VC confined to these cells, then we have
	    // a decomposition - return it.
	    const VCList& vl = cons.GetList(VC::FULL, g1->first, g2->first);
	    for (VCListConstIterator it(vl); it; ++it)
            {
		if (BitsetUtil::IsSubsetOf(it->Carrier(), decompArea)) 
                {
		    captured = it->Carrier();
		    return true;
		}
	    }
	}
    }
    
    // No combinatorial decomposition with a VC was found.
    captured.reset();
    return false;
}
/** Does a 1-ply search.

    For each move in the consider set, if the move is a win, returns
    true and the move. If the move is a loss, prune it out of the
    consider set if there are non-losing moves in the consider set.
    If all moves are losing, perform no pruning, search will resist.

    Returns true if there is a win, false otherwise. 

    @todo Is it true that MoHex will resist in the strongest way
    possible?
*/
bool MoHexPlayer::PerformPreSearch(HexBoard& brd, HexColor color, 
                                   bitset_t& consider, double maxTime, 
                                   PointSequence& winningSequence)
{
   
    bitset_t losing;
    HexColor other = !color;
    PointSequence seq;
    bool foundWin = false;

    SgTimer elapsed;
    Resistance resist;
    resist.Evaluate(brd);
    std::vector<HexPoint> moves;
    SortConsiderSet(consider, resist, moves);
    for (std::size_t i = 0; i < moves.size() && !foundWin; ++i) 
    {
        if (elapsed.GetTime() > maxTime)
        {
            LogInfo() << "PreSearch: max time reached "
                      << '(' << i << '/' << moves.size() << ").\n";
            break;
        }
        brd.PlayMove(color, moves[i]);
        seq.push_back(moves[i]);
        if (EndgameUtil::IsLostGame(brd, other)) // Check for winning move
        {
            winningSequence = seq;
            foundWin = true;
        }	
        else if (EndgameUtil::IsWonGame(brd, other))
            losing.set(moves[i]);
        seq.pop_back();
        brd.UndoMove();
    }

    // Abort if we found a one-move win
    if (foundWin)
        return true;

    // Backing up cannot cause this to happen, right? 
    BenzeneAssert(!EndgameUtil::IsDeterminedState(brd, color));

    // Use the backed-up ice info to shrink the moves to consider
    if (m_backup_ice_info) 
    {
        bitset_t new_consider 
            = EndgameUtil::MovesToConsider(brd, color) & consider;

        if (new_consider.count() < consider.count()) 
        {
            consider = new_consider;       
            LogFine() << "$$$$$$ new moves to consider $$$$$$" 
                      << brd.Write(consider) << '\n';
        }
    }

    // Subtract any losing moves from the set we consider, unless all of them
    // are losing (in which case UCT search will find which one resists the
    // loss well).
    if (losing.any()) 
    {
	if (BitsetUtil::IsSubsetOf(consider, losing)) 
	    LogInfo() << "************************************\n"
                      << " All UCT root children are losing!!\n"
                      << "************************************\n";
        else 
        {
            LogFine() << "Removed losing moves: " << brd.Write(losing) << '\n';
	    consider = consider - losing;
	}
    }

    BenzeneAssert(consider.any());
    LogInfo() << "Moves to consider:\n" << brd.Write(consider) << '\n';
    return false;
}
HexPoint MoHexPlayer::Search(const HexState& state, const Game& game,
                             HexBoard& brd, const bitset_t& given_to_consider,
                             double maxTime, double& score)
{
    BenzeneAssert(!brd.GetGroups().IsGameOver());
    HexColor color = state.ToPlay();   
   
    SgTimer totalElapsed;
    PrintParameters(color, maxTime);

    // Do presearch and abort if win found. Allow it to take 20% of
    // total time.
    SgTimer timer;
    bitset_t consider = given_to_consider;
    PointSequence winningSequence;
    if (m_performPreSearch && PerformPreSearch(brd, color, consider, 
                                               maxTime * 0.2, winningSequence))
    {
	LogInfo() << "Winning sequence found before UCT search!\n"
		  << "Sequence: " << winningSequence[0] << '\n';
        score = IMMEDIATE_WIN;
	return winningSequence[0];
    }
    timer.Stop();
    LogInfo() << "Time for PreSearch: " << timer.GetTime() << "s\n";

    maxTime -= timer.GetTime();
    maxTime = std::max(1.0, maxTime);
        
    // Create the initial state data
    MoHexSharedData data(m_search.FillinMapBits());
    data.gameSequence = game.History();
    data.rootState = HexState(brd.GetPosition(), color);
    data.rootConsider = consider;
    
    // Reuse the old subtree
    SgUctTree* initTree = 0;
    if (m_reuse_subtree)
    {
        MoHexSharedData oldData(m_search.SharedData());
        initTree = TryReuseSubtree(oldData, data);
        if (!initTree)
            LogInfo() << "No subtree to reuse.\n";
    }
    m_search.SetSharedData(data);

    brd.GetPatternState().ClearPatternCheckStats();
    int old_radius = brd.GetPatternState().UpdateRadius();
    brd.GetPatternState().SetUpdateRadius(m_search.TreeUpdateRadius());

    // Do the search
    std::vector<SgMove> sequence;
    std::vector<SgMove> rootFilter;
    m_search.SetBoard(brd);
    score = m_search.Search(m_max_games, maxTime, sequence,
                            rootFilter, initTree, 0);

    brd.GetPatternState().SetUpdateRadius(old_radius);

    // Output stats
    std::ostringstream os;
    os << '\n';
#if COLLECT_PATTERN_STATISTICS
    {
        MoHexState& thread0 
            = dynamic_cast<MoHexState&>(m_search.ThreadState(0));
        MoHexPolicy* policy = dynamic_cast<MoHexPolicy*>(thread0.Policy());
        if (policy)
            os << policy->DumpStatistics() << '\n';
    }
#endif
    os << "Elapsed Time   " << totalElapsed.GetTime() << "s\n";
    m_search.WriteStatistics(os);
    os << "Score          " << std::setprecision(2) << score << "\n"
       << "Sequence      ";
    for (std::size_t i = 0; i < sequence.size(); i++)
        os << " " << MoHexUtil::MoveString(sequence[i]);
    os << '\n';
    LogInfo() << os.str() << '\n';

#if 0
    if (m_save_games) 
    {
        std::string filename = "uct-games.sgf";
        uct.SaveGames(filename);
        LogInfo() << "Games saved in '" << filename << "'.\n";
    }
#endif

    // Return move recommended by MoHexSearch
    if (sequence.size() > 0) 
        return static_cast<HexPoint>(sequence[0]);

    // It is possible that MoHexSearch did only 1 simulation (probably
    // because it ran out of time to do more); in this case, the move
    // sequence is empty and so we give a warning and return a random
    // move.
    LogWarning() << "**** MoHexSearch returned empty sequence!\n"
		 << "**** Returning random move!\n";
    return BoardUtil::RandomEmptyCell(brd.GetPosition());
}
Esempio n. 4
0
TEST_F(HexBoardTest,HexBoardBasicFuncs) {
  //2. test setNodeValue
  //3. test setNumofhexgons
  //4. test resetHexBoard();
  int numofhexgon = 5;
  int numofedges = (numofhexgon * numofhexgon * 2 + 6);

  HexBoard board(numofhexgon);
  HexBoard playersboard;
  playersboard.setNumofhexgons(numofhexgon);

  ASSERT_EQ(board.getSizeOfVertices(), numofhexgon * numofhexgon);
  ASSERT_EQ(board.getSizeOfEdges(), numofedges);
  ASSERT_EQ(playersboard.getSizeOfVertices(), numofhexgon * numofhexgon);
  ASSERT_EQ(playersboard.getSizeOfEdges(), 0);
  int k = 0;
  for (int i = 1; i <= numofhexgon * numofhexgon; ++i) {
    hexgonValKind playerkind;
    if (i % 2 == 0)
      playerkind = hexgonValKind_RED;
      else
      playerkind = hexgonValKind_BLUE;

    ASSERT_EQ(hexgonValKind_EMPTY, board.getNodeValue(i));
    ASSERT_EQ(hexgonValKind_EMPTY, playersboard.getNodeValue(i));
    playersboard.setEdgeValue(i);
    vector<int> neighbors = playersboard.getNeighbors(i);
    if (i % 2 == 0) {
      board.setNodeValue(i, playerkind);
      playersboard.setNodeValue(i, playerkind);
      EXPECT_EQ(playerkind, board.getNodeValue(i));
      EXPECT_EQ(playerkind, playersboard.getNodeValue(i));
    } else {
      board.setNodeValue(i, playerkind);
      EXPECT_EQ(playerkind, board.getNodeValue(i));
      EXPECT_EQ(hexgonValKind_EMPTY, playersboard.getNodeValue(i));
    }
#if __cplusplus > 199711L
    for (auto j : neighbors) {
#else
      for (vector<int>::iterator iter = neighbors.begin(); iter != neighbors.end(); ++iter) {
        int j = *iter;
#endif
      if (playersboard.getNodeValue(j) != playerkind)
        playersboard.deleteEdge(i, j);
    }
    if (i % 6 == 0 || i == 8 || i == 14 || i == 16 || i == 22)
      k++;
    EXPECT_EQ(playersboard.getSizeOfEdges(), k);
  }
  EXPECT_EQ(board.getNumofemptyhexgons(), 0);
  EXPECT_EQ(playersboard.getNumofemptyhexgons(), 13);
  EXPECT_EQ(playersboard.getSizeOfEdges(), 8);
  EXPECT_EQ(board.getSizeOfVertices(), numofhexgon * numofhexgon);
  EXPECT_EQ(playersboard.getSizeOfVertices(), numofhexgon * numofhexgon);
  int connectnode[8] = { 2, 4, 8, 10, 12, 14, 18, 20 };
  for (unsigned i = 0; i < 8; i++){
    EXPECT_FALSE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 3)));
    EXPECT_TRUE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 4)));
    EXPECT_FALSE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 5)));
  }

  board.resetHexBoard(false);
  EXPECT_EQ(board.getSizeOfVertices(), board.getNumofemptyhexgons());
  EXPECT_EQ(board.getSizeOfEdges(), numofedges);
  EXPECT_EQ(8, playersboard.getSizeOfEdges());
  playersboard.resetHexBoard();
  playersboard.setNumofhexgons(numofhexgon);
  EXPECT_EQ(numofhexgon, playersboard.getNumofhexgons());
  EXPECT_EQ(numofhexgon * numofhexgon, playersboard.getSizeOfVertices());
  EXPECT_EQ(playersboard.getSizeOfVertices(),
            playersboard.getNumofemptyhexgons());
  EXPECT_EQ(0, playersboard.getSizeOfEdges());
  for (unsigned i = 0; i < 8; i++){
    EXPECT_FALSE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 3)));
    EXPECT_FALSE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 4)));
    EXPECT_FALSE(playersboard.isAdjacent(connectnode[i], (connectnode[i] + 5)));
  }
  for (int i = 1; i <= numofhexgon * numofhexgon; ++i) {
    EXPECT_EQ(hexgonValKind_EMPTY, board.getNodeValue(i));
    EXPECT_EQ(hexgonValKind_EMPTY, playersboard.getNodeValue(i));
  }
  playersboard.resetHexBoard();
  playersboard.setNumofhexgons(3);
  EXPECT_EQ(playersboard.getSizeOfVertices(), 9);

  for (int i = 1; i <= 9; ++i)
    EXPECT_EQ(hexgonValKind_EMPTY, playersboard.getNodeValue(i));
}
TEST_F(HexBoardTest,HexBoardWinningTest) {
  //test 5x5 Hexboard
  HexBoard board(5);
  Game hexboardgame(board);

  Player playera(board, hexgonValKind_RED);
  Player playerb(board, hexgonValKind_BLUE);
  //generate a sequence of paths
  ASSERT_TRUE(hexboardgame.setMove(playera, 1, 1));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playerb, 1, 2));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playera, 2, 1));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playerb, 2, 2));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playera, 3, 1));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playerb, 3, 2));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playera, 4, 1));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playerb, 4, 2));
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  ASSERT_TRUE(hexboardgame.setMove(playera, 5, 1));
  EXPECT_EQ("RED", hexboardgame.getWinner(playera, playerb));
  hexboardgame.resetGame(playera, playerb);
  EXPECT_EQ("UNKNOWN", hexboardgame.getWinner(playera, playerb));
  hexboardgame.showView(playera, playerb);
}
Esempio n. 5
0
TEST_F(HexBoardTest,HexBoardSetMove) {
  //test 5x5 Hexboard
  HexBoard board(5);
  Game hexboardgame(board);
  Player playera(board, hexgonValKind_RED);

  //test with private set setPlayerBoard method and corresponding MST tree
  ASSERT_TRUE(hexboardgame.setMove(playera, 1, 1));
  HexBoard playerasboard = playera.getPlayersboard();
  EXPECT_EQ(0, playerasboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 1);
  ASSERT_TRUE(hexboardgame.setMove(playera, 2, 1));
  playerasboard = playera.getPlayersboard();
  EXPECT_EQ(1, playerasboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 2);
  ASSERT_TRUE(hexboardgame.setMove(playera, 3, 1));
  playerasboard = playera.getPlayersboard();
  EXPECT_EQ(2, playerasboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 3);
  ASSERT_TRUE(hexboardgame.setMove(playera, 4, 1));
  playerasboard = playera.getPlayersboard();
  EXPECT_EQ(3, playerasboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 4);
  ASSERT_TRUE(hexboardgame.setMove(playera, 5, 1));
  playerasboard = playera.getPlayersboard();
  EXPECT_EQ(4, playerasboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 5);

  MinSpanTreeAlgo<hexgonValKind, int> mstalgo(playerasboard);
  MinSpanTreeAlgo<hexgonValKind, int>::UnionFind unionfind(mstalgo);
  mstalgo.calculate(unionfind);

  Graph<hexgonValKind, int> msttree = mstalgo.getMsttree();
  EXPECT_EQ(4, msttree.getSizeOfEdges());
  vector<vector<int> > subgraphs = msttree.getAllSubGraphs();
  EXPECT_EQ(1, subgraphs.size());

  Player playerb(board, hexgonValKind_BLUE);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 1, 2));
  HexBoard playerbsboard = playerb.getPlayersboard();
  EXPECT_EQ(0, playerbsboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 6);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 2, 2));
  playerbsboard = playerb.getPlayersboard();
  EXPECT_EQ(1, playerbsboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 7);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 3, 2));
  playerbsboard = playerb.getPlayersboard();
  EXPECT_EQ(2, playerbsboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 8);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 4, 2));
  playerbsboard = playerb.getPlayersboard();
  EXPECT_EQ(3, playerbsboard.getSizeOfEdges());
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 9);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 2, 5));
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 10);
  ASSERT_TRUE(hexboardgame.setMove(playerb, 3, 5));
  EXPECT_EQ(board.getNumofemptyhexgons(), board.getSizeOfVertices() - 11);
  playerbsboard = playerb.getPlayersboard();
  EXPECT_EQ(4, playerbsboard.getSizeOfEdges());

  MinSpanTreeAlgo<hexgonValKind, int> mstalgob(playerbsboard);
  MinSpanTreeAlgo<hexgonValKind, int>::UnionFind unionfindb(mstalgob);
  mstalgo.calculate(unionfindb);
  Graph<hexgonValKind, int> msttreeb = mstalgob.getMsttree();
  EXPECT_EQ(4, msttreeb.getSizeOfEdges());
  vector<vector<int> > subgraphsb = msttreeb.getAllSubGraphs();
  EXPECT_EQ(2, subgraphsb.size());
}
/** Finds inferior cells, builds vcs. Sets moves to consider to all
    empty cells. If fillin causes terminal state, sets
    m_fillinCausedWin to true and recomputes fillin/vcs with ice
    temporarily turned off (so it can pass the players a non-empty
    consider set).
*/
HexPoint BenzenePlayer::InitSearch(HexBoard& brd, HexColor color, 
                                   bitset_t& consider, double& score)
{
    // Resign if the game is already over
    Groups groups;
    GroupBuilder::Build(brd.GetPosition(), groups);
    if (groups.IsGameOver()) 
    {
        score = IMMEDIATE_LOSS;
        return RESIGN;
    }
    StoneBoard original(brd.GetPosition());
    brd.ComputeAll(color);
    m_fillinCausedWin = false;
    m_fillinWinner = EMPTY;
    if (brd.GetGroups().IsGameOver()) 
    {
        // Fillin caused win, remove and re-compute without ice.
        m_fillinCausedWin = true;
        m_fillinWinner = brd.GetGroups().GetWinner();
        LogInfo() << "Captured cells caused win! Removing...\n";
        brd.GetPosition().SetPosition(original);
        bool oldUseIce = brd.UseICE();
        brd.SetUseICE(false);
        brd.ComputeAll(color);
        brd.SetUseICE(oldUseIce);
        BenzeneAssert(!brd.GetGroups().IsGameOver());
    } 
    consider = brd.GetPosition().GetEmpty();
    score = 0;
    return INVALID_POINT;
}