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()); }
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); }
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; }