com.barrybecker4.game.twoplayer.go.board.analysis.group.GroupAnalyzer Maven / Gradle / Ivy
/** Copyright by Barry G. Becker, 2000-2011. Licensed under MIT License: http://www.opensource.org/licenses/MIT */
package com.barrybecker4.game.twoplayer.go.board.analysis.group;
import com.barrybecker4.common.format.FormatUtil;
import com.barrybecker4.game.common.GameContext;
import com.barrybecker4.game.twoplayer.go.board.GoBoard;
import com.barrybecker4.game.twoplayer.go.board.elements.eye.GoEyeSet;
import com.barrybecker4.game.twoplayer.go.board.elements.group.GroupChangeListener;
import com.barrybecker4.game.twoplayer.go.board.elements.group.IGoGroup;
import com.barrybecker4.game.twoplayer.go.board.elements.position.GoBoardPosition;
import com.barrybecker4.game.twoplayer.go.board.elements.position.GoStone;
import com.barrybecker4.game.twoplayer.go.GoController;
/**
* Analyzes a group to determine how alive it is, and also find other properties like eyes and liberties.
*
* @author Barry Becker
*/
public class GroupAnalyzer implements GroupChangeListener {
/** The group of go stones that we are analyzing. */
private IGoGroup group_;
private StoneInGroupAnalyzer stoneInGroupAnalyzer_;
/**
* This measure of health is also between -1 and 1 but it should be more
* accurate because it takes into account the health of neighboring enemy groups as well.
* it uses the absolute health as a base and exaggerates it base on the relative strength of the
* weakest enemy nbr group.
*/
private float relativeHealth_;
private AbsoluteHealthCalculator absHealthCalculator_;
/** cached absolute health to avoid needless recalculation. */
private float absoluteHealth_;
private GroupAnalyzerMap analyzerMap_;
/**
* Constructor.
* @param group group to analyze.
*/
public GroupAnalyzer(IGoGroup group, GroupAnalyzerMap analyzerMap) {
group_ = group;
analyzerMap_ = analyzerMap;
absHealthCalculator_ = new AbsoluteHealthCalculator(group, analyzerMap_);
stoneInGroupAnalyzer_ = new StoneInGroupAnalyzer(group);
group.addChangeListener(this);
}
public IGoGroup getGroup() {
return group_;
}
/**
* Called when the group we are maintaining info about changes.
* It changes by having stones added or removed.
*/
public void groupChanged() {
invalidate();
}
/**
* @return health score independent of neighboring groups.
*/
public float getAbsoluteHealth() {
return absoluteHealth_;
}
/**
* We try to use the cached relative health value if we can.
* @param board needed to calculate new value if not cached
* @param useCachedValue if true, just return the cached value instead of checking for validity.
* Only do this if you are sure the value returned does not have to be perfectly accurate.
* @return relative health
*/
public float getRelativeHealth(GoBoard board, boolean useCachedValue) {
if (!GoController.USE_RELATIVE_GROUP_SCORING) {
return getAbsoluteHealth();
}
if (isValid() || useCachedValue) {
if (!isValid())
GameContext.log(3, "using cached relative health when not valid");
return getRelativeHealth();
}
GameContext.log(0, "stale abs health. recalculating relative health");
return calculateRelativeHealth(board);
}
/**
* @return health score dependent on strength of neighboring groups.
*/
float getRelativeHealth() {
return relativeHealth_;
}
public void invalidate() {
absHealthCalculator_.invalidate();
}
/**
* @return true if the group has changed (structurally) in any way.
*/
public boolean isValid()
{
return absHealthCalculator_.isValid();
}
/**
* If nothing cached, this may not be accurate.
* @return number of cached liberties.
*/
public int getNumLiberties(GoBoard board) {
return group_.getNumLiberties(board);
}
/**
* @return set of eyes currently identified for this group.
*/
public GoEyeSet getEyes(GoBoard board) {
return absHealthCalculator_.getEyes(board);
}
public float calculateAbsoluteHealth(GoBoard board) {
absoluteHealth_ = absHealthCalculator_.calculateAbsoluteHealth(board);
return absoluteHealth_;
}
/**
* @return true if the piece is an enemy of the set owner.
* If the difference in health between the stones is great, then they are not really enemies
* because one of them is dead.
*/
public boolean isTrueEnemy(GoBoardPosition pos) {
assert (pos.isOccupied());
GoStone stone = (GoStone)pos.getPiece();
boolean muchWeaker = isStoneMuchWeakerThanGroup(stone);
return ( stone.isOwnedByPlayer1() != group_.isOwnedByPlayer1() && !muchWeaker);
}
/**
* used only for test. Remove when tested through AbsoluteGroupHealthCalc
* @return eye potential
*/
public float getEyePotential() {
return absHealthCalculator_.getEyePotential();
}
/**
* Calculate the relative health of a group.
* This method must be called only after calculateAbsoluteHealth has be done for all groups.
* Good health is positive for a black group.
* This measure of the group's health should be much more accurate than the absolute health
* because it takes into account the relative health of neighboring groups.
* If the health of an opponent bordering group is in worse shape
* than our own then we get a boost since we can probably kill that group first.
*
* @return the overall health of the group.
*/
public float calculateRelativeHealth(GoBoard board) {
if (!isValid()) {
calculateAbsoluteHealth(board);
}
RelativeHealthCalculator relativeCalculator = new RelativeHealthCalculator(group_, analyzerMap_);
relativeHealth_ = relativeCalculator.calculateRelativeHealth(board, absoluteHealth_);
return relativeHealth_;
}
/**
* @return a deep copy of this GroupAnalyzer
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
((GroupAnalyzer)clone).absHealthCalculator_ = new AbsoluteHealthCalculator(group_, analyzerMap_);
return clone;
}
/**
* @return true if the stone is much weaker than the group
*/
private boolean isStoneMuchWeakerThanGroup(GoStone stone) {
return stoneInGroupAnalyzer_.isStoneMuchWeakerThanGroup(stone, getAbsoluteHealth());
}
/**
* @return string form.
*/
public String toString() {
String newline = "\n";
StringBuilder sb = new StringBuilder();
sb.append(group_.toString());
GoEyeSet eyes = getEyes(null);
if (eyes!=null && !eyes.isEmpty())
sb.append(eyes.toString()).append(newline);
// make sure that the health and eyes are up to date
sb.append("abs health=").append(FormatUtil.formatNumber(getAbsoluteHealth()));
sb.append(" rel health=").append(FormatUtil.formatNumber(getRelativeHealth()));
sb.append(" group Liberties=").append(getNumLiberties(null)).append('\n');
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy