00001 //---------------------------------------------------------------------------- 00002 /** @file ICEngine.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "Time.hpp" 00007 #include "BitsetIterator.hpp" 00008 #include "ICEngine.hpp" 00009 #include "BoardUtils.hpp" 00010 00011 #include "boost/filesystem/path.hpp" 00012 00013 using namespace benzene; 00014 00015 //---------------------------------------------------------------------------- 00016 00017 namespace { 00018 00019 //---------------------------------------------------------------------------- 00020 00021 /** Returns set of cells not reachable from either edge. These areas 00022 are dead, but may not be identified via patterns, etc. Thus we use 00023 a BFS-type algorithm, checking which areas we can reach from an 00024 edge without going through the opposite edge or stones of the 00025 opponent's colour. Note that if the game is already decided, all 00026 remaining empty cells are dead. 00027 */ 00028 bitset_t ComputeEdgeUnreachableRegions(const StoneBoard& brd, HexColor c, 00029 const bitset_t& stopSet, 00030 bool flowFrom1=true, 00031 bool flowFrom2=true) 00032 { 00033 bitset_t reachable1, reachable2; 00034 bitset_t flowSet = (brd.GetEmpty() | brd.GetColor(c)) 00035 & brd.Const().GetCells(); 00036 if (flowFrom1) 00037 { 00038 bitset_t flowSet1 = flowSet; 00039 flowSet1.set(HexPointUtil::colorEdge1(c)); 00040 reachable1 00041 = BoardUtils::ReachableOnBitset(brd.Const(), flowSet1, stopSet, 00042 HexPointUtil::colorEdge1(c)); 00043 } 00044 if (flowFrom2) 00045 { 00046 bitset_t flowSet2 = flowSet; 00047 flowSet2.set(HexPointUtil::colorEdge2(c)); 00048 reachable2 00049 = BoardUtils::ReachableOnBitset(brd.Const(), flowSet2, stopSet, 00050 HexPointUtil::colorEdge2(c)); 00051 } 00052 return brd.GetEmpty() - (reachable1 | reachable2); 00053 } 00054 00055 /** Computes dead regions on the board created by a single group's 00056 neighbour set. This finds dead regions that cannot be identified 00057 using only local patterns/properties. 00058 */ 00059 bitset_t ComputeDeadRegions(const Groups& groups) 00060 { 00061 const StoneBoard& brd = groups.Board(); 00062 if (groups.IsGameOver()) 00063 return brd.GetEmpty(); 00064 00065 bitset_t dead; 00066 for (GroupIterator i(groups, NOT_EMPTY); i; ++i) 00067 { 00068 /** @note We believe single stone groups cannot isolate regions by 00069 themselves (i.e. they need to be combined with a non-singleton 00070 group to create a dead region. This should be proven [Phil]. */ 00071 if (i->Size() == 1) 00072 continue; 00073 00074 HexColor c = i->Color(); 00075 HexAssert(HexColorUtil::isBlackWhite(c)); 00076 00077 /** Compute which empty cells are reachable from the edges when we 00078 cannot go through this group's empty neighbours (which form a 00079 clique). If the clique covers one edge, we only compute 00080 reachability from the opposite edge. */ 00081 bitset_t cliqueCutset = i->Nbs() & brd.GetEmpty(); 00082 dead |= ComputeEdgeUnreachableRegions(brd, c, cliqueCutset, 00083 i->Captain() != HexPointUtil::colorEdge1(c), 00084 i->Captain() != HexPointUtil::colorEdge2(c)); 00085 } 00086 00087 // Areas not reachable due to one or more clique cutsets are dead. 00088 HexAssert(BitsetUtil::IsSubsetOf(dead, brd.GetEmpty())); 00089 return dead; 00090 } 00091 00092 /** Finds dead regions formed by one group as well as a single cell adjacent 00093 to two of the group's neighbours (but not the group itself). 00094 */ 00095 bitset_t FindType1Cliques(const Groups& groups) 00096 { 00097 bitset_t dead; 00098 const StoneBoard& brd = groups.Board(); 00099 bitset_t empty = brd.GetEmpty(); 00100 00101 // Find two cells that are adjacent through some group, but not directly. 00102 for (BitsetIterator x(empty); x; ++x) { 00103 for (BitsetIterator y(empty); *y != *x; ++y) { 00104 if (brd.Const().Adjacent(*x, *y)) continue; 00105 bitset_t xyNbs 00106 = groups.Nbs(*x, NOT_EMPTY) & groups.Nbs(*y, NOT_EMPTY); 00107 if (xyNbs.none()) continue; 00108 00109 // Find a 3rd cell directly adjacent to the first two, but not 00110 // adjacent to some group that connects them. 00111 for (BitsetIterator z(empty); z; ++z) { 00112 if (!brd.Const().Adjacent(*x, *z)) continue; 00113 if (!brd.Const().Adjacent(*y, *z)) continue; 00114 HexAssert(*x != *z); 00115 HexAssert(*y != *z); 00116 bitset_t xyExclusiveNbs = xyNbs - groups.Nbs(*z, NOT_EMPTY); 00117 if (xyExclusiveNbs.none()) continue; 00118 00119 // The 3 cells x,y,z form a clique. 00120 bitset_t clique; 00121 clique.set(*x); 00122 clique.set(*y); 00123 clique.set(*z); 00124 00125 // The specific group(s) common to x and y do not affect the 00126 // stop set, so we check reachability at most once per color. 00127 if ((xyExclusiveNbs & brd.GetBlack()).any()) { 00128 dead |= ComputeEdgeUnreachableRegions(brd, BLACK, clique); 00129 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00130 } 00131 if ((xyExclusiveNbs & brd.GetWhite()).any()) { 00132 dead |= ComputeEdgeUnreachableRegions(brd, WHITE, clique); 00133 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00134 } 00135 } 00136 } 00137 } 00138 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00139 return dead; 00140 } 00141 00142 /** Finds dead regions formed by two groups of the same color, using 00143 common empty neighbours and a direct adjacency between two of 00144 their exclusive neighbours. 00145 */ 00146 bitset_t FindType2Cliques(const Groups& groups) 00147 { 00148 const StoneBoard& brd = groups.Board(); 00149 bitset_t dead; 00150 bitset_t empty = brd.GetEmpty(); 00151 00152 // Find two non-edge groups of the same color with both common 00153 // empty neighbours in common and also exclusive empty neighbours. 00154 for (BWIterator c; c; ++c) { 00155 for (GroupIterator g1(groups, *c); g1; ++g1) { 00156 if (HexPointUtil::isEdge(g1->Captain())) continue; 00157 bitset_t g1_nbs = groups.Nbs(*g1, EMPTY); 00158 00159 for (GroupIterator g2(groups, *c); &*g2 != &*g1; ++g2) { 00160 if (HexPointUtil::isEdge(g2->Captain())) continue; 00161 bitset_t g2_nbs = groups.Nbs(*g2, EMPTY); 00162 if ((g1_nbs & g2_nbs).none()) continue; 00163 00164 bitset_t g1Exclusive = g1_nbs - g2_nbs; 00165 if (g1Exclusive.none()) continue; 00166 bitset_t g2Exclusive = g2_nbs - g1_nbs; 00167 if (g2Exclusive.none()) continue; 00168 00169 // Now find two cells exclusive neighbours of these two 00170 // groups that are directly adjacent to one another. 00171 for (BitsetIterator x(g1Exclusive); x; ++x) { 00172 for (BitsetIterator y(g2Exclusive); y; ++y) { 00173 if (!brd.Const().Adjacent(*x, *y)) continue; 00174 00175 // Cells x, y and the common neighbours of 00176 // groups g1, g2 form a clique. 00177 bitset_t clique = g1_nbs & g2_nbs; 00178 clique.set(*x); 00179 clique.set(*y); 00180 dead |= ComputeEdgeUnreachableRegions(brd, *c, clique); 00181 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00182 } 00183 } 00184 } 00185 } 00186 } 00187 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00188 return dead; 00189 } 00190 00191 /** Finds dead regions cutoff by cliques created by 3 groups of the 00192 same color. 00193 */ 00194 bitset_t FindType3Cliques(const Groups& groups) 00195 { 00196 const StoneBoard& brd = groups.Board(); 00197 bitset_t dead; 00198 bitset_t empty = brd.GetEmpty(); 00199 00200 // Find 3 non-edge groups of the same color such that each pair has 00201 // a non-empty intersection of their empty neighbours. 00202 for (BWIterator c; c; ++c) { 00203 for (GroupIterator g1(groups, *c); g1; ++g1) { 00204 if (HexPointUtil::isEdge(g1->Captain())) continue; 00205 bitset_t g1_nbs = groups.Nbs(*g1, EMPTY); 00206 00207 for (GroupIterator g2(groups, *c); &*g2 != &*g1; ++g2) { 00208 if (HexPointUtil::isEdge(g2->Captain())) continue; 00209 bitset_t g2_nbs = groups.Nbs(*g2, EMPTY); 00210 if ((g1_nbs & g2_nbs).none()) continue; 00211 00212 for (GroupIterator g3(groups, *c); &*g3 != &*g2; ++g3) { 00213 if (HexPointUtil::isEdge(g3->Captain())) continue; 00214 bitset_t g3_nbs = groups.Nbs(*g3, EMPTY); 00215 if ((g1_nbs & g3_nbs).none()) continue; 00216 if ((g2_nbs & g3_nbs).none()) continue; 00217 00218 // The union of the pairwise neighbour intersections 00219 // of groups g1, g2, g3 form a clique. 00220 bitset_t clique; 00221 clique = (g1_nbs & g2_nbs) | 00222 (g1_nbs & g3_nbs) | 00223 (g2_nbs & g3_nbs); 00224 dead |= ComputeEdgeUnreachableRegions(brd, *c, clique); 00225 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00226 } 00227 } 00228 } 00229 } 00230 HexAssert(BitsetUtil::IsSubsetOf(dead, empty)); 00231 return dead; 00232 } 00233 00234 /** Computes dead regions on the board separated via a clique cutset 00235 composed of the intersection of three known maximal 00236 cliques. Returns the union of calls to FindType1Cliques(), 00237 FindType2Cliques(), and FindType3Cliques(). 00238 00239 This finds additional regions not identified via local patterns. 00240 */ 00241 bitset_t FindThreeSetCliques(const Groups& groups) 00242 { 00243 if (groups.IsGameOver()) 00244 return groups.Board().GetEmpty(); 00245 00246 bitset_t dead1 = FindType1Cliques(groups); 00247 bitset_t dead2 = FindType2Cliques(groups); 00248 bitset_t dead3 = FindType3Cliques(groups); 00249 00250 return dead1 | dead2 | dead3; 00251 } 00252 00253 //---------------------------------------------------------------------------- 00254 00255 /** Returns true if the vector of points forms a clique on brd, while 00256 excluding any checks on exclude (to find pre-simplicial cells 00257 exclude should be in vn). */ 00258 bool IsClique(const ConstBoard& brd, const std::vector<HexPoint>& vn, 00259 HexPoint exclude=INVALID_POINT) 00260 { 00261 for (unsigned a=0; a<vn.size(); ++a) { 00262 if (vn[a] == exclude) continue; 00263 for (unsigned b=a+1; b<vn.size(); ++b) { 00264 if (vn[b] == exclude) continue; 00265 if (!brd.Adjacent(static_cast<HexPoint>(vn[a]), 00266 static_cast<HexPoint>(vn[b]))) 00267 return false; 00268 } 00269 } 00270 return true; 00271 } 00272 00273 /** Finds dead and vulnerable cells using graph theory; ie, not using 00274 local patterns. The board will have any found dead cells 00275 filled-in. */ 00276 void UseGraphTheoryToFindDeadVulnerable(HexColor color, Groups& groups, 00277 PatternState& pastate, 00278 InferiorCells& inf) 00279 { 00280 StoneBoard& brd = groups.Board(); 00281 bitset_t simplicial; 00282 bitset_t adj_to_both_edges = 00283 groups.Nbs(HexPointUtil::colorEdge1(color), EMPTY) & 00284 groups.Nbs(HexPointUtil::colorEdge2(color), EMPTY); 00285 bitset_t consider = brd.GetEmpty(); 00286 consider = consider - adj_to_both_edges; 00287 00288 // find presimplicial cells and their dominators 00289 for (BitsetIterator p(consider); p; ++p) 00290 { 00291 std::set<HexPoint> enbs, cnbs; 00292 bitset_t empty_adj_to_group; 00293 bool adj_to_edge = false; 00294 HexPoint edgeNbr = INVALID_POINT; 00295 00296 // Categorize neighbours as either 'empty' or 'color'. 00297 for (BoardIterator nb(brd.Const().Nbs(*p)); nb; ++nb) 00298 { 00299 HexColor ncolor = brd.GetColor(*nb); 00300 if (ncolor == EMPTY) 00301 { 00302 enbs.insert(*nb); 00303 } 00304 else if (ncolor == color) 00305 { 00306 HexPoint cap = groups.CaptainOf(*nb); 00307 bitset_t adj = groups.Nbs(cap, EMPTY); 00308 adj.reset(*p); 00309 00310 // Ignore color groups with no empty neighbours (after 00311 // removing *p). If color group has one non-*p 00312 // neighbour, store it as an empty neighbour. 00313 // Otherwise, add as a color group (helps us to 00314 // identify cliques later). Lastly, edges are a 00315 // special case - always added as a group. 00316 if (HexPointUtil::isColorEdge(cap, color)) 00317 { 00318 HexAssert(!adj_to_edge || edgeNbr == cap); 00319 adj_to_edge = true; 00320 edgeNbr = cap; 00321 cnbs.insert(cap); 00322 empty_adj_to_group |= adj; 00323 } 00324 else if (adj.count() == 1) 00325 { 00326 enbs.insert(static_cast<HexPoint> 00327 (BitsetUtil::FindSetBit(adj))); 00328 } 00329 else if (adj.count() >= 2) 00330 { 00331 cnbs.insert(cap); 00332 empty_adj_to_group |= adj; 00333 } 00334 } 00335 } 00336 00337 // Remove empty neighbours that are adjacent to a color neighbour. 00338 std::set<HexPoint>::iterator it; 00339 for (it = enbs.begin(); it != enbs.end(); ) 00340 { 00341 if (empty_adj_to_group.test(*it)) 00342 { 00343 enbs.erase(it); 00344 it = enbs.begin(); 00345 } 00346 else 00347 ++it; 00348 } 00349 00350 //////////////////////////////////////////////////////////// 00351 // if adjacent to one empty cell, or a single group of 00352 // your color, then neighbours are a clique, so *p is dead. 00353 if (enbs.size() + cnbs.size() <= 1) 00354 simplicial.set(*p); 00355 // Handle cells adjacent to the edge and those adjacent to 00356 // multiple groups of color (2 or 3). Need to test whether 00357 // the edge/a group's neighbours include all other groups' 00358 // neighbours, possibly omitting one. This, along with at most 00359 // one empty neighbour, makes the cell dead or vulnerable. 00360 else if (adj_to_edge || cnbs.size() >= 2) 00361 { 00362 if (enbs.size() >= 2) 00363 continue; 00364 00365 if (cnbs.size() == 1) 00366 { 00367 HexAssert(adj_to_edge && enbs.size() == 1); 00368 inf.AddVulnerable(*p, *enbs.begin()); 00369 } 00370 else 00371 { 00372 HexAssert(!adj_to_edge || 00373 HexPointUtil::isColorEdge(edgeNbr, color)); 00374 00375 bitset_t killers_bs; 00376 bool isPreSimp = false; 00377 00378 // Determine if *p is dead (flag if vulnerable) 00379 for (std::set<HexPoint>::iterator i = cnbs.begin(); 00380 i != cnbs.end(); ++i) 00381 { 00382 // When adjacent to the edge, only the edge can 00383 // trump other groups' adjacencies. 00384 if (adj_to_edge && *i != edgeNbr) continue; 00385 00386 bitset_t remainingNbs = 00387 empty_adj_to_group - groups.Nbs(*i, EMPTY); 00388 00389 if (remainingNbs.count() == 0) 00390 { 00391 if (enbs.size() == 0) 00392 simplicial.set(*p); 00393 else 00394 { 00395 HexAssert(enbs.size() == 1); 00396 isPreSimp = true; 00397 killers_bs.set(*enbs.begin()); 00398 } 00399 } 00400 else if (remainingNbs.count() == 1 && enbs.size() == 0) 00401 { 00402 isPreSimp = true; 00403 killers_bs.set(BitsetUtil::FindSetBit(remainingNbs)); 00404 } 00405 } 00406 00407 if (!simplicial.test(*p) && isPreSimp) 00408 { 00409 HexAssert(killers_bs.any()); 00410 for (BitsetIterator k(killers_bs); k; ++k) 00411 inf.AddVulnerable(*p, *k); 00412 } 00413 } 00414 00415 } 00416 // If many neighbours and previous cases didn't apply, 00417 // then most likely *p is not dead or vulnerable. 00418 else if (enbs.size() + cnbs.size() >= 4) 00419 { 00420 // do nothing 00421 } 00422 // If adjacent to one group and some empty cells, then *p 00423 // cannot be dead, but might be vulnerable. 00424 else if (cnbs.size() == 1) 00425 { 00426 if (enbs.size() > 1) 00427 continue; 00428 00429 HexAssert(enbs.size() == 1); 00430 HexAssert(empty_adj_to_group.count() >= 2); 00431 00432 // The single empty neighbour always kills *p 00433 inf.AddVulnerable(*p, *enbs.begin()); 00434 00435 if (empty_adj_to_group.count() == 2) 00436 { 00437 // If the single group has only two neighbours, it is 00438 // possible that one or both of its neighbours are 00439 // adjacent to the single direct neighbour, causing us 00440 // to have more killers of *p 00441 HexPoint omit = *enbs.begin(); 00442 for (BitsetIterator i(empty_adj_to_group); i; ++i) 00443 enbs.insert(*i); 00444 00445 // determine the additional killers of this vulnerable 00446 // cell 00447 std::vector<HexPoint> vn(enbs.begin(), enbs.end()); 00448 for (unsigned ex=0; ex<vn.size(); ++ex) 00449 { 00450 if (vn[ex] == omit) 00451 continue; 00452 if (IsClique(brd.Const(), vn, vn[ex])) 00453 inf.AddVulnerable(*p, vn[ex]); 00454 } 00455 } 00456 } 00457 else 00458 { 00459 // If all empty neighbours form a clique, is dead. Otherwise 00460 // check if eliminating one makes the rest a clique. 00461 HexAssert(cnbs.size() == 0); 00462 std::vector<HexPoint> vn(enbs.begin(), enbs.end()); 00463 00464 if (IsClique(brd.Const(), vn)) 00465 simplicial.set(*p); 00466 else 00467 { 00468 for (unsigned ex=0; ex<vn.size(); ++ex) 00469 if (IsClique(brd.Const(), vn, vn[ex])) 00470 inf.AddVulnerable(*p, vn[ex]); 00471 } 00472 } 00473 } 00474 // Add the simplicial stones to the board 00475 if (simplicial.any()) 00476 { 00477 inf.AddDead(simplicial); 00478 brd.AddColor(DEAD_COLOR, simplicial); 00479 pastate.Update(simplicial); 00480 GroupBuilder::Build(brd, groups); 00481 } 00482 } 00483 00484 //---------------------------------------------------------------------------- 00485 00486 } // anonymous namespace 00487 00488 //---------------------------------------------------------------------------- 00489 00490 ICEngine::ICEngine() 00491 : m_find_presimplicial_pairs(true), 00492 m_find_permanently_inferior(true), 00493 m_find_mutual_fillin(false), 00494 m_find_all_pattern_killers(true), 00495 m_find_all_pattern_reversers(false), 00496 m_find_all_pattern_dominators(false), 00497 m_use_handcoded_patterns(true), 00498 m_backup_opponent_dead(false), 00499 m_find_three_sided_dead_regions(false), 00500 m_iterative_dead_regions(false) 00501 { 00502 LoadHandCodedPatterns(); 00503 LoadPatterns(); 00504 } 00505 00506 ICEngine::~ICEngine() 00507 { 00508 } 00509 00510 //---------------------------------------------------------------------------- 00511 00512 /** Creates the set of hand-coded patterns. */ 00513 void ICEngine::LoadHandCodedPatterns() 00514 { 00515 HandCodedPattern::CreatePatterns(m_hand_coded); 00516 LogFine() << "ICEngine: " << m_hand_coded.size() 00517 << " hand coded patterns.\n"; 00518 } 00519 00520 /** Loads local patterns from "ice-pattern-file". */ 00521 void ICEngine::LoadPatterns() 00522 { 00523 #ifdef ABS_TOP_SRCDIR 00524 m_patterns.LoadPatterns(boost::filesystem::path(ABS_TOP_SRCDIR) 00525 / "share" / "ice-patterns.txt"); 00526 #else 00527 LogWarning() << "**** NO ICE PATTERNS LOADED ***\n"; 00528 #endif 00529 } 00530 00531 //---------------------------------------------------------------------------- 00532 00533 std::size_t ICEngine::ComputeDeadCaptured(Groups& groups, PatternState& pastate, 00534 InferiorCells& inf, 00535 HexColorSet colors_to_capture) const 00536 { 00537 StoneBoard& brd = groups.Board(); 00538 // find dead and captured cells and fill them in. 00539 std::size_t count = 0; 00540 while (true) 00541 { 00542 // search for dead; if some are found, fill them in 00543 // and iterate again. 00544 while (true) 00545 { 00546 /** @todo This can be optimized quite a bit. */ 00547 bitset_t dead = FindDead(pastate, brd.GetEmpty()); 00548 if (dead.none()) 00549 break; 00550 count += dead.count(); 00551 inf.AddDead(dead); 00552 brd.AddColor(DEAD_COLOR, dead); 00553 pastate.Update(dead); 00554 } 00555 00556 // search for black captured cells; if some are found, 00557 // fill them in and go back to look for more dead. 00558 { 00559 bitset_t black; 00560 if (HexColorSetUtil::InSet(BLACK, colors_to_capture)) 00561 black = FindCaptured(pastate, BLACK, brd.GetEmpty()); 00562 if (black.any()) 00563 { 00564 count += black.count(); 00565 inf.AddCaptured(BLACK, black); 00566 brd.AddColor(BLACK, black); 00567 pastate.Update(black); 00568 continue; 00569 } 00570 } 00571 00572 // search for white captured cells; if some are found, fill 00573 // them in and go back to look for more dead/black captured. 00574 { 00575 bitset_t white; 00576 if (HexColorSetUtil::InSet(WHITE, colors_to_capture)) 00577 white = FindCaptured(pastate, WHITE, brd.GetEmpty()); 00578 if (white.any()) 00579 { 00580 count += white.count(); 00581 inf.AddCaptured(WHITE, white); 00582 brd.AddColor(WHITE, white); 00583 pastate.Update(white); 00584 continue; 00585 } 00586 } 00587 // did not find any fillin, so abort. 00588 break; 00589 } 00590 if (count) 00591 GroupBuilder::Build(brd, groups); 00592 return count; 00593 } 00594 00595 /** Calls FindPermanentlyInferior() and adds any found to the board 00596 and the set of inferior cells.x */ 00597 std::size_t ICEngine::FillinPermanentlyInferior(Groups& groups, 00598 PatternState& pastate, 00599 HexColor color, InferiorCells& out, 00600 HexColorSet colors_to_capture) const 00601 { 00602 if (!m_find_permanently_inferior) 00603 return 0; 00604 if (!HexColorSetUtil::InSet(color, colors_to_capture)) 00605 return 0; 00606 StoneBoard& brd = groups.Board(); 00607 bitset_t carrier; 00608 bitset_t perm = FindPermanentlyInferior(pastate, color, brd.GetEmpty(), 00609 carrier); 00610 if (perm.any()) 00611 { 00612 out.AddPermInf(color, perm, carrier); 00613 brd.AddColor(color, perm); 00614 pastate.Update(perm); 00615 GroupBuilder::Build(brd, groups); 00616 } 00617 return perm.count(); 00618 } 00619 00620 /** Calls FindMutualFillin() and adds any found to the board and the 00621 set of inferior cells.x */ 00622 std::size_t ICEngine::FillInMutualFillin(Groups& groups, PatternState& pastate, 00623 HexColor color, InferiorCells& out, 00624 HexColorSet colors_to_capture) const 00625 { 00626 if (!m_find_mutual_fillin) 00627 return 0; 00628 /** Can only use mutual fillin when both colours can be captured. */ 00629 if (!HexColorSetUtil::InSet(BLACK, colors_to_capture) || 00630 !HexColorSetUtil::InSet(WHITE, colors_to_capture)) 00631 return 0; 00632 StoneBoard& brd = groups.Board(); 00633 bitset_t carrier; 00634 bitset_t mut[BLACK_AND_WHITE]; 00635 FindMutualFillin(pastate, color, brd.GetEmpty(), carrier, mut); 00636 if (mut[BLACK].any()) 00637 { 00638 HexAssert(mut[WHITE].any()); 00639 HexAssert((mut[BLACK] & mut[WHITE]).none()); 00640 /** Note: mutual fillin carrier is same for both colors (* cells). */ 00641 out.AddMutualFillin(BLACK, mut[BLACK], carrier); 00642 brd.AddColor(BLACK, mut[BLACK]); 00643 pastate.Update(mut[BLACK]); 00644 out.AddMutualFillin(WHITE, mut[WHITE], carrier); 00645 brd.AddColor(WHITE, mut[WHITE]); 00646 pastate.Update(mut[WHITE]); 00647 GroupBuilder::Build(brd, groups); 00648 } 00649 else 00650 HexAssert(mut[WHITE].none()); 00651 return (mut[BLACK] | mut[WHITE]).count(); 00652 } 00653 00654 /** Finds vulnerable cells for color and finds presimplicial pairs 00655 and fills them in for the other color. Simplicial stones will 00656 be added as dead and played to the board as DEAD_COLOR. */ 00657 std::size_t ICEngine::FillInVulnerable(HexColor color, Groups& groups, 00658 PatternState& pastate, InferiorCells& inf, 00659 HexColorSet colors_to_capture) const 00660 { 00661 std::size_t count = 0; 00662 inf.ClearVulnerable(); 00663 00664 UseGraphTheoryToFindDeadVulnerable(color, groups, pastate, inf); 00665 00666 // Find vulnerable cells with local patterns--do not ignore the 00667 // presimplicial cells previously found because a pattern 00668 // may encode another dominator. 00669 bitset_t consider = groups.Board().GetEmpty() - inf.Dead(); 00670 FindVulnerable(pastate, color, consider, inf); 00671 00672 // Fill in presimplicial pairs only if we are doing fillin for the 00673 // other player. 00674 if (HexColorSetUtil::InSet(!color, colors_to_capture)) 00675 { 00676 bitset_t captured = inf.FindPresimplicialPairs(); 00677 if (captured.any()) 00678 { 00679 inf.AddCaptured(!color, captured); 00680 groups.Board().AddColor(!color, captured); 00681 pastate.Update(captured); 00682 GroupBuilder::Build(groups.Board(), groups); 00683 } 00684 count += captured.count(); 00685 } 00686 return count; 00687 } 00688 00689 /** Calls ComputeDeadRegions() and FindThreeSetCliques() and adds 00690 fill-in to board and set of inferior cells. */ 00691 std::size_t ICEngine::CliqueCutsetDead(Groups& groups, PatternState& pastate, 00692 InferiorCells& out) const 00693 { 00694 bitset_t notReachable = ComputeDeadRegions(groups); 00695 if (m_find_three_sided_dead_regions) 00696 notReachable |= FindThreeSetCliques(groups); 00697 if (notReachable.any()) 00698 { 00699 out.AddDead(notReachable); 00700 groups.Board().AddColor(DEAD_COLOR, notReachable); 00701 pastate.Update(notReachable); 00702 GroupBuilder::Build(groups.Board(), groups); 00703 } 00704 return notReachable.count(); 00705 } 00706 00707 void ICEngine::ComputeFillin(HexColor color, Groups& groups, 00708 PatternState& pastate, InferiorCells& out, 00709 HexColorSet colors_to_capture) const 00710 { 00711 out.Clear(); 00712 bool considerCliqueCutset = true; 00713 while(true) 00714 { 00715 std::size_t count; 00716 int iterations = 0; 00717 for (;; ++iterations) 00718 { 00719 count = 0; 00720 count += ComputeDeadCaptured(groups, pastate, out, 00721 colors_to_capture); 00722 count += FillinPermanentlyInferior(groups, pastate, color, out, 00723 colors_to_capture); 00724 count += FillinPermanentlyInferior(groups, pastate, !color, out, 00725 colors_to_capture); 00726 count += FillInMutualFillin(groups, pastate, color, out, 00727 colors_to_capture); 00728 count += FillInMutualFillin(groups, pastate, !color, out, 00729 colors_to_capture); 00730 count += FillInVulnerable(!color, groups, pastate, out, 00731 colors_to_capture); 00732 count += FillInVulnerable(color, groups, pastate, out, 00733 colors_to_capture); 00734 if (0 == count) 00735 break; 00736 considerCliqueCutset = true; 00737 } 00738 if (m_iterative_dead_regions && considerCliqueCutset) 00739 count = CliqueCutsetDead(groups, pastate, out); 00740 if (0 == count) 00741 break; 00742 considerCliqueCutset = false; 00743 } 00744 if (!m_iterative_dead_regions) 00745 CliqueCutsetDead(groups, pastate, out); 00746 } 00747 00748 void ICEngine::ComputeInferiorCells(HexColor color, Groups& groups, 00749 PatternState& pastate, 00750 InferiorCells& out) const 00751 { 00752 #ifndef NDEBUG 00753 HexAssert(groups.Board() == pastate.Board()); 00754 StoneBoard oldBoard(groups.Board()); 00755 #endif 00756 double startTime = Time::Get(); 00757 00758 ComputeFillin(color, groups, pastate, out); 00759 00760 { 00761 // Note: We consider vulnerable cells when matching reversible patterns 00762 // since the captured pattern applies to the entire carrier, not 00763 // just the centre cell of the pattern. 00764 bitset_t consider = groups.Board().GetEmpty(); 00765 FindReversible(pastate, color, consider, out); 00766 } 00767 00768 { 00769 bitset_t consider = groups.Board().GetEmpty() 00770 - out.Vulnerable() 00771 - out.Reversible(); 00772 FindDominated(pastate, color, consider, out); 00773 } 00774 00775 // Play opponent in all empty cells, any dead they created 00776 // are actually vulnerable to the move played. 00777 if (m_backup_opponent_dead) 00778 { 00779 int found = BackupOpponentDead(color, groups.Board(), pastate, out); 00780 if (found) { 00781 LogFine() << "Found " << found 00782 << " cells vulnerable to opponent moves.\n"; 00783 } 00784 } 00785 00786 double endTime = Time::Get(); 00787 LogFine() << " " << (endTime - startTime) 00788 << "s to find inferior cells.\n"; 00789 #ifndef NDEBUG 00790 HexAssert(groups.Board().Hash() == oldBoard.Hash()); 00791 #endif 00792 } 00793 00794 /** For each empty cell on the board, the move is played with the 00795 opponent's stone (ie, !color) and the fill-in is computed. Any 00796 dead cells in this state are backed-up as vulnerable cells in the 00797 original state, with the set of captured stones as the 00798 vulnerable-carrier. This can be moderately expensive. 00799 00800 @todo Link to the "ice-backup-opp-dead" option, or link it's 00801 documentation here. 00802 */ 00803 std::size_t ICEngine::BackupOpponentDead(HexColor color, 00804 const StoneBoard& board, 00805 PatternState& pastate, 00806 InferiorCells& out) const 00807 { 00808 StoneBoard brd(board); 00809 PatternState ps(brd); 00810 ps.CopyState(pastate); 00811 00812 bitset_t reversible = out.Reversible(); 00813 bitset_t dominated = out.Dominated(); 00814 00815 std::size_t found = 0; 00816 for (BitsetIterator p(board.GetEmpty()); p; ++p) 00817 { 00818 brd.StartNewGame(); 00819 brd.SetColor(BLACK, board.GetBlack()); 00820 brd.SetColor(WHITE, board.GetWhite()); 00821 brd.PlayMove(!color, *p); 00822 ps.Update(); 00823 Groups groups; 00824 GroupBuilder::Build(brd, groups); 00825 00826 InferiorCells inf; 00827 ComputeFillin(color, groups, ps, inf); 00828 bitset_t filled = inf.Fillin(BLACK) | inf.Fillin(WHITE); 00829 00830 for (BitsetIterator d(inf.Dead()); d; ++d) 00831 { 00832 /** @todo Add if already vulnerable? */ 00833 if (!out.Vulnerable().test(*d) 00834 && !reversible.test(*d) 00835 && !dominated.test(*d)) 00836 { 00837 bitset_t carrier = filled; 00838 carrier.reset(*d); 00839 carrier.reset(*p); 00840 out.AddVulnerable(*d, VulnerableKiller(*p, carrier)); 00841 found++; 00842 } 00843 } 00844 } 00845 return found; 00846 } 00847 00848 //---------------------------------------------------------------------------- 00849 00850 bitset_t ICEngine::FindDead(const PatternState& pastate, 00851 const bitset_t& consider) const 00852 { 00853 return pastate.MatchOnBoard(consider, m_patterns.HashedDead()); 00854 } 00855 00856 bitset_t ICEngine::FindCaptured(const PatternState& pastate, HexColor color, 00857 const bitset_t& consider) const 00858 { 00859 bitset_t captured; 00860 for (BitsetIterator p(consider); p; ++p) 00861 { 00862 if (captured.test(*p)) 00863 continue; 00864 00865 PatternHits hits; 00866 pastate.MatchOnCell(m_patterns.HashedCaptured(color), *p, 00867 PatternState::STOP_AT_FIRST_HIT, hits); 00868 00869 // Mark carrier as captured if carrier does not intersect the 00870 // set of captured cells found in this pass. 00871 if (!hits.empty()) 00872 { 00873 HexAssert(hits.size() == 1); 00874 const std::vector<HexPoint>& moves = hits[0].moves2(); 00875 00876 bitset_t carrier; 00877 for (unsigned i = 0; i < moves.size(); ++i) 00878 carrier.set(moves[i]); 00879 carrier.set(*p); 00880 if ((carrier & captured).none()) 00881 captured |= carrier; 00882 } 00883 } 00884 return captured; 00885 } 00886 00887 bitset_t ICEngine::FindPermanentlyInferior(const PatternState& pastate, 00888 HexColor color, 00889 const bitset_t& consider, 00890 bitset_t& carrier) const 00891 { 00892 std::vector<PatternHits> hits(FIRST_INVALID); 00893 bitset_t ret = pastate.MatchOnBoard(consider, 00894 m_patterns.HashedPermInf(color), 00895 PatternState::STOP_AT_FIRST_HIT, hits); 00896 for (BitsetIterator p(ret); p; ++p) 00897 { 00898 HexAssert(hits[*p].size() == 1); 00899 const std::vector<HexPoint>& moves = hits[*p][0].moves2(); 00900 for (unsigned i=0; i<moves.size(); ++i) 00901 carrier.set(moves[i]); 00902 } 00903 return ret; 00904 } 00905 00906 void ICEngine::FindMutualFillin(const PatternState& pastate, 00907 HexColor color, 00908 const bitset_t& consider, 00909 bitset_t& carrier, 00910 bitset_t *mut) const 00911 { 00912 bitset_t altered; 00913 for (BitsetIterator p(consider); p; ++p) 00914 { 00915 PatternHits hits; 00916 pastate.MatchOnCell(m_patterns.HashedMutualFillin(color), *p, 00917 PatternState::STOP_AT_FIRST_HIT, hits); 00918 if (hits.empty()) 00919 continue; 00920 00921 /** Ensure this mutual fillin pattern does not interfere with 00922 any other already-added mutual fillin. 00923 */ 00924 HexAssert(hits.size() == 1); 00925 bitset_t willAlter; 00926 willAlter.set(*p); 00927 const std::vector<HexPoint>& moves1 = hits[0].moves1(); 00928 for (unsigned i = 0; i < moves1.size(); ++i) 00929 willAlter.set(moves1[i]); 00930 const std::vector<HexPoint>& moves2 = hits[0].moves2(); 00931 for (unsigned i = 0; i < moves2.size(); ++i) 00932 willAlter.set(moves2[i]); 00933 if ((willAlter & altered).any()) 00934 continue; 00935 00936 /** The mutual fillin can be added. */ 00937 altered |= willAlter; 00938 carrier.set(*p); 00939 for (unsigned i = 0; i < moves1.size(); ++i) 00940 mut[color].set(moves1[i]); 00941 for (unsigned i = 0; i < moves2.size(); ++i) 00942 mut[!color].set(moves2[i]); 00943 } 00944 } 00945 00946 void ICEngine::FindVulnerable(const PatternState& pastate, HexColor color, 00947 const bitset_t& consider, 00948 InferiorCells& inf) const 00949 { 00950 PatternState::MatchMode matchmode = PatternState::STOP_AT_FIRST_HIT; 00951 if (m_find_all_pattern_killers) 00952 matchmode = PatternState::MATCH_ALL; 00953 00954 std::vector<PatternHits> hits(FIRST_INVALID); 00955 bitset_t vul = pastate.MatchOnBoard(consider, 00956 m_patterns.HashedVulnerable(color), 00957 matchmode, hits); 00958 00959 // Add the new vulnerable cells with their killers 00960 for (BitsetIterator p(vul); p; ++p) 00961 { 00962 for (unsigned j=0; j<hits[*p].size(); ++j) 00963 { 00964 const std::vector<HexPoint>& moves1 = hits[*p][j].moves1(); 00965 HexAssert(moves1.size() == 1); 00966 HexPoint killer = moves1[0]; 00967 00968 bitset_t carrier; 00969 const std::vector<HexPoint>& moves2 = hits[*p][j].moves2(); 00970 for (unsigned i=0; i<moves2.size(); ++i) { 00971 carrier.set(moves2[i]); 00972 } 00973 inf.AddVulnerable(*p, VulnerableKiller(killer, carrier)); 00974 } 00975 } 00976 } 00977 00978 void ICEngine::FindReversible(const PatternState& pastate, HexColor color, 00979 const bitset_t& consider, 00980 InferiorCells& inf) const 00981 { 00982 PatternState::MatchMode matchmode = PatternState::STOP_AT_FIRST_HIT; 00983 if (m_find_all_pattern_reversers) 00984 matchmode = PatternState::MATCH_ALL; 00985 00986 // Find reversers using patterns 00987 std::vector<PatternHits> hits(FIRST_INVALID); 00988 bitset_t rev = pastate.MatchOnBoard(consider, 00989 m_patterns.HashedReversible(color), 00990 matchmode, hits); 00991 // Add the new reversible cells with their reversers 00992 for (BitsetIterator p(rev); p; ++p) 00993 { 00994 for (unsigned j=0; j<hits[*p].size(); ++j) 00995 { 00996 const std::vector<HexPoint>& moves1 = hits[*p][j].moves1(); 00997 HexAssert(moves1.size() == 1); 00998 HexPoint reverser = moves1[0]; 00999 01000 // Carriers are mandatory for reversible patterns; 01001 // otherwise cannot check for independence 01002 HexAssert(hits[*p][j].moves2().size() != 0); 01003 bitset_t carrier; 01004 const std::vector<HexPoint>& moves2 = hits[*p][j].moves2(); 01005 for (unsigned i=0; i<moves2.size(); ++i) { 01006 carrier.set(moves2[i]); 01007 } 01008 inf.AddReversible(*p, carrier, reverser); 01009 } 01010 } 01011 } 01012 01013 void ICEngine::FindDominated(const PatternState& pastate, HexColor color, 01014 const bitset_t& consider, 01015 InferiorCells& inf) const 01016 { 01017 PatternState::MatchMode matchmode = PatternState::STOP_AT_FIRST_HIT; 01018 if (m_find_all_pattern_dominators) 01019 matchmode = PatternState::MATCH_ALL; 01020 01021 // Find dominators using patterns 01022 std::vector<PatternHits> hits(FIRST_INVALID); 01023 bitset_t dom = pastate.MatchOnBoard(consider, 01024 m_patterns.HashedDominated(color), 01025 matchmode, hits); 01026 // Add the new dominated cells with their dominators 01027 for (BitsetIterator p(dom); p; ++p) 01028 { 01029 for (unsigned j=0; j<hits[*p].size(); ++j) 01030 { 01031 const std::vector<HexPoint>& moves1 = hits[*p][j].moves1(); 01032 HexAssert(moves1.size() == 1); 01033 inf.AddDominated(*p, moves1[0]); 01034 01035 // For now, no dominated patterns have carriers 01036 // Note: this can change in the future if more complex ICE 01037 // patterns are found 01038 HexAssert(hits[*p][j].moves2().size() == 0); 01039 } 01040 } 01041 // Add dominators found via hand coded patterns 01042 if (m_use_handcoded_patterns) 01043 FindHandCodedDominated(pastate.Board(), color, consider, inf); 01044 } 01045 01046 void ICEngine::FindDominatedOnCell(const PatternState& pastate, 01047 HexColor color, 01048 HexPoint cell, 01049 PatternHits& hits) const 01050 { 01051 PatternState::MatchMode matchmode = PatternState::MATCH_ALL; 01052 pastate.MatchOnCell(m_patterns.HashedDominated(color), cell, 01053 matchmode, hits); 01054 } 01055 01056 void ICEngine::FindHandCodedDominated(const StoneBoard& board, 01057 HexColor color, 01058 const bitset_t& consider, 01059 InferiorCells& inf) const 01060 { 01061 // If board is rectangular, these hand-coded patterns should not 01062 // be used because they need to be mirrored (which is not a valid 01063 // operation on non-square boards). 01064 if (board.Width() != board.Height()) 01065 return; 01066 for (unsigned i=0; i<m_hand_coded.size(); ++i) 01067 CheckHandCodedDominates(board, color, m_hand_coded[i], 01068 consider, inf); 01069 } 01070 01071 /** Handles color flipping/rotations for this hand-coded pattern. If 01072 pattern matches, dominators are added to inf. */ 01073 void ICEngine::CheckHandCodedDominates(const StoneBoard& brd, 01074 HexColor color, 01075 const HandCodedPattern& pattern, 01076 const bitset_t& consider, 01077 InferiorCells& inf) const 01078 { 01079 if (brd.Width() < 4 || brd.Height() < 3) 01080 return; 01081 HandCodedPattern pat(pattern); 01082 // Mirror and flip colors if checking for white 01083 if (color == WHITE) 01084 { 01085 pat.mirror(brd.Const()); 01086 pat.flipColors(); 01087 } 01088 // Top corner 01089 if (consider.test(pat.dominatee()) && pat.check(brd)) 01090 inf.AddDominated(pat.dominatee(), pat.dominator()); 01091 // Bottom corner 01092 pat.rotate(brd.Const()); 01093 if (consider.test(pat.dominatee()) && pat.check(brd)) 01094 inf.AddDominated(pat.dominatee(), pat.dominator()); 01095 } 01096 01097 //---------------------------------------------------------------------------- 01098 01099 void IceUtil::Update(InferiorCells& out, const InferiorCells& in) 01100 { 01101 // Overwrite old vulnerable/dominated with new vulnerable/dominated 01102 out.ClearVulnerable(); 01103 out.ClearReversible(); 01104 out.ClearDominated(); 01105 out.AddVulnerableFrom(in); 01106 out.AddReversibleFrom(in); 01107 out.AddDominatedFrom(in); 01108 01109 // Add the new fillin to the old fillin 01110 for (BWIterator c; c; ++c) 01111 { 01112 out.AddCaptured(*c, in.Captured(*c)); 01113 out.AddPermInfFrom(*c, in); 01114 out.AddMutualFillinFrom(*c, in); 01115 } 01116 01117 // Add the new dead cells. 01118 out.AddDead(in.Dead()); 01119 } 01120 01121 //----------------------------------------------------------------------------