/** 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; }
bitset_t ProofUtil::InitialProofForOpponent(const HexBoard& brd, HexColor toPlay) { // Add opponent played stones and deduction set. const InferiorCells& inf = brd.GetInferiorCells(); bitset_t proof = brd.GetPosition().GetPlayed(!toPlay); proof |= inf.DeductionSet(!toPlay); // Add all semi-connections from the mustplay. const VCList& lst = brd.Cons(!toPlay).GetList(VC::SEMI, HexPointUtil::colorEdge1(!toPlay), HexPointUtil::colorEdge2(!toPlay)); const bool useGreedy = brd.Builder().Parameters().use_greedy_union; proof |= useGreedy ? lst.getGreedyUnion() : lst.getUnion(); // Add reversable reversers. // The carriers do NOT need to be included in the proof, since // they are captured by the (losing) player, not his opponent (for // whom we are building the proof set). // TODO: Currently, we just add the first reverser: we should see // if any reverser is already in the proof, since then we wouldn't // need to add one. for (BitsetIterator p(inf.Reversible()); p; ++p) { const std::set<HexPoint>& reversers = inf.Reversers(*p); proof.set(*reversers.begin()); } // Add vulnerable killers and their carriers. // TODO: Currently, we just add the first killer: we should see if // any killer is already in the proof, since then we wouldn't need // to add one. for (BitsetIterator p(inf.Vulnerable()); p; ++p) { const std::set<VulnerableKiller>& killers = inf.Killers(*p); proof.set((*killers.begin()).killer()); proof |= ((*killers.begin()).carrier()); } return proof; }
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; }
bitset_t ProofUtil::MaximumProofSet(const HexBoard& brd, HexColor toPlay) { return brd.GetPosition().GetEmpty() | brd.GetPosition().GetPlayed(toPlay) | brd.GetInferiorCells().DeductionSet(toPlay); }
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()); }