bool GoBoardHistory:: IsAlternatePlayFollowUpOf(const GoBoardHistory& other, std::vector<SgPoint>& sequence) const { if (m_boardSize != other.m_boardSize || m_rules != other.m_rules || m_setup != other.m_setup || m_moves.Length() < other.m_moves.Length()) return false; for (int i = 0; i < other.m_moves.Length(); ++i) if (m_moves[i] != other.m_moves[i]) return false; sequence.clear(); SgBlackWhite toPlay = other.m_toPlay; for (int i = other.m_moves.Length(); i < m_moves.Length(); ++i) { GoPlayerMove m = m_moves[i]; if (m.Color() != toPlay) return false; sequence.push_back(m.Point()); toPlay = SgOppBW(toPlay); } if (toPlay != m_toPlay) return false; return true; }
void GoRegionBoard::OnExecutedUncodedMove(int move, SgBlackWhite moveColor) { if (DEBUG_REGION_BOARD) SgDebug() << "OnExecutedUncodedMove " << SgWritePoint(move) << '\n'; { m_stack.StartMoveInfo(); if (move != SG_PASS) { SG_ASSERT(! Board().LastMoveInfo(GO_MOVEFLAG_SUICIDE)); // can't handle yet, // should be forbidden anyway. @todo allowed in Chinese rules. bool fWasCapture = Board().LastMoveInfo(GO_MOVEFLAG_CAPTURING); UpdateBlock(move, moveColor); { GoRegion* r = PreviousRegionAt(move, moveColor); bool split = GoEyeUtil::IsSplitPt(move, r->Points()); r->OnAddStone(move); PushStone(r, move); SgPointSet points = r->Points(); // needed even after RemoveRegion(r). if (split || points.IsEmpty()) // must remove old region before generating new ones, // because removing clears m_anchor[] RemoveRegion(r); if (split) // find new regions { for (SgConnCompIterator it(points, Board().Size()); it; ++it) GenRegion(*it, moveColor); } } if (fWasCapture) { // FindNewNeighborRegions(move, moveColor); MergeAdjacentAndAddBlock(move, SgOppBW(moveColor)); } m_code = Board().GetHashCode(); if (HEAVYCHECK) CheckConsistency(); } } { for (SgBWIterator it; it; ++it) { SgBlackWhite color(*it); for (SgVectorIteratorOf<GoRegion> it(AllRegions(color)); it; ++it) { GoRegion* r1 = *it; if (! r1->IsValid()) r1->ComputeBasicFlags(); } } } }
void GoInfluence::FindInfluence(const GoBoard& board, int nuExpand, int nuShrink, SgBWSet* influence) { SgBWSet result = SgBWSet(board.All(SG_BLACK), board.All(SG_WHITE)); SgBWSet next; const int size = board.Size(); for (int i = 1; i <= nuExpand; ++i) { for (SgBlackWhite c = SG_BLACK; c <= SG_WHITE; ++c) { SgBlackWhite opp = SgOppBW(c); next[c] = result[c].Border(size) - result[opp]; } result[SG_BLACK] |= (next[SG_BLACK] - next[SG_WHITE]); result[SG_WHITE] |= (next[SG_WHITE] - next[SG_BLACK]); } for (int i = 1; i <= nuShrink; ++i) { result[SG_BLACK] = result[SG_BLACK].Kernel(size); result[SG_WHITE] = result[SG_WHITE].Kernel(size); } *influence = result; }
bool GoEyeUtil::IsSinglePointEye2(const GoBoard& board, SgPoint p, SgBlackWhite color, SgVector<SgPoint>& eyes) { // Must be an empty point if (! board.IsColor(p, SG_EMPTY)) return false; // All adjacent neighbours must be friendly SgBoardColor opp = SgOppBW(color); for (SgNb4Iterator adj(p); adj; ++adj) { SgBoardColor adjColor = board.GetColor(*adj); if (adjColor == opp || adjColor == SG_EMPTY) return false; } // All diagonals (with up to one exception) must be friendly or an eye int baddiags = 0; int maxbaddiags = (board.Line(p) == 1 ? 0 : 1); for (SgNb4DiagIterator it(p); it; ++it) { if (board.IsColor(*it, opp)) ++baddiags; if (board.IsColor(*it, SG_EMPTY) && ! eyes.Contains(*it)) { // Assume this point is an eye and recurse eyes.PushBack(p); if (! IsSinglePointEye2(board, *it, color, eyes)) ++baddiags; eyes.PopBack(); } if (baddiags > maxbaddiags) return false; } return true; }
bool GoEyeUtil::NumberOfMoveToEye(const GoBoard& board, SgBlackWhite color, SgPoint p, int& number) { SG_ASSERT(board.IsEmpty(p)); SgBlackWhite att = SgOppBW(color); // attacker if ( board.Line(p) == 1) // corner or edge { if ( board.Num8Neighbors(p, att) > 0 ) return false; else { number = board.Num8EmptyNeighbors(p); return true; } } else // in center { if ( board.Num8Neighbors(p, att) >= 2 ) return false; else if ( board.NumNeighbors(p, att) > 0 ) return false; else // only 0 or 1 attacker point and not in NB4 { number = board.Num8EmptyNeighbors(p); return true; } } }
// @todo: as a first cut, this simply checks if the regions has any // stones in it. It does not check if the move is suitable for capturing the // stones. bool GoSafetySolver::PotentialCaptureMove(SgPoint p, SgBlackWhite regionColor) const { SG_ASSERT(UpToDate()); SG_ASSERT(Board().ToPlay() == regionColor); const GoRegion* r = Regions()->RegionAt(p, regionColor); SG_ASSERT(r); return r->Points().Overlaps(Board().All(SgOppBW(regionColor))); }
int SpAverageMoveGenerator::Evaluate() { // We are Opponent since this is after executing our move SgBlackWhite player = m_board.Opponent(); int score = LibertyAveragex10(m_board, player) - LibertyAveragex10(m_board, SgOppBW(player)); return score; }
bool GoEyeUtil::IsSinglePointEye(const GoBoard& bd, SgPoint p, SgBlackWhite color) { SG_ASSERT(bd.IsEmpty(p)); const SgBlackWhite opp = SgOppBW(color); if (bd.HasEmptyNeighbors(p) || bd.HasNeighbors(p, opp)) return false; if (bd.Line(p) == 1) return ! (bd.HasDiagonals(p, SG_EMPTY) || bd.HasDiagonals(p, opp)); return (bd.NumDiagonals(p, SG_EMPTY) + bd.NumDiagonals(p, opp)) <= 1; }
int SpMinLibMoveGenerator::Evaluate() { // We are Opponent since this is after executing our move SgBlackWhite player = m_board.Opponent(); int minlibscore = LibertyMinimum(player); int avlibscore = ::LibertyAveragex10(m_board, player) - ::LibertyAveragex10(m_board, SgOppBW(player)); // Select moves in priority order according to: // a) Minimum liberties (for either player) // b) Average liberties x 10 // Note that the 1/x scaling of minlibs means that avlibs can matter more // when |minlibs| is very high. Which is probably a good thing! int score = minlibscore * 100 + avlibscore; // roughly in range [-10000,10000] return score; }
bool GoEyeUtil::IsPossibleEye(const GoBoard& board, SgBlackWhite color, SgPoint p) { bool isPossibleEye = false; SG_ASSERT(board.GetColor(p) != color); const SgBlackWhite opp = SgOppBW(color); if (board.Line(p) == 1) // corner or edge { const int nuOwn = (board.Pos(p) == 1) ? 2 : 4; if ( board.Num8Neighbors(p, color) == nuOwn && board.Num8EmptyNeighbors(p) == 1 ) { isPossibleEye = true; } } else // in center { // have all neighbors, and 2 diagonals, and can get a third if ( board.NumNeighbors(p, color) == 4 && board.NumDiagonals(p, color) == 2 && board.NumEmptyDiagonals(p) > 0 ) { isPossibleEye = true; } // have 3 of 4 neighbors, can get the 4th, and have enough diagonals else if ( board.NumNeighbors(p, color) == 3 && board.NumNeighbors(p, opp) == 0 && board.NumDiagonals(p, color) >= 3 ) { isPossibleEye = true; } } return isPossibleEye; }
/** See SgStrategy::ExecuteMove */ void SgMiaiMap::ExecuteMove(SgPoint p, SgBlackWhite player) { if (m_forcedMove != SG_NULLPOINT) { SG_ASSERT(m_forcedMove == p); m_forcedMove = SG_NULLPOINT; } else if (m_map[player][p] != SG_NULLPOINT) { m_forcedMove = m_map[player][p]; m_map[player][p] = SG_NULLPOINT; m_map[player][m_forcedMove] = SG_NULLPOINT; // too early??? SgBlackWhite opp = SgOppBW(player); SgPoint temp = m_map[opp][p]; if (temp != SG_NULLPOINT) { SG_ASSERT(temp != SG_NULLPOINT); m_map[opp][p] = SG_NULLPOINT; SG_ASSERT(m_map[opp][temp] == p); m_map[opp][temp] = SG_NULLPOINT; } } }
size_t DfpnSolver::MID(const DfpnBounds& maxBounds, DfpnHistory& history) { maxBounds.CheckConsistency(); SG_ASSERT(maxBounds.phi > 1); SG_ASSERT(maxBounds.delta > 1); ++m_numMIDcalls; size_t prevWork = 0; SgEmptyBlackWhite colorToMove = GetColorToMove(); DfpnData data; if (TTRead(data)) { prevWork = data.m_work; if (! maxBounds.GreaterThan(data.m_bounds)) // Estimated bounds are larger than we had // anticipated. The calling state must have computed // the max bounds with out of date information, so just // return here without doing anything: the caller will // now update to this new info and carry on. return 0; } else { SgEmptyBlackWhite winner = SG_EMPTY; if (TerminalState(colorToMove, winner)) { ++m_numTerminal; DfpnBounds terminal; if (colorToMove == winner) DfpnBounds::SetToWinning(terminal); else { SG_ASSERT(SgOppBW(colorToMove) == winner); DfpnBounds::SetToLosing(terminal); } TTWrite(DfpnData(terminal, SG_NULLMOVE, 1)); return 1; } } ++m_generateMoves; DfpnChildren children; GenerateChildren(children.Children()); // Not thread safe: perhaps move into while loop below later... std::vector<DfpnData> childrenData(children.Size()); for (size_t i = 0; i < children.Size(); ++i) LookupData(childrenData[i], children, i); // Index used for progressive widening size_t maxChildIndex = ComputeMaxChildIndex(childrenData); SgHashCode currentHash = Hash(); SgMove bestMove = SG_NULLMOVE; DfpnBounds currentBounds; size_t localWork = 1; do { UpdateBounds(currentBounds, childrenData, maxChildIndex); if (! maxBounds.GreaterThan(currentBounds)) break; // Select most proving child std::size_t bestIndex = 999999; DfpnBoundType delta2 = DfpnBounds::INFTY; SelectChild(bestIndex, delta2, childrenData, maxChildIndex); bestMove = children.MoveAt(bestIndex); // Compute maximum bound for child const DfpnBounds childBounds(childrenData[bestIndex].m_bounds); DfpnBounds childMaxBounds; childMaxBounds.phi = maxBounds.delta - (currentBounds.delta - childBounds.phi); childMaxBounds.delta = delta2 == DfpnBounds::INFTY ? maxBounds.phi : std::min(maxBounds.phi, std::max(delta2 + 1, DfpnBoundType(delta2 * (1.0 + m_epsilon)))); SG_ASSERT(childMaxBounds.GreaterThan(childBounds)); if (delta2 != DfpnBounds::INFTY) m_deltaIncrease.Add(float(childMaxBounds.delta-childBounds.delta)); // Recurse on best child PlayMove(bestMove); history.Push(bestMove, currentHash); localWork += MID(childMaxBounds, history); history.Pop(); UndoMove(); // Update bounds for best child LookupData(childrenData[bestIndex], children, bestIndex); // Compute some stats when find winning move if (childrenData[bestIndex].m_bounds.IsLosing()) { m_moveOrderingIndex.Add(float(bestIndex)); m_moveOrderingPercent.Add(float(bestIndex) / (float)childrenData.size()); m_totalWastedWork += prevWork + localWork - childrenData[bestIndex].m_work; } else if (childrenData[bestIndex].m_bounds.IsWinning()) maxChildIndex = ComputeMaxChildIndex(childrenData); } while (! CheckAbort()); // Find the most delaying move for losing states, and the smallest // winning move for winning states. if (currentBounds.IsSolved()) { if (currentBounds.IsLosing()) { std::size_t maxWork = 0; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_work > maxWork) { maxWork = childrenData[i].m_work; bestMove = children.MoveAt(i); } } } else { std::size_t minWork = DfpnBounds::INFTY; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_bounds.IsLosing() && childrenData[i].m_work < minWork) { minWork = childrenData[i].m_work; bestMove = children.MoveAt(i); } } } } // Store search results TTWrite(DfpnData(currentBounds, bestMove, localWork + prevWork)); return localWork; }
vector<SgPoint> GoUctDefaultMoveFilter::Get() { vector<SgPoint> rootFilter; const SgBlackWhite toPlay = m_bd.ToPlay(); const SgBlackWhite opp = SgOppBW(toPlay); // Safe territory if (m_param.m_checkSafety) { SgBWSet alternateSafe; // Alternate safety is used to prune moves only in opponent territory // and only if everything is alive under alternate play. This ensures that // capturing moves that are not liberties of dead blocks and ko threats // will not be pruned. This alternate safety pruning is not going to // improve or worsen playing strength, but may cause earlier passes, // which is nice in games against humans GoSafetySolver safetySolver(m_bd); safetySolver.FindSafePoints(&alternateSafe); // Benson solver guarantees that capturing moves of dead blocks are // liberties of the dead blocks and that no move in Benson safe territory // is a ko threat GoBensonSolver bensonSolver(m_bd); SgBWSet unconditionalSafe; bensonSolver.FindSafePoints(&unconditionalSafe); for (GoBoard::Iterator it(m_bd); it; ++it) { const SgPoint p = *it; if (m_bd.IsLegal(p)) { bool isUnconditionalSafe = unconditionalSafe[toPlay].Contains(p); bool isUnconditionalSafeOpp = unconditionalSafe[opp].Contains(p); bool isAlternateSafeOpp = alternateSafe[opp].Contains(p); bool hasOppNeighbors = m_bd.HasNeighbors(p, opp); // Always generate capturing moves in own safe territory, even // if current rules do no use CaptureDead(), because the UCT // player always scores with Tromp-Taylor after two passes in the // in-tree phase // if ( (isAllAlternateSafe && isAlternateSafeOpp) if ( isAlternateSafeOpp || isUnconditionalSafeOpp || (isUnconditionalSafe && ! hasOppNeighbors) || ( alternateSafe[toPlay].Contains(p) && ! safetySolver.PotentialCaptureMove(p, toPlay) ) ) rootFilter.push_back(p); } } } // Losing ladder defense moves if (m_param.m_checkLadders) { for (GoBlockIterator it(m_bd); it; ++it) { const SgPoint p = *it; if (m_bd.GetStone(p) == toPlay && m_bd.InAtari(p)) { if (m_ladder.Ladder(m_bd, p, toPlay, &m_ladderSequence, false/*twoLibIsEscape*/) < 0) { if (m_ladderSequence.Length() >= m_param.m_minLadderLength) rootFilter.push_back(m_bd.TheLiberty(p)); } } } } if (m_param.m_checkOffensiveLadders) { for (GoBlockIterator it(m_bd); it; ++it) { const SgPoint p = *it; if (m_bd.GetStone(p) == opp && m_bd.NumStones(p) >= 5 && m_bd.NumLiberties(p) == 2 && m_ladder.Ladder(m_bd, p, toPlay, &m_ladderSequence, false/*twoLibIsEscape*/) > 0 && m_ladderSequence.Length() >= m_param.m_minLadderLength ) rootFilter.push_back(m_ladderSequence[0]); } } if (m_param.m_filterFirstLine) { // Moves on edge of board, if no stone is near const SgBoardConst& bc = m_bd.BoardConst(); for (SgLineIterator it(bc, 1); it; ++it) { const SgPoint p = *it; if (m_bd.IsEmpty(p) && IsEmptyEdge(m_bd, p)) rootFilter.push_back(p); } } return rootFilter; }
void GoEyeUtil::TestNakade(const SgPointSet& points, const GoBoard& bd, SgBlackWhite color, bool isFullyEnclosed, bool& isNakade, bool& makeNakade, bool& makeFalse, bool& maybeSeki, bool& sureSeki, SgPoint* vital) { // handles case // of more than 1 point passing vital point test. // passes back vital point if exactly one is found. // @todo handle case where vital is illegal or suicide (eye within eye?). // also test bigger, would-be-alive shapes in atari. // @todo handle seki. SG_UNUSED(makeFalse); isNakade = makeNakade = maybeSeki = sureSeki = false; SgPoint vitalP(SG_NULLPOINT); const SgBlackWhite opp = SgOppBW(color); const int nuPoints = points.Size(); SG_ASSERT(nuPoints >= 3); // don't call for smaller areas, no need, // and results in isNakade = false which is confusing. if (nuPoints == 4) // special case 4 point areas { if (IsBulkyFour(points)) { if ( isFullyEnclosed && TwoDiagonalStonesInBulkyFour(bd, points, opp) ) makeNakade = true; else isNakade = true; /* */ return; /* */ } else if ( isFullyEnclosed && points.SubsetOf(bd.AllEmpty()) && IsBentFour(points, bd.Size(), vital) ) { makeNakade = true; /* */ return; /* */ } } else if (isFullyEnclosed && nuPoints == 5) // special case 5 point areas { const GoEyeStatus status = BulkyFiveNakade(bd, points, opp); if (ProcessStatus(status, isNakade, makeNakade)) /* */ return; /* */ } else if (isFullyEnclosed && nuPoints == 6) // special case 6 point areas { if (Is2x3Area (points)) { GoEyeStatus status = Special2x3Cases(bd, points, opp); if (ProcessStatus(status, isNakade, makeNakade)) /* */ return; /* */ } } if ( isFullyEnclosed && AlmostFilledByNakade(bd, points, opp) ) { isNakade = true; /* */ return; /* */ } if ( isFullyEnclosed && ( AlmostFilledByLivingShape(bd, points, opp) || ContainsLivingShape(bd, points, opp) ) ) { // not nakade, 2 eyes /* */ return; /* */ } int nuMakeNakade = 0; int nuVitalOccupied = 0; bool hasDivider = false; // counting number of nakade fixes bug with stretched 5 pt eye // that had 3 vital pts, // one of them occupied. was classified as 'isNakade7 = 1 eye. // see sgf/ld200,#20 for (SgSetIterator it(points); it; ++it) { SgPoint p(*it); if (IsVitalPt(points, p, opp, bd)) { if (bd.IsEmpty(p)) { if (bd.IsLegal(p, opp)) { ++nuMakeNakade; vitalP = p; } else hasDivider = true; } else ++nuVitalOccupied; } } if (hasDivider) { // alive } else if (nuMakeNakade == 1) // exactly one way to make nakade here. { makeNakade = true; *vital = vitalP; } else if (nuMakeNakade > 0) isNakade = false; else if (nuVitalOccupied < 3) isNakade = true; else { maybeSeki = true; // @todo if (IsSureSekiShape(...)) sureSeki = true; } }
bool GoEyeUtil::NumberOfMoveToEye2(const GoBoard& board, SgBlackWhite color, SgPoint p, int& nummoves) { nummoves = 0; bool capturing = false; SgVector<SgPoint> usedpoints; usedpoints.PushBack(p); SgPointSet counted; // Can never turn own stone into an eye if (board.IsColor(p, color)) return false; // If opponent stone then it must be captured to make eye if (board.IsColor(p, SgOppBW(color))) { capturing = true; // If it is obviously safe then it can never be an eye if (SinglePointSafe2(board, p)) // Quick, naive safety test return false; for (GoBoard::LibertyIterator libit(board, p); libit; ++libit) counted.Include(*libit); } // Count immediate adjacencies for (SgNb4Iterator nb(p); nb; ++nb) { SgPoint adj = *nb; // Empty points must be filled if (board.IsColor(adj, SG_EMPTY)) { counted.Include(adj); } // If adjacent opponent then can never be an eye else if (board.IsColor(adj, SgOppBW(color))) { if (capturing) counted.Include(adj); // must capture and then fill else return false; } } // Up to one diagonal can be ignored: estimate most costly SgPoint toignore = SG_NULLPOINT; int maxcost = 0; int infcost = 1000; if (board.Line(p) > 1) { for (SgNb4DiagIterator nbd(p); nbd; ++nbd) { SgPoint diag = *nbd; int cost = 0; if ( board.IsColor(diag, SG_EMPTY) && ! IsSinglePointEye2(board, diag, color, usedpoints)) { cost = 1; } else if (board.IsColor(diag, SgOppBW(color))) { // quick safety test if (SinglePointSafe2(board, diag)) cost = infcost; else cost = board.NumLiberties(diag); } if (cost > maxcost) { maxcost = cost; toignore = diag; } } } // Now mark points that must be played to secure diagonals for (SgNb4DiagIterator nbd(p); nbd; ++nbd) { SgPoint diag = *nbd; if (diag == toignore) continue; // Empty points must be filled (unless they are eyes) if ( board.IsColor(diag, SG_EMPTY) && ! IsSinglePointEye2(board, diag, color, usedpoints)) { counted.Include(diag); } // Opponent stones on diagonals must be captured and filled else if (board.IsColor(diag, SgOppBW(color))) { if (SinglePointSafe2(board, diag)) return false; else { counted.Include(diag); for (GoBoard::LibertyIterator libit(board, diag); libit; ++libit) counted.Include(*libit); } } } nummoves = counted.Size(); return true; }