Completed comments of TurnLogic

git-svn-id: svn://sunsvr01.isp.uni-luebeck.de/swproj13/trunk@535 72836036-5685-4462-b002-a69064685172
This commit is contained in:
Jannis Harder 2011-06-21 03:45:44 +02:00
parent 0b1f9383ce
commit 9b5f3648ed

View file

@ -71,7 +71,7 @@ public class TurnLogic {
* Create a copy of a state
*
* @param state
* the state to copy
* the state to copy
*/
public State(State state) {
for (StoneState stone : state.stones) {
@ -84,7 +84,7 @@ public class TurnLogic {
* Adds stones to be considered in the state
*
* @param stone
* the stone to add
* the stone to add
*/
public void add(StoneState stone) {
stones.add(stone);
@ -98,7 +98,7 @@ public class TurnLogic {
* Update the stones to consider the changes of the changed stones
*
* @throws Contradiction
* Is thrown if the changes contradict each other
* Is thrown if the changes contradict each other
*/
public void updateStones() throws Contradiction {
checkJokerCount();
@ -121,9 +121,9 @@ public class TurnLogic {
* One step in the update process
*
* @throws Contradiction
* Is thrown if changes contradict each other
* Is thrown if changes contradict each other
*/
public void updateStonesStep() throws Contradiction {
private void updateStonesStep() throws Contradiction {
HashSet<Integer> newChangedStones = new HashSet<Integer>();
for (int i = 0; i < stoneCount; i++) {
StoneState stone = stones.get(i);
@ -137,8 +137,8 @@ public class TurnLogic {
}
/**
* Returns whether the stones have a definite value and the state is thereby
* solved
* Returns whether the stones have a definite value and the state is
* thereby solved
*
* @return whether the state is solved
*/
@ -152,10 +152,10 @@ public class TurnLogic {
}
/**
* Checks that not too many jokers are trying to be used
* Checks that not more jokers than available are needed
*
* @throws Contradiction
* Is thrown if too many jokers are trying to be used
* Is thrown if too many jokers are needed
*/
private void checkJokerCount() throws Contradiction {
int jokersLeft = jokerIDs.size();
@ -177,10 +177,10 @@ public class TurnLogic {
}
/**
* Checks that enough points and a high enough score are reached
* Checks that enough points and a high enough score can be reached
*
* @throws Contradiction
* Is thrown if points are too few or score is too low
* Is thrown if points are too few or score is too low
*/
public void checkScoreAndPoints() throws Contradiction {
if (getPoints() < neededPoints) {
@ -224,8 +224,8 @@ public class TurnLogic {
}
/**
* Contains the remaining possible values, colors, positions, etc. a stone can
* take and its possible neighbors in a group or run
* Contains the remaining possible values, colors, positions, etc. a stone
* can take and its possible neighbors in a group or run
*/
private class StoneState {
int id;
@ -243,11 +243,11 @@ public class TurnLogic {
* Creates a new
*
* @param id
* the stone's identifier
* the stone's identifier
* @param stone
* the stone
* the stone
* @param table
* whether is on the table
* whether is on the table
*/
public StoneState(int id, Stone stone, boolean table) {
this.id = id;
@ -268,7 +268,7 @@ public class TurnLogic {
* Creates a copy
*
* @param stone
* the state to copy
* the state to copy
*/
public StoneState(StoneState stone) {
this.id = stone.id;
@ -300,37 +300,85 @@ public class TurnLogic {
}
/**
* Returns whether the recent changes affect the stone
* Returns whether the recent changes could affect the stone
*
* @param changes
* the changes
* @return whether the stone is affected
* the changes
* @return whether the stone could be affected
*/
public boolean isInterested(HashSet<Integer> changes) {
private boolean isInterested(HashSet<Integer> changes) {
return !(Collections.disjoint(changes, leftRun)
&& Collections.disjoint(changes, rightRun)
&& Collections.disjoint(changes, leftGroup) && Collections.disjoint(
changes, rightGroup));
&& Collections.disjoint(changes, leftGroup) && Collections
.disjoint(changes, rightGroup));
}
public <T extends Comparable<T>> boolean lessThan(T a, T b) {
/**
* Compare two objects, returning true if either is null
*
* @param <T>
* object's type
* @param a
* first object
* @param b
* second object
* @return whether a is less than b or either is null
*/
private <T extends Comparable<T>> boolean lessThan(T a, T b) {
return a == null || b == null || a.compareTo(b) < 0;
}
public <T> boolean same(T a, T b) {
/**
* Compare two objects, returning true if either is null
*
* @param <T>
* object;s type
* @param a
* first object
* @param b
* second object
* @return whether a is less than b or either is null
*/
private <T> boolean same(T a, T b) {
return a == null || b == null || a.equals(b);
}
public boolean step(Integer a, Integer b) {
/**
* Checks whether a and b are consecutive integers or either is null
*
* @param a
* first integer
* @param b
* next integer
* @return whether a + 1 is b or either is null
*/
private boolean step(Integer a, Integer b) {
return a == null || b == null || (int) a == b - 1;
}
public boolean cycleStep(Integer a, Integer b) {
/**
* Checks whether a and b are consecutive integers modulo highest value
* or either i snull
*
* @param a
* first integer
* @param b
* second integer
* @return whether a + 1 is b (modulo highest value) or either is null
*/
private boolean cycleStep(Integer a, Integer b) {
return a == null || b == null || (int) a == b - 1
|| a == settings.getHighestValue() && b == 1;
}
public boolean groupNeighbor(StoneState other) {
/**
* Checks whether this stone could be left to other in a group
*
* @param other
* possible right neighbor
* @return whether they can be neighbors
*/
private boolean groupNeighbor(StoneState other) {
if (isNullSet(leftGroup) && isNullSet(other.rightGroup)) {
return false;
}
@ -340,7 +388,14 @@ public class TurnLogic {
return lessThan(color, other.color) && same(value, other.value);
}
public boolean runNeighbor(StoneState other) {
/**
* Checks whether this stone could be left to other in a run
*
* @param other
* possible right neighbor
* @return whether they can be neighbors
*/
private boolean runNeighbor(StoneState other) {
if (isNullSet(leftRun) && isNullSet(other.rightRun)) {
return false;
}
@ -358,7 +413,16 @@ public class TurnLogic {
}
}
public boolean update(HashSet<Integer> changes) throws Contradiction {
/**
* Updates this stone considering the changes of all changed stones
*
* @param changes
* stones that have changed
* @return whether we could be updated
* @throws Contradiction
* Is thrown if the changes contradict each other
*/
private boolean update(HashSet<Integer> changes) throws Contradiction {
boolean changed = false;
changed |= updateRightRuns(changes);
@ -372,11 +436,29 @@ public class TurnLogic {
return changed;
}
/**
* Checks that two objects are equal to each other and not null
*
* @param a
* @param b
* @return whether a equals b and both are not null
*/
private boolean nonNullEquals(Object a, Object b) {
return a != null && b != null && a.equals(b);
}
private void breakSymmetries(HashSet<Integer> changes) throws Contradiction {
/**
* Try to break symmetries between exchangeable stones to reduce the
* state space
*
* @param changes
* stones that changed
* @throws Contradiction
* Is thrown when there exists another trivial solution by
* symmetry
*/
private void breakSymmetries(HashSet<Integer> changes)
throws Contradiction {
Integer mySym = symmetryBreakerValue();
if (mySym == null) {
return;
@ -387,7 +469,8 @@ public class TurnLogic {
}
StoneState otherStone = top.stones.get(other);
if (!(joker && otherStone.joker || nonNullEquals(value,
otherStone.value) && nonNullEquals(color, otherStone.color))) {
otherStone.value)
&& nonNullEquals(color, otherStone.color))) {
continue;
}
Integer otherSym = top.stones.get(other).symmetryBreakerValue();
@ -402,12 +485,18 @@ public class TurnLogic {
}
}
/**
* Order stones by their neighbors to decide on a solution of a set of
* symmetric solutions
*
* @return the order value or null if it can't be determined yer
*/
private Integer symmetryBreakerValue() {
if (onTable != Boolean.TRUE) {
return -1;
}
if (leftRun.size() > 1 || leftGroup.size() > 1 || rightRun.size() > 1
|| rightGroup.size() > 1) {
if (leftRun.size() > 1 || leftGroup.size() > 1
|| rightRun.size() > 1 || rightGroup.size() > 1) {
return null;
}
int mode;
@ -437,7 +526,17 @@ public class TurnLogic {
return neighbor + stoneCount * mode;
}
public boolean updateRightRuns(HashSet<Integer> changes)
/**
* Updates this stone's right run neighbors considering the changes of
* all changed stones
*
* @param changes
* stones that have changed
* @return whether we could be updated
* @throws Contradiction
* Is thrown if the changes contradict each other
*/
private boolean updateRightRuns(HashSet<Integer> changes)
throws Contradiction {
boolean changed = false;
HashSet<Integer> relevantChanges = new HashSet<Integer>(leftRun);
@ -447,7 +546,8 @@ public class TurnLogic {
if (!other.runNeighbor(this) || !other.rightRun.contains(id)) {
leftRun.remove(i);
changed = true;
} else if (other.rightRun.size() == 1 && other.onTable == Boolean.TRUE) {
} else if (other.rightRun.size() == 1
&& other.onTable == Boolean.TRUE) {
changed |= leftRun.retainAll(Arrays.asList(i));
changed |= onTable != Boolean.TRUE;
onTable = true;
@ -457,7 +557,17 @@ public class TurnLogic {
return changed;
}
public boolean updateLeftRuns(HashSet<Integer> changes)
/**
* Updates this stone's left run neighbors considering the changes of
* all changed stones
*
* @param changes
* stones that have changed
* @return whether we could be updated
* @throws Contradiction
* Is thrown if the changes contradict each other
*/
private boolean updateLeftRuns(HashSet<Integer> changes)
throws Contradiction {
boolean changed = false;
HashSet<Integer> relevantChanges = new HashSet<Integer>(rightRun);
@ -467,7 +577,8 @@ public class TurnLogic {
if (!this.runNeighbor(other) || !other.leftRun.contains(id)) {
rightRun.remove(i);
changed = true;
} else if (other.leftRun.size() == 1 && other.onTable == Boolean.TRUE) {
} else if (other.leftRun.size() == 1
&& other.onTable == Boolean.TRUE) {
changed |= rightRun.retainAll(Arrays.asList(i));
changed |= onTable != Boolean.TRUE;
onTable = true;
@ -477,14 +588,25 @@ public class TurnLogic {
return changed;
}
public boolean updateRightGroups(HashSet<Integer> changes)
/**
* Updates this stone's right group neighbors considering the changes of
* all changed stones
*
* @param changes
* stones that have changed
* @return whether we could be updated
* @throws Contradiction
* Is thrown if the changes contradict each other
*/
private boolean updateRightGroups(HashSet<Integer> changes)
throws Contradiction {
boolean changed = false;
HashSet<Integer> relevantChanges = new HashSet<Integer>(leftGroup);
relevantChanges.retainAll(changes);
for (int i : relevantChanges) {
StoneState other = top.stones.get(i);
if (!other.groupNeighbor(this) || !other.rightGroup.contains(id)) {
if (!other.groupNeighbor(this)
|| !other.rightGroup.contains(id)) {
leftGroup.remove(i);
changed = true;
} else if (other.rightGroup.size() == 1
@ -498,7 +620,17 @@ public class TurnLogic {
return changed;
}
public boolean updateLeftGroups(HashSet<Integer> changes)
/**
* Updates this stone's left group neighbors considering the changes of
* all changed stones
*
* @param changes
* stones that have changed
* @return whether we could be updated
* @throws Contradiction
* Is thrown if the changes contradict each other
*/
private boolean updateLeftGroups(HashSet<Integer> changes)
throws Contradiction {
boolean changed = false;
HashSet<Integer> relevantChanges = new HashSet<Integer>(leftGroup);
@ -509,7 +641,8 @@ public class TurnLogic {
if (!this.groupNeighbor(other) || !other.leftGroup.contains(id)) {
rightGroup.remove(i);
changed = true;
} else if (other.leftGroup.size() == 1 && other.onTable == Boolean.TRUE) {
} else if (other.leftGroup.size() == 1
&& other.onTable == Boolean.TRUE) {
changed |= rightGroup.retainAll(Arrays.asList(i));
changed |= onTable != Boolean.TRUE;
onTable = true;
@ -519,7 +652,14 @@ public class TurnLogic {
return changed;
}
public boolean checkState() throws Contradiction {
/**
* Enforce local rule consistency
*
* @return whether this stone changed
* @throws Contradiction
* Is thrown if this stone can't be placed anywhere
*/
private boolean checkState() throws Contradiction {
boolean changed = false;
if (onTable == Boolean.FALSE) {
@ -550,6 +690,12 @@ public class TurnLogic {
return changed;
}
/**
* If this stone has to be in a group it can't be part of a run and the
* other way around
*
* @return whether this stone changed
*/
private boolean checkSetTypeKnown() {
boolean changed = false;
if (!(leftGroup.contains(null) || rightGroup.contains(null))) {
@ -563,11 +709,18 @@ public class TurnLogic {
return changed;
}
/**
* If we have no group/run neighbors and no left/right neighbors but are
* on the table we need to have a neighbor for the remaining case
*
* @return whether this stone changed
*/
private boolean checkLonelyStone() {
boolean changed = false;
@SuppressWarnings("unchecked")
List<HashSet<Integer>> sets = Arrays.<HashSet<Integer>> asList(leftGroup,
rightGroup, leftRun, rightRun, leftGroup, rightGroup, leftRun);
List<HashSet<Integer>> sets = Arrays.<HashSet<Integer>> asList(
leftGroup, rightGroup, leftRun, rightRun, leftGroup,
rightGroup, leftRun);
for (int i = 0; i < 4; i++) {
if (isNullSet(sets.get(i)) && isNullSet(sets.get(i + 1))
&& isNullSet(sets.get(i + 2))) {
@ -577,6 +730,12 @@ public class TurnLogic {
return changed;
}
/**
* If we have a neighbor that is on the end of a set we can't be on the
* end either
*
* @return whether this stone changed
*/
private boolean checkNeighborStoneNeeded() {
boolean changed = false;
if (isSingleNonNullSet(leftRun)
@ -599,10 +758,19 @@ public class TurnLogic {
return changed;
}
/**
* When we need to have some kind of neighbor but there isn't any
* possible one we're either not on the table or if we have to have a
* contradiction
*
* @return whether this stone changed
* @throws Contradiction
* Is thrown if this stone can't be placed anywhere
*/
private boolean checkStoneCanBeOnTable() throws Contradiction {
boolean changed = false;
if (leftGroup.isEmpty() || rightGroup.isEmpty() || leftRun.isEmpty()
|| rightRun.isEmpty()) {
if (leftGroup.isEmpty() || rightGroup.isEmpty()
|| leftRun.isEmpty() || rightRun.isEmpty()) {
if (onTable == Boolean.TRUE) {
throw new Contradiction();
}
@ -618,6 +786,13 @@ public class TurnLogic {
return changed;
}
/**
* If we need to be in a group and in a run at the same time we have a
* contradiction
*
* @throws Contradiction
* Is thrown if this stone can't be placed anywhere
*/
private void checkGroupRunExclusive() throws Contradiction {
if ((isSingleNonNullSet(leftGroup) || isSingleNonNullSet(rightGroup))
&& (isSingleNonNullSet(leftRun) || isSingleNonNullSet(rightRun))) {
@ -625,6 +800,11 @@ public class TurnLogic {
}
}
/**
* Try to derive the value of a joker stone from it's neighbor(s)
*
* @return whether we could derive the value
*/
private boolean checkJokerValue() {
boolean changed = false;
if (this.value == null) {
@ -640,7 +820,8 @@ public class TurnLogic {
} else if (isSingleNonNullSet(rightRun)) {
Integer value = top.stones.get(rightRun.iterator().next()).value;
if (value != null) {
this.value = (value - 2) % settings.getHighestValue() + 1;
this.value = (value - 2) % settings.getHighestValue()
+ 1;
}
}
changed |= this.value != null;
@ -648,6 +829,11 @@ public class TurnLogic {
return changed;
}
/**
* Try to derive the color of a joker stone from it's neighbor(s)
*
* @return whether we could derive the color
*/
private boolean checkJokerColor() {
boolean changed = false;
if (this.color == null) {
@ -661,6 +847,11 @@ public class TurnLogic {
return changed;
}
/**
* Check whether this stone's position, value and color are known
*
* @return true when this stone is solved
*/
public boolean isSolved() {
if (onTable == Boolean.FALSE) {
return true;
@ -668,13 +859,18 @@ public class TurnLogic {
if (onTable == null || color == null || value == null) {
return false;
}
if (leftRun.size() > 1 || rightRun.size() > 1 || leftGroup.size() > 1
|| rightGroup.size() > 1) {
if (leftRun.size() > 1 || rightRun.size() > 1
|| leftGroup.size() > 1 || rightGroup.size() > 1) {
return false;
}
return true;
}
/**
* How badly we want to get rid of this stone
*
* @return this stone's badness score
*/
public double getScore() {
if (fromTable) {
return 0;
@ -682,6 +878,11 @@ public class TurnLogic {
return 1;
}
/**
* How many points we get for melding this stone
*
* @return amount of points
*/
public double getPoints() {
if (fromTable) {
return 0;
@ -692,13 +893,22 @@ public class TurnLogic {
return (double) value;
}
/**
* Checks whether we need a joker on the left or right side to place
* this stone
*
* @param side
* which side to check
* @return true when a joker is the only possible neighbor
*/
@SuppressWarnings("unchecked")
public boolean needsJoker(boolean side) {
if (onTable != Boolean.TRUE) {
return false;
}
for (HashSet<Integer> set : side ? Arrays.asList(leftGroup, leftRun)
: Arrays.asList(rightGroup, rightRun)) {
for (HashSet<Integer> set : side ? Arrays
.asList(leftGroup, leftRun) : Arrays.asList(rightGroup,
rightRun)) {
cancle: if (!set.contains(null)) {
for (int idx : set) {
if (!top.stones.get(idx).joker) {
@ -713,10 +923,24 @@ public class TurnLogic {
}
}
/**
* Check whether a set of Integers only contains null
*
* @param i
* set to check
* @return true when it only contains null
*/
private static boolean isNullSet(HashSet<Integer> i) {
return i.size() == 1 && i.contains(null);
}
/**
* Check whether a set of Integers only contains a single non null value
*
* @param i
* set to check
* @return true when it only contains a single non null value
*/
private static boolean isSingleNonNullSet(HashSet<Integer> i) {
return i.size() == 1 && !i.contains(null);
}
@ -725,11 +949,11 @@ public class TurnLogic {
* Creates new turn logic
*
* @param settings
* the game settings
* the game settings
* @param tableStones
* all stones on the table
* all stones on the table
* @param handStones
* all stones on the hand
* all stones on the hand
*/
public TurnLogic(GameSettings settings, Collection<Stone> tableStones,
Collection<Stone> handStones) {
@ -758,17 +982,18 @@ public class TurnLogic {
@Override
public int compare(Pair<Stone, Boolean> o1, Pair<Stone, Boolean> o2) {
int cmp;
cmp = ((Boolean) o1.getFirst().isJoker()).compareTo(o2.getFirst()
.isJoker());
cmp = ((Boolean) o1.getFirst().isJoker()).compareTo(o2
.getFirst().isJoker());
if (cmp != 0) {
return -cmp;
}
cmp = (o1.getFirst().getColor()).compareTo(o2.getFirst().getColor());
cmp = (o1.getFirst().getColor()).compareTo(o2.getFirst()
.getColor());
if (cmp != 0) {
return cmp;
}
cmp = ((Integer) o1.getFirst().getValue()).compareTo(o2.getFirst()
.getValue());
cmp = ((Integer) o1.getFirst().getValue()).compareTo(o2
.getFirst().getValue());
return cmp;
}
});
@ -787,6 +1012,9 @@ public class TurnLogic {
neededPoints = settings.getInitialMeldThreshold();
}
/**
* Remove a contradicted state from the try stack, reset top
*/
private void pop() {
stack.remove(stack.size() - 1);
if (!stack.isEmpty()) {
@ -794,16 +1022,29 @@ public class TurnLogic {
}
}
/**
* Remove an unsolved state, to be replaced with refined state, from the try
* stack
*/
private void replace() {
stack.remove(stack.size() - 1);
}
/**
* Push multiple new state onto the try stack
*
* @param states
* states to push
*/
private void pushes(State... states) {
for (State state : states) {
stack.add(state);
}
}
/**
* Done with modifying the try stack, reset top
*/
private void done() {
top = stack.get(stack.size() - 1);
}
@ -835,7 +1076,8 @@ public class TurnLogic {
ArrayList<Stone> setStones = new ArrayList<Stone>();
while (true) {
setStones.add(inputStones.get(stone.id));
if (isNullSet(stone.rightRun) && isNullSet(stone.rightGroup)) {
if (isNullSet(stone.rightRun)
&& isNullSet(stone.rightGroup)) {
break;
}
Integer rightRunID = stone.rightRun.iterator().next();
@ -890,7 +1132,10 @@ public class TurnLogic {
return neededScore;
}
private void branch() throws Contradiction {
/**
* Refine the currently unsolved top stack state
*/
private void branch() {
for (int i = 0; i < stoneCount; i++) {
StoneState stone = top.stones.get(i);
if (stone.leftRun.size() > 1) {
@ -929,6 +1174,12 @@ public class TurnLogic {
throw new Error("Internal AI error");
}
/**
* Create states for all possible values of stone i
*
* @param i
* stone id to branch on
*/
private void branchValue(int i) {
replace();
for (int value = 1; value <= settings.getHighestValue(); value++) {
@ -939,6 +1190,12 @@ public class TurnLogic {
}
}
/**
* Create states for all possible colors of stone i
*
* @param i
* stone id to branch on
*/
private void branchColor(int i) {
replace();
for (StoneColor color : stoneColors) {
@ -947,8 +1204,15 @@ public class TurnLogic {
newState.changedStones.add(i);
pushes(newState);
}
}
/**
* Create states for all possible right group neighbors of stone i
*
* @param i
* stone id to branch on
*/
private void branchRightGroup(int i, StoneState stone) {
replace();
for (Integer id : stone.rightGroup) {
@ -960,6 +1224,12 @@ public class TurnLogic {
}
}
/**
* Create states for all possible left group neighbors of stone i
*
* @param i
* stone id to branch on
*/
private void branchLeftGroup(int i, StoneState stone) {
replace();
for (Integer id : stone.leftGroup) {
@ -971,6 +1241,12 @@ public class TurnLogic {
}
}
/**
* Create states for all possible right run neighbors of stone i
*
* @param i
* stone id to branch on
*/
private void branchRightRun(int i, StoneState stone) {
replace();
for (Integer id : stone.rightRun) {
@ -982,6 +1258,12 @@ public class TurnLogic {
}
}
/**
* Create states for all possible left run neighbors of stone i
*
* @param i
* stone id to branch on
*/
private void branchLeftRun(int i, StoneState stone) {
replace();
for (Integer id : stone.leftRun) {
@ -993,6 +1275,12 @@ public class TurnLogic {
}
}
/**
* Create states with stone i on the table and stone i on the hand
*
* @param i
* stone id to branch on
*/
private void branchOnHand(int i) {
replace();
State newState = new State(top);