/** 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; }
bool Decompositions::FindSplitting(const HexBoard& brd, HexColor color, HexPoint& group) { // compute nbrs of color edges PointToBitset adjByMiai; ComputeAdjacentByMiai(brd, adjByMiai); const Groups& groups = brd.GetGroups(); HexPoint edge1 = HexPointUtil::colorEdge1(!color); HexPoint edge2 = HexPointUtil::colorEdge2(!color); bitset_t adjto1 = adjByMiai[edge1] | groups.Nbs(edge1, color); bitset_t adjto2 = adjByMiai[edge2] | groups.Nbs(edge2, color); // NOTE: must & with getCells() because we want non-edge groups; // this assumes that edges are always captains. bitset_t adjToBothEdges = adjto1 & adjto2 & brd.Const().GetCells(); // if there is a group adjacent to both opponent edges, return it. if (adjToBothEdges.any()) { group = groups.CaptainOf(static_cast<HexPoint> (BitsetUtil::FirstSetBit(adjToBothEdges))); return true; } return false; }
HexPoint EndgameUtil::PlayDeterminedState(const HexBoard& brd, HexColor color) { BenzeneAssert(HexColorUtil::isBlackWhite(color)); BenzeneAssert(IsDeterminedState(brd, color)); BenzeneAssert(!brd.GetGroups().IsGameOver()); if (IsWonGame(brd, color)) return PlayWonGame(brd, color); BenzeneAssert(IsLostGame(brd, color)); return PlayLostGame(brd, color); }
bool EndgameUtil::IsLostGame(const HexBoard& brd, HexColor color, bitset_t& proof) { if (brd.GetGroups().GetWinner() == !color) { proof = StonesInProof(brd, !color); return true; } VC vc; if (brd.Cons(!color).SmallestVC(HexPointUtil::colorEdge1(!color), HexPointUtil::colorEdge2(!color), VC::FULL, vc)) { proof = vc.Carrier() | StonesInProof(brd, !color); return true; } if (ComputeConsiderSet(brd, color).none()) { proof = brd.GetPosition().GetEmpty() | StonesInProof(brd, !color); return true; } return false; }
bool EndgameUtil::IsWonGame(const HexBoard& brd, HexColor color, bitset_t& proof) { if (brd.GetGroups().GetWinner() == color) { proof = StonesInProof(brd, color); return true; } VC vc; const HexPoint edge1 = HexPointUtil::colorEdge1(color); const HexPoint edge2 = HexPointUtil::colorEdge2(color); if (brd.Cons(color).SmallestVC(edge1, edge2, VC::SEMI, vc)) { proof = vc.Carrier() | StonesInProof(brd, color); return true; } if (brd.Cons(color).SmallestVC(edge1, edge2, VC::FULL, vc)) { proof = vc.Carrier() | StonesInProof(brd, color); return true; } return false; }
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; }
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()); }