Reimplemented AI (old one was too slow)
git-svn-id: svn://sunsvr01.isp.uni-luebeck.de/swproj13/trunk@443 72836036-5685-4462-b002-a69064685172
This commit is contained in:
parent
1b9c7c4778
commit
e06ba8ea13
26 changed files with 623 additions and 1755 deletions
|
@ -1,391 +1,633 @@
|
||||||
package jrummikub.ai;
|
package jrummikub.ai;
|
||||||
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.constant;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.index;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.lessThan;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.lessThanEq;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.offset;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.same;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.sum;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.unless;
|
|
||||||
import static jrummikub.ai.fdsolver.Constraints.when;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Solver;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
import jrummikub.model.GameSettings;
|
import jrummikub.model.GameSettings;
|
||||||
import jrummikub.model.Stone;
|
import jrummikub.model.Stone;
|
||||||
import jrummikub.model.StoneColor;
|
import jrummikub.model.StoneColor;
|
||||||
|
import jrummikub.util.Pair;
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Humanlike Advanced Logic (HAL) that models the options given to a
|
|
||||||
* player in a turn to the Solver MCP.
|
|
||||||
*/
|
|
||||||
public class TurnLogic {
|
public class TurnLogic {
|
||||||
private Solver solver = new Solver();
|
GameSettings settings;
|
||||||
private GameSettings settings;
|
int stoneCount;
|
||||||
private int stoneCount;
|
StoneColor maxColor;
|
||||||
private int maxSetSize;
|
StoneColor minColor;
|
||||||
private int maxHandPoints;
|
ArrayList<StoneColor> stoneColors;
|
||||||
|
|
||||||
private Var<Boolean> trueVar;
|
ArrayList<State> stack = new ArrayList<State>();
|
||||||
private Var<Integer> totalPoints;
|
State top;
|
||||||
|
|
||||||
|
int neededPoints = 0;
|
||||||
|
double neededScore = Double.NEGATIVE_INFINITY;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static class Contradiction extends Throwable {
|
||||||
|
|
||||||
private List<Var<Boolean>> onTable = new ArrayList<Var<Boolean>>();
|
}
|
||||||
|
|
||||||
private List<Var<Integer>> pointsValue = new ArrayList<Var<Integer>>();
|
private class State {
|
||||||
|
ArrayList<StoneState> stones = new ArrayList<StoneState>();
|
||||||
|
HashSet<Integer> changedStones = new HashSet<Integer>();
|
||||||
|
ArrayList<Integer> jokerIDs = new ArrayList<Integer>();
|
||||||
|
|
||||||
private List<Var<Integer>> stoneValue = new ArrayList<Var<Integer>>();
|
public State() {
|
||||||
private List<Var<Integer>> leftStoneValue = new ArrayList<Var<Integer>>();
|
}
|
||||||
private List<Var<Integer>> rightStoneValue = new ArrayList<Var<Integer>>();
|
|
||||||
|
|
||||||
private List<Var<StoneColor>> stoneColor = new ArrayList<Var<StoneColor>>();
|
public State(State state) {
|
||||||
private List<Var<StoneColor>> leftStoneColor = new ArrayList<Var<StoneColor>>();
|
for (StoneState stone : state.stones) {
|
||||||
private List<Var<StoneColor>> rightStoneColor = new ArrayList<Var<StoneColor>>();
|
stones.add(new StoneState(stone));
|
||||||
|
}
|
||||||
|
jokerIDs = state.jokerIDs;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Var<Integer>> lowCount = new ArrayList<Var<Integer>>();
|
public void add(StoneState stone) {
|
||||||
private List<Var<Integer>> leftLowCount = new ArrayList<Var<Integer>>();
|
stones.add(stone);
|
||||||
private List<Var<Integer>> rightLowCount = new ArrayList<Var<Integer>>();
|
changedStones.add(stones.size() - 1);
|
||||||
|
if (stone.joker) {
|
||||||
|
jokerIDs.add(stone.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Var<Integer>> highCount = new ArrayList<Var<Integer>>();
|
public void updateStones() throws Contradiction {
|
||||||
private List<Var<Integer>> leftHighCount = new ArrayList<Var<Integer>>();
|
checkJokers();
|
||||||
private List<Var<Integer>> rightHighCount = new ArrayList<Var<Integer>>();
|
for (int i : changedStones) {
|
||||||
|
stones.get(i).checkState();
|
||||||
|
}
|
||||||
|
checkScoreAndPoints();
|
||||||
|
while (!changedStones.isEmpty()) {
|
||||||
|
updateStonesStep();
|
||||||
|
checkScoreAndPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Var<Integer>> setSize = new ArrayList<Var<Integer>>();
|
public void updateStonesStep() throws Contradiction {
|
||||||
|
HashSet<Integer> newChangedStones = new HashSet<Integer>();
|
||||||
|
for (int i = 0; i < stoneCount; i++) {
|
||||||
|
StoneState stone = stones.get(i);
|
||||||
|
if (stone.isInterested(changedStones)) {
|
||||||
|
if (stone.update(changedStones)) {
|
||||||
|
newChangedStones.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changedStones = newChangedStones;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Var<Boolean>> isRun = new ArrayList<Var<Boolean>>();
|
public boolean isSolved() {
|
||||||
|
for (StoneState stone : stones) {
|
||||||
|
if (!stone.isSolved())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Var<Integer>> leftNeighbor = new ArrayList<Var<Integer>>();
|
public void checkJokers() throws Contradiction {
|
||||||
private List<Var<Integer>> rightNeighbor = new ArrayList<Var<Integer>>();
|
for (int i = 0; i < jokerIDs.size(); i++) {
|
||||||
|
StoneState left = stones.get(jokerIDs.get(i));
|
||||||
|
|
||||||
private List<Var<Integer>> stoneID = new ArrayList<Var<Integer>>();
|
HashSet<Integer> leftLeftGroup = new HashSet<Integer>(
|
||||||
|
left.leftGroup);
|
||||||
|
HashSet<Integer> leftLeftRun = new HashSet<Integer>(
|
||||||
|
left.leftRun);
|
||||||
|
leftLeftGroup.remove(null);
|
||||||
|
leftLeftRun.remove(null);
|
||||||
|
int runID, groupID;
|
||||||
|
if (leftLeftGroup.isEmpty()) {
|
||||||
|
groupID = -1;
|
||||||
|
} else {
|
||||||
|
groupID = Collections.min(leftLeftGroup);
|
||||||
|
}
|
||||||
|
if (leftLeftRun.isEmpty()) {
|
||||||
|
runID = -1;
|
||||||
|
} else {
|
||||||
|
runID = Collections.min(leftLeftRun);
|
||||||
|
}
|
||||||
|
for (int j = i + 1; j < jokerIDs.size(); j++) {
|
||||||
|
StoneState right = stones.get(jokerIDs.get(j));
|
||||||
|
|
||||||
private List<Var<Boolean>> hasLeftNeighbor = new ArrayList<Var<Boolean>>();
|
if (right.leftGroup.remove(groupID)) {
|
||||||
private List<Var<Boolean>> hasRightNeighbor = new ArrayList<Var<Boolean>>();
|
changedStones.add(jokerIDs.get(j));
|
||||||
|
}
|
||||||
|
if (right.leftRun.remove(runID)) {
|
||||||
|
changedStones.add(jokerIDs.get(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Boolean> isJoker = new ArrayList<Boolean>();
|
public void checkScoreAndPoints() throws Contradiction {
|
||||||
|
if (getPoints() < neededPoints) {
|
||||||
|
throw new Contradiction();
|
||||||
|
}
|
||||||
|
if (getScore() <= neededScore) {
|
||||||
|
throw new Contradiction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPoints() {
|
||||||
|
int sum = 0;
|
||||||
|
for (StoneState stone : stones) {
|
||||||
|
if (stone.onTable != Boolean.FALSE) {
|
||||||
|
sum += stone.getPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScore() {
|
||||||
|
double sum = 0;
|
||||||
|
for (StoneState stone : stones) {
|
||||||
|
if (stone.onTable != Boolean.FALSE) {
|
||||||
|
sum += stone.getScore();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StoneState {
|
||||||
|
int id;
|
||||||
|
boolean joker;
|
||||||
|
Integer value;
|
||||||
|
StoneColor color;
|
||||||
|
Boolean onTable;
|
||||||
|
boolean fromTable;
|
||||||
|
HashSet<Integer> leftRun;
|
||||||
|
HashSet<Integer> rightRun;
|
||||||
|
HashSet<Integer> leftGroup;
|
||||||
|
HashSet<Integer> rightGroup;
|
||||||
|
|
||||||
|
public StoneState(int id, Stone stone, boolean table) {
|
||||||
|
this.id = id;
|
||||||
|
joker = stone.isJoker();
|
||||||
|
if (!joker) {
|
||||||
|
this.value = stone.getValue();
|
||||||
|
this.color = stone.getColor();
|
||||||
|
}
|
||||||
|
onTable = table ? true : null;
|
||||||
|
fromTable = table;
|
||||||
|
leftRun = makeFullSet();
|
||||||
|
rightRun = makeFullSet();
|
||||||
|
leftGroup = makeFullSet();
|
||||||
|
rightGroup = makeFullSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoneState(StoneState stone) {
|
||||||
|
this.id = stone.id;
|
||||||
|
this.joker = stone.joker;
|
||||||
|
this.value = stone.value;
|
||||||
|
this.color = stone.color;
|
||||||
|
this.onTable = stone.onTable;
|
||||||
|
this.fromTable = stone.fromTable;
|
||||||
|
this.leftRun = new HashSet<Integer>(stone.leftRun);
|
||||||
|
this.rightRun = new HashSet<Integer>(stone.rightRun);
|
||||||
|
this.leftGroup = new HashSet<Integer>(stone.leftGroup);
|
||||||
|
this.rightGroup = new HashSet<Integer>(stone.rightGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<Integer> makeFullSet() {
|
||||||
|
HashSet<Integer> set = new HashSet<Integer>();
|
||||||
|
for (int i = 0; i < stoneCount; i++) {
|
||||||
|
if (i != id) {
|
||||||
|
set.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set.add(null);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInterested(HashSet<Integer> changes) {
|
||||||
|
return !(Collections.disjoint(changes, leftRun)
|
||||||
|
&& Collections.disjoint(changes, rightRun)
|
||||||
|
&& Collections.disjoint(changes, leftGroup) && Collections
|
||||||
|
.disjoint(changes, rightGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNullSet(HashSet<Integer> i) {
|
||||||
|
return i.size() == 1 && i.contains(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSingleNonNullSet(HashSet<Integer> i) {
|
||||||
|
return i.size() == 1 && !i.contains(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <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) {
|
||||||
|
return a == null || b == null || a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean step(Integer a, Integer b) {
|
||||||
|
return a == null || b == null || (int) a == b - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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) {
|
||||||
|
if (isNullSet(leftGroup) && isNullSet(other.rightGroup)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (other.color == minColor || color == maxColor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return lessThan(color, other.color) && same(value, other.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runNeighbor(StoneState other) {
|
||||||
|
if (isNullSet(leftRun) && isNullSet(other.rightRun)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!same(color, other.color)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (settings.isNoLimits()) {
|
||||||
|
return cycleStep(value, other.value);
|
||||||
|
} else {
|
||||||
|
if (((Integer) 1).equals(other.value)
|
||||||
|
|| ((Integer) settings.getHighestValue()).equals(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return step(value, other.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean update(HashSet<Integer> changes) throws Contradiction {
|
||||||
|
boolean changed = false;
|
||||||
|
changed |= updateRuns(changes);
|
||||||
|
changed |= updateGroups(changes);
|
||||||
|
changed |= checkState();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateRuns(HashSet<Integer> changes)
|
||||||
|
throws Contradiction {
|
||||||
|
boolean changed = false;
|
||||||
|
HashSet<Integer> relevantChanges = new HashSet<Integer>(leftRun);
|
||||||
|
relevantChanges.retainAll(changes);
|
||||||
|
for (int i : relevantChanges) {
|
||||||
|
StoneState other = top.stones.get(i);
|
||||||
|
if (!other.runNeighbor(this) || !other.rightRun.contains(id)) {
|
||||||
|
leftRun.remove(i);
|
||||||
|
changed = true;
|
||||||
|
} else if (other.rightRun.size() == 1
|
||||||
|
&& other.onTable == Boolean.TRUE) {
|
||||||
|
changed |= leftRun.retainAll(Arrays.asList(i));
|
||||||
|
changed |= onTable != Boolean.TRUE;
|
||||||
|
onTable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relevantChanges = new HashSet<Integer>(rightRun);
|
||||||
|
relevantChanges.retainAll(changes);
|
||||||
|
for (int i : relevantChanges) {
|
||||||
|
StoneState other = top.stones.get(i);
|
||||||
|
if (!this.runNeighbor(other) || !other.leftRun.contains(id)) {
|
||||||
|
rightRun.remove(i);
|
||||||
|
changed = true;
|
||||||
|
} else if (other.leftRun.size() == 1
|
||||||
|
&& other.onTable == Boolean.TRUE) {
|
||||||
|
changed |= rightRun.retainAll(Arrays.asList(i));
|
||||||
|
changed |= onTable != Boolean.TRUE;
|
||||||
|
onTable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateGroups(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)) {
|
||||||
|
leftGroup.remove(i);
|
||||||
|
changed = true;
|
||||||
|
} else if (other.rightGroup.size() == 1
|
||||||
|
&& other.onTable == Boolean.TRUE) {
|
||||||
|
changed |= leftGroup.retainAll(Arrays.asList(i));
|
||||||
|
changed |= onTable != Boolean.TRUE;
|
||||||
|
onTable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relevantChanges = new HashSet<Integer>(rightGroup);
|
||||||
|
relevantChanges.retainAll(changes);
|
||||||
|
for (int i : relevantChanges) {
|
||||||
|
StoneState other = top.stones.get(i);
|
||||||
|
if (!this.groupNeighbor(other) || !other.leftGroup.contains(id)) {
|
||||||
|
rightGroup.remove(i);
|
||||||
|
changed = true;
|
||||||
|
} else if (other.leftGroup.size() == 1
|
||||||
|
&& other.onTable == Boolean.TRUE) {
|
||||||
|
changed |= rightGroup.retainAll(Arrays.asList(i));
|
||||||
|
changed |= onTable != Boolean.TRUE;
|
||||||
|
onTable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkState() throws Contradiction {
|
||||||
|
boolean changed = false;
|
||||||
|
if (onTable == Boolean.FALSE) {
|
||||||
|
if (leftRun.size() + rightRun.size() + leftGroup.size()
|
||||||
|
+ rightGroup.size() != 0) {
|
||||||
|
leftRun.clear();
|
||||||
|
rightRun.clear();
|
||||||
|
leftGroup.clear();
|
||||||
|
rightGroup.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(leftGroup.contains(null) || rightGroup.contains(null))) {
|
||||||
|
changed |= leftRun.retainAll(Arrays.asList((Integer) null))
|
||||||
|
| rightRun.retainAll(Arrays.asList((Integer) null));
|
||||||
|
}
|
||||||
|
if (!(leftRun.contains(null) || rightRun.contains(null))) {
|
||||||
|
changed |= leftGroup.retainAll(Arrays.asList((Integer) null))
|
||||||
|
| rightGroup.retainAll(Arrays.asList((Integer) null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
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))) {
|
||||||
|
changed |= sets.get(i + 3).remove(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftGroup.isEmpty() || rightGroup.isEmpty()
|
||||||
|
|| leftRun.isEmpty() || rightRun.isEmpty()) {
|
||||||
|
if (onTable == Boolean.TRUE) {
|
||||||
|
throw new Contradiction();
|
||||||
|
}
|
||||||
|
if (onTable == null) {
|
||||||
|
onTable = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingleNonNullSet(leftRun)
|
||||||
|
&& isNullSet(top.stones.get(leftRun.iterator().next()).leftRun)) {
|
||||||
|
changed |= rightRun.remove(null);
|
||||||
|
}
|
||||||
|
if (isSingleNonNullSet(rightRun)
|
||||||
|
&& isNullSet(top.stones.get(rightRun.iterator().next()).rightRun)) {
|
||||||
|
changed |= leftRun.remove(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSingleNonNullSet(leftGroup)
|
||||||
|
&& isNullSet(top.stones.get(leftGroup.iterator().next()).leftGroup)) {
|
||||||
|
changed |= rightGroup.remove(null);
|
||||||
|
}
|
||||||
|
if (isSingleNonNullSet(rightGroup)
|
||||||
|
&& isNullSet(top.stones.get(rightGroup.iterator().next()).rightGroup)) {
|
||||||
|
changed |= leftGroup.remove(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
changed |= checkJoker();
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkJoker() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSolved() {
|
||||||
|
if (onTable == Boolean.FALSE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (onTable == null || color == null || value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (leftRun.size() > 1 || rightRun.size() > 1
|
||||||
|
|| leftGroup.size() > 1 || rightGroup.size() > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScore() {
|
||||||
|
if (fromTable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPoints() {
|
||||||
|
if (fromTable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (value == null)
|
||||||
|
return (double) settings.getHighestValue();
|
||||||
|
else
|
||||||
|
return (double) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TurnLogic(GameSettings settings, Collection<Stone> tableStones,
|
public TurnLogic(GameSettings settings, Collection<Stone> tableStones,
|
||||||
Collection<Stone> handStones) {
|
Collection<Stone> handStones) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
stoneCount = tableStones.size() + handStones.size();
|
stoneCount = tableStones.size() + handStones.size();
|
||||||
maxSetSize = Math.max(settings.getHighestValue(), settings
|
|
||||||
.getStoneColors().size());
|
|
||||||
|
|
||||||
trueVar = solver.makeVar(true);
|
maxColor = Collections.max(settings.getStoneColors());
|
||||||
|
minColor = Collections.min(settings.getStoneColors());
|
||||||
|
|
||||||
|
stoneColors = new ArrayList<StoneColor>(settings.getStoneColors());
|
||||||
|
Collections.sort(stoneColors);
|
||||||
|
|
||||||
|
top = new State();
|
||||||
|
stack.add(top);
|
||||||
|
|
||||||
|
ArrayList<Pair<Stone, Boolean>> sortedStones = new ArrayList<Pair<Stone, Boolean>>();
|
||||||
|
|
||||||
maxHandPoints = 0;
|
|
||||||
int i = 0;
|
|
||||||
for (Stone stone : tableStones) {
|
for (Stone stone : tableStones) {
|
||||||
addStone(i, true, stone);
|
sortedStones.add(new Pair<Stone, Boolean>(stone, true));
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
for (Stone stone : handStones) {
|
for (Stone stone : handStones) {
|
||||||
addStone(i, false, stone);
|
sortedStones.add(new Pair<Stone, Boolean>(stone, false));
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
Collections.sort(sortedStones, new Comparator<Pair<Stone, Boolean>>() {
|
||||||
for (i = 0; i < stoneCount; i++) {
|
@Override
|
||||||
addConstraints(i);
|
public int compare(Pair<Stone, Boolean> o1, Pair<Stone, Boolean> o2) {
|
||||||
}
|
int cmp;
|
||||||
|
cmp = ((Boolean) o1.getFirst().isJoker()).compareTo(o2
|
||||||
totalPoints = solver.makeRangeVar(settings.getInitialMeldThreshold(),
|
.getFirst().isJoker());
|
||||||
maxHandPoints);
|
if (cmp != 0) {
|
||||||
|
return -cmp;
|
||||||
List<Var<Integer>> points = new ArrayList<Var<Integer>>();
|
}
|
||||||
|
cmp = (o1.getFirst().getColor()).compareTo(o2.getFirst()
|
||||||
for (Var<Integer> var : pointsValue) {
|
.getColor());
|
||||||
if (var != null) {
|
if (cmp != 0) {
|
||||||
points.add(var);
|
return cmp;
|
||||||
|
}
|
||||||
|
cmp = ((Integer) o1.getFirst().getValue()).compareTo(o2
|
||||||
|
.getFirst().getValue());
|
||||||
|
return cmp;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
add(sum(totalPoints, points));
|
int i = 0;
|
||||||
|
for (Pair<Stone, Boolean> pair : sortedStones) {
|
||||||
|
top.add(new StoneState(i++, pair.getFirst(), pair.getSecond()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void needIntialMeldThreshold() {
|
||||||
|
neededPoints = settings.getInitialMeldThreshold();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStone(int i, boolean table, Stone stone) {
|
private void pop() {
|
||||||
if (table) {
|
stack.remove(stack.size() - 1);
|
||||||
onTable.add(trueVar);
|
if (!stack.isEmpty()) {
|
||||||
} else {
|
top = stack.get(stack.size() - 1);
|
||||||
onTable.add(solver.makeBoolVar());
|
|
||||||
}
|
}
|
||||||
isJoker.add(stone.isJoker());
|
|
||||||
if (stone.isJoker()) {
|
|
||||||
stoneValue.add(solver.makeRangeVar(1, settings.getHighestValue()));
|
|
||||||
if (table) {
|
|
||||||
pointsValue.add(null);
|
|
||||||
} else {
|
|
||||||
pointsValue.add(solver.makeRangeVar(0,
|
|
||||||
settings.getHighestValue()));
|
|
||||||
maxHandPoints += settings.getHighestValue();
|
|
||||||
}
|
|
||||||
stoneColor.add(solver.makeVar(settings.getStoneColors()));
|
|
||||||
} else {
|
|
||||||
stoneValue.add(solver.makeVar(stone.getValue()));
|
|
||||||
if (table) {
|
|
||||||
pointsValue.add(null);
|
|
||||||
} else {
|
|
||||||
pointsValue.add(solver.makeVar(0, stone.getValue()));
|
|
||||||
maxHandPoints += stone.getValue();
|
|
||||||
}
|
|
||||||
stoneColor.add(solver.makeVar(stone.getColor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
leftStoneValue.add(solver.makeRangeVar(1, settings.getHighestValue()));
|
|
||||||
rightStoneValue.add(solver.makeRangeVar(1, settings.getHighestValue()));
|
|
||||||
leftStoneColor.add(solver.makeVar(settings.getStoneColors()));
|
|
||||||
rightStoneColor.add(solver.makeVar(settings.getStoneColors()));
|
|
||||||
|
|
||||||
lowCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
leftLowCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
rightLowCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
|
|
||||||
highCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
leftHighCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
rightHighCount.add(solver.makeRangeVar(0, maxSetSize - 1));
|
|
||||||
|
|
||||||
setSize.add(solver.makeRangeVar(2, maxSetSize - 1));
|
|
||||||
|
|
||||||
isRun.add(solver.makeBoolVar());
|
|
||||||
|
|
||||||
leftNeighbor.add(solver.makeRangeVar(0, stoneCount - 1));
|
|
||||||
rightNeighbor.add(solver.makeRangeVar(0, stoneCount - 1));
|
|
||||||
|
|
||||||
stoneID.add(solver.makeVar(i));
|
|
||||||
|
|
||||||
hasLeftNeighbor.add(solver.makeBoolVar());
|
|
||||||
hasRightNeighbor.add(solver.makeBoolVar());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add(Constraint c) {
|
private void replace() {
|
||||||
solver.addConstraint(c);
|
stack.remove(stack.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addConstraints(int i) {
|
private void pushes(State... states) {
|
||||||
// Linking of neighbors
|
for (State state : states) {
|
||||||
add(when(hasLeftNeighbor.get(i),
|
stack.add(state);
|
||||||
index(stoneID.get(i), leftNeighbor.get(i), rightNeighbor)));
|
|
||||||
add(unless(hasLeftNeighbor.get(i), constant(leftNeighbor.get(i), 0)));
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(trueVar, leftNeighbor.get(i), hasRightNeighbor)));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(stoneID.get(i), rightNeighbor.get(i), leftNeighbor)));
|
|
||||||
add(unless(hasRightNeighbor.get(i), constant(rightNeighbor.get(i), 0)));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(trueVar, rightNeighbor.get(i), hasLeftNeighbor)));
|
|
||||||
// hand stones have no neighbors
|
|
||||||
add(unless(onTable.get(i), constant(hasLeftNeighbor.get(i), false)));
|
|
||||||
add(unless(onTable.get(i), constant(hasRightNeighbor.get(i), false)));
|
|
||||||
|
|
||||||
// low/high counts and size
|
|
||||||
add(unless(hasLeftNeighbor.get(i), constant(lowCount.get(i), 0)));
|
|
||||||
add(unless(hasRightNeighbor.get(i), constant(highCount.get(i), 0)));
|
|
||||||
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(leftLowCount.get(i), leftNeighbor.get(i), lowCount)));
|
|
||||||
add(unless(hasLeftNeighbor.get(i), constant(leftLowCount.get(i), 0)));
|
|
||||||
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(rightLowCount.get(i), rightNeighbor.get(i), lowCount)));
|
|
||||||
add(unless(hasRightNeighbor.get(i), constant(rightLowCount.get(i), 0)));
|
|
||||||
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(leftHighCount.get(i), leftNeighbor.get(i), highCount)));
|
|
||||||
add(unless(hasLeftNeighbor.get(i), constant(leftHighCount.get(i), 0)));
|
|
||||||
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(rightHighCount.get(i), rightNeighbor.get(i), highCount)));
|
|
||||||
add(unless(hasRightNeighbor.get(i), constant(rightHighCount.get(i), 0)));
|
|
||||||
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(setSize.get(i), leftNeighbor.get(i), setSize)));
|
|
||||||
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(setSize.get(i), rightNeighbor.get(i), setSize)));
|
|
||||||
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
offset(1, leftLowCount.get(i), lowCount.get(i))));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
offset(1, lowCount.get(i), rightLowCount.get(i))));
|
|
||||||
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
offset(1, rightHighCount.get(i), highCount.get(i))));
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
offset(1, highCount.get(i), leftHighCount.get(i))));
|
|
||||||
|
|
||||||
add(when(onTable.get(i),
|
|
||||||
sum(lowCount.get(i), highCount.get(i), setSize.get(i))));
|
|
||||||
|
|
||||||
add(unless(onTable.get(i), constant(setSize.get(i), 2)));
|
|
||||||
|
|
||||||
// set same rules
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(isRun.get(i), leftNeighbor.get(i), isRun)));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(isRun.get(i), rightNeighbor.get(i), isRun)));
|
|
||||||
|
|
||||||
add(unless(onTable.get(i), constant(isRun.get(i), false)));
|
|
||||||
|
|
||||||
// rule neighbors
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(leftStoneColor.get(i), leftNeighbor.get(i), stoneColor)));
|
|
||||||
add(unless(hasLeftNeighbor.get(i),
|
|
||||||
constant(leftStoneColor.get(i), StoneColor.RED)));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(rightStoneColor.get(i), rightNeighbor.get(i), stoneColor)));
|
|
||||||
add(unless(hasRightNeighbor.get(i),
|
|
||||||
constant(rightStoneColor.get(i), StoneColor.RED)));
|
|
||||||
|
|
||||||
add(when(hasLeftNeighbor.get(i),
|
|
||||||
index(leftStoneValue.get(i), leftNeighbor.get(i), stoneValue)));
|
|
||||||
add(unless(hasLeftNeighbor.get(i), constant(leftStoneValue.get(i), 1)));
|
|
||||||
add(when(hasRightNeighbor.get(i),
|
|
||||||
index(rightStoneValue.get(i), rightNeighbor.get(i), stoneValue)));
|
|
||||||
add(unless(hasRightNeighbor.get(i), constant(rightStoneValue.get(i), 1)));
|
|
||||||
// general rules
|
|
||||||
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
lessThanEq(leftStoneValue.get(i), stoneValue.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
lessThanEq(stoneValue.get(i), rightStoneValue.get(i)))));
|
|
||||||
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
lessThanEq(leftStoneColor.get(i), stoneColor.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
lessThanEq(stoneColor.get(i), rightStoneColor.get(i)))));
|
|
||||||
|
|
||||||
// run rules
|
|
||||||
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
offset(1, leftStoneValue.get(i), stoneValue.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
offset(1, stoneValue.get(i), rightStoneValue.get(i)))));
|
|
||||||
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
same(leftStoneColor.get(i), stoneColor.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
when(isRun.get(i),
|
|
||||||
same(stoneColor.get(i), rightStoneColor.get(i)))));
|
|
||||||
|
|
||||||
// group rules
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
unless(isRun.get(i),
|
|
||||||
same(leftStoneValue.get(i), stoneValue.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
unless(isRun.get(i),
|
|
||||||
same(stoneValue.get(i), rightStoneValue.get(i)))));
|
|
||||||
|
|
||||||
add(when(
|
|
||||||
hasLeftNeighbor.get(i),
|
|
||||||
unless(isRun.get(i),
|
|
||||||
lessThan(leftStoneColor.get(i), stoneColor.get(i)))));
|
|
||||||
add(when(
|
|
||||||
hasRightNeighbor.get(i),
|
|
||||||
unless(isRun.get(i),
|
|
||||||
lessThan(stoneColor.get(i), rightStoneColor.get(i)))));
|
|
||||||
|
|
||||||
// joker defaulting
|
|
||||||
if (isJoker.get(i)) {
|
|
||||||
add(unless(onTable.get(i), constant(stoneValue.get(i), 1)));
|
|
||||||
add(unless(onTable.get(i),
|
|
||||||
constant(stoneColor.get(i), StoneColor.RED)));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// initial meld points
|
private void done() {
|
||||||
if (pointsValue.get(i) != null) {
|
top = stack.get(stack.size() - 1);
|
||||||
add(when(onTable.get(i),
|
|
||||||
same(stoneValue.get(i), pointsValue.get(i))));
|
|
||||||
add(unless(onTable.get(i), constant(pointsValue.get(i), 0)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean solve() {
|
public boolean solve() {
|
||||||
Set<Integer> tableIDs = new HashSet<Integer>();
|
if (top.isSolved()) {
|
||||||
Set<Integer> leftIDs = new HashSet<Integer>();
|
pop();
|
||||||
boolean res = solver.solve();
|
}
|
||||||
if (!res)
|
while (stack.size() != 0) {
|
||||||
return false;
|
try {
|
||||||
System.out.println("== Hand ==");
|
top.updateStones();
|
||||||
for (int i = 0; i < stoneCount; i++) {
|
if (top.isSolved()) {
|
||||||
if (onTable.get(i).getValue()) {
|
return true;
|
||||||
tableIDs.add(i);
|
|
||||||
if (!hasLeftNeighbor.get(i).getValue()) {
|
|
||||||
leftIDs.add(i);
|
|
||||||
}
|
}
|
||||||
} else {
|
branch();
|
||||||
outputStone(i);
|
done();
|
||||||
|
} catch (Contradiction c) {
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("");
|
return false;
|
||||||
System.out.println("== Table ==");
|
|
||||||
boolean newLine = false;
|
|
||||||
boolean first = true;
|
|
||||||
int nextID = -1; // leftIDs.iterator().next();
|
|
||||||
while (!tableIDs.isEmpty()) {
|
|
||||||
if (tableIDs.contains(nextID)) {
|
|
||||||
tableIDs.remove(nextID);
|
|
||||||
leftIDs.remove(nextID);
|
|
||||||
if (newLine) {
|
|
||||||
if (!first) {
|
|
||||||
System.out.println("");
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
newLine = false;
|
|
||||||
}
|
|
||||||
outputStone(nextID);
|
|
||||||
if (!hasRightNeighbor.get(nextID).getValue()) {
|
|
||||||
newLine = true;
|
|
||||||
nextID = -1;
|
|
||||||
} else {
|
|
||||||
nextID = rightNeighbor.get(nextID).getValue();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (leftIDs.isEmpty()) {
|
|
||||||
nextID = tableIDs.iterator().next();
|
|
||||||
} else {
|
|
||||||
nextID = leftIDs.iterator().next();
|
|
||||||
}
|
|
||||||
newLine = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.out.println("");
|
|
||||||
System.out.println("");
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputStone(int i) {
|
public double optimize() {
|
||||||
System.out.print("["
|
while (solve()) {
|
||||||
+ stoneColor.get(i).getValue().toString().substring(0, 1)
|
neededScore = top.getScore();
|
||||||
+ stoneValue.get(i).getValue() + "]");
|
}
|
||||||
/*
|
return neededScore;
|
||||||
* + "," + leftNeighbor.get(i).getValue() +
|
}
|
||||||
* hasLeftNeighbor.get(i).getValue() + lowCount.get(i).getValue() + ","
|
|
||||||
* + rightNeighbor.get(i).getValue() +
|
private void branch() throws Contradiction {
|
||||||
* hasRightNeighbor.get(i).getValue() + highCount.get(i).getValue() +
|
for (int i = 0; i < stoneCount; i++) {
|
||||||
* "]");
|
StoneState stone = top.stones.get(i);
|
||||||
*/
|
if (stone.onTable == null) {
|
||||||
|
replace();
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).onTable = false;
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
State altState = new State(top);
|
||||||
|
altState.stones.get(i).onTable = true;
|
||||||
|
altState.changedStones.add(i);
|
||||||
|
pushes(altState, newState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.leftRun.size() > 1) {
|
||||||
|
replace();
|
||||||
|
for (Integer id : stone.leftRun) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).leftRun.clear();
|
||||||
|
newState.stones.get(i).leftRun.add(id);
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.rightRun.size() > 1) {
|
||||||
|
replace();
|
||||||
|
for (Integer id : stone.rightRun) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).rightRun.clear();
|
||||||
|
newState.stones.get(i).rightRun.add(id);
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.leftGroup.size() > 1) {
|
||||||
|
replace();
|
||||||
|
for (Integer id : stone.leftGroup) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).leftGroup.clear();
|
||||||
|
newState.stones.get(i).leftGroup.add(id);
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.rightGroup.size() > 1) {
|
||||||
|
replace();
|
||||||
|
for (Integer id : stone.rightGroup) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).rightGroup.clear();
|
||||||
|
newState.stones.get(i).rightGroup.add(id);
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.color == null) {
|
||||||
|
replace();
|
||||||
|
for (StoneColor color : stoneColors) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).color = color;
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stone.value == null) {
|
||||||
|
replace();
|
||||||
|
for (int value = 1; value <= settings.getHighestValue(); value++) {
|
||||||
|
State newState = new State(top);
|
||||||
|
newState.stones.get(i).value = value;
|
||||||
|
newState.changedStones.add(i);
|
||||||
|
pushes(newState);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should never happen
|
||||||
|
throw new Error("Internal AI error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public abstract class Constraint {
|
|
||||||
Collection<Propagator> cachedPropagators;
|
|
||||||
|
|
||||||
public abstract Collection<Var<?>> getWatchedVars();
|
|
||||||
|
|
||||||
public abstract Collection<Propagator> getPropagators(boolean negate);
|
|
||||||
|
|
||||||
public abstract Satisfiability getSatisfiability();
|
|
||||||
|
|
||||||
public boolean isSatisfiable() {
|
|
||||||
return getSatisfiability() != Satisfiability.UNSAT;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.constraint.Filter;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.FilterConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.IfConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.IndexConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.LessThan;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.ListSumConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.OffsetConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.SameConstraint;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.SumConstraint;
|
|
||||||
|
|
||||||
public class Constraints {
|
|
||||||
public static Constraint when(Var<Boolean> cond, Constraint c) {
|
|
||||||
return new IfConstraint(false, cond, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Constraint unless(Var<Boolean> cond, Constraint c) {
|
|
||||||
return new IfConstraint(true, cond, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static <T> Constraint index(Var<T> target, Var<Integer> index, List<Var<T>> list) {
|
|
||||||
return new IndexConstraint<T>(target, index, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Constraint constant(Var<T> target, final T constant) {
|
|
||||||
return new FilterConstraint<T>(new Filter<T>() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(T value) {
|
|
||||||
return value.equals(constant);
|
|
||||||
}
|
|
||||||
}, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Constraint offset(int offset, Var<Integer> x, Var<Integer> y) {
|
|
||||||
return new OffsetConstraint(offset, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Constraint same(Var<T> x, Var<T> y) {
|
|
||||||
return new SameConstraint<T>(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Constraint sum(Var<Integer> x, Var<Integer> y, Var<Integer> z) {
|
|
||||||
return new SumConstraint(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Constraint sum(Var<Integer> sum, List<Var<Integer>> list) {
|
|
||||||
return new ListSumConstraint(sum, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends Comparable<T>> Constraint lessThan(Var<T> x, Var<T> y) {
|
|
||||||
return new LessThan<T>(false, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends Comparable<T>> Constraint lessThanEq(Var<T> x, Var<T> y) {
|
|
||||||
return new LessThan<T>(true, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface Propagator {
|
|
||||||
public abstract Collection<Var<?>> getWatchedVars();
|
|
||||||
|
|
||||||
public abstract void propagate();
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
public enum Satisfiability {
|
|
||||||
TAUT, SAT, UNSAT
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import static jrummikub.ai.fdsolver.Satisfiability.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Solver class is the Main Constraint Propagator (MCP) that tries to find a
|
|
||||||
* labeling of all variables that satisfies all given constraints.
|
|
||||||
*/
|
|
||||||
public class Solver {
|
|
||||||
Set<Var<?>> vars = new HashSet<Var<?>>();
|
|
||||||
|
|
||||||
Set<Var<?>> dirtyVars = new HashSet<Var<?>>();
|
|
||||||
|
|
||||||
Set<Var<?>> unsolvedVars = new HashSet<Var<?>>();
|
|
||||||
|
|
||||||
Set<Constraint> constraints = new HashSet<Constraint>();
|
|
||||||
|
|
||||||
Set<Constraint> dirtyConstraints = new HashSet<Constraint>();
|
|
||||||
|
|
||||||
ArrayList<StackFrame> stack = new ArrayList<StackFrame>();
|
|
||||||
|
|
||||||
boolean contradiction = false;
|
|
||||||
|
|
||||||
static private class StackFrame {
|
|
||||||
Map<Var<?>, HashSet<?>> invalidatedValues = new HashMap<Var<?>, HashSet<?>>();
|
|
||||||
Var<?> branchVar;
|
|
||||||
Object branchValue;
|
|
||||||
HashSet<Constraint> finishedConstraints = new HashSet<Constraint>();
|
|
||||||
|
|
||||||
public <T> void invalidate(Var<T> var, T invalid) {
|
|
||||||
HashSet<T> values = (HashSet<T>) invalidatedValues.get(var);
|
|
||||||
if (values == null) {
|
|
||||||
invalidatedValues.put(var,
|
|
||||||
new HashSet<T>(Arrays.asList(invalid)));
|
|
||||||
} else {
|
|
||||||
values.add(invalid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "StackItem [invalidatedValues=" + invalidatedValues + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Solver() {
|
|
||||||
stack.add(new StackFrame());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSolved() {
|
|
||||||
return dirtyVars.isEmpty() && unsolvedVars.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean solve() {
|
|
||||||
do {
|
|
||||||
solveStep();
|
|
||||||
} while (!(contradiction || isSolved()));
|
|
||||||
return !contradiction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void solveStep() {
|
|
||||||
if (isSolved()) {
|
|
||||||
contradiction = true;
|
|
||||||
} else {
|
|
||||||
propagateAll();
|
|
||||||
}
|
|
||||||
if (contradiction) {
|
|
||||||
if (stack.size() == 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
backtrack();
|
|
||||||
} else if (unsolvedVars.isEmpty()) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
branch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void propagateEach() {
|
|
||||||
List<Var<?>> oldDirtyVars = new ArrayList<Var<?>>(dirtyVars);
|
|
||||||
dirtyVars.clear();
|
|
||||||
Collections.sort(oldDirtyVars);
|
|
||||||
outerLoop: for(Var<?> dirtyVar : oldDirtyVars) {
|
|
||||||
for (Constraint constraint : dirtyVar.getConstraints()) {
|
|
||||||
dirtyConstraints.add(constraint);
|
|
||||||
for (Propagator propagator : constraint.cachedPropagators) {
|
|
||||||
if (propagator.getWatchedVars().contains(dirtyVar)) {
|
|
||||||
propagator.propagate();
|
|
||||||
if (contradiction) {
|
|
||||||
break outerLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void propagateAll() {
|
|
||||||
int i = 0;
|
|
||||||
while (!(dirtyVars.isEmpty() || contradiction)) {
|
|
||||||
i++;
|
|
||||||
if (i == 50) {
|
|
||||||
cleanUpConstraints();
|
|
||||||
i = 0;
|
|
||||||
} else {
|
|
||||||
propagateEach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanUpConstraints();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanUpConstraints() {
|
|
||||||
for (Constraint constraint : dirtyConstraints) {
|
|
||||||
Satisfiability sat = constraint.getSatisfiability();
|
|
||||||
if (sat == UNSAT) {
|
|
||||||
contradiction = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (sat == TAUT) {
|
|
||||||
finishedConstraint(constraint, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirtyConstraints.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addVar(Var<?> var) {
|
|
||||||
vars.add(var);
|
|
||||||
if (var.getRange().size() != 1) {
|
|
||||||
unsolvedVars.add(var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addConstraint(Constraint constraint) {
|
|
||||||
constraint.cachedPropagators = constraint.getPropagators(false);
|
|
||||||
constraints.add(constraint);
|
|
||||||
for (Var<?> var : constraint.getWatchedVars()) {
|
|
||||||
var.makeDirty();
|
|
||||||
var.getConstraints().add(constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// backtracking and logging
|
|
||||||
|
|
||||||
void branch() {
|
|
||||||
branchOn(Collections.min(unsolvedVars));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void branchOn(Var<?> var) {
|
|
||||||
|
|
||||||
Set<?> range = var.getRange();
|
|
||||||
int n = (int) (Math.random() * range.size());
|
|
||||||
Iterator<?> it = range.iterator();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
it.next();
|
|
||||||
}
|
|
||||||
Object value = it.next();
|
|
||||||
branchWith((Var<Object>) var, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
<T> void branchWith(Var<T> var, T value) {
|
|
||||||
StackFrame stackFrame = new StackFrame();
|
|
||||||
stackFrame.branchVar = var;
|
|
||||||
stackFrame.branchValue = value;
|
|
||||||
stack.add(stackFrame);
|
|
||||||
var.choose(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StackFrame getTopStackFrame() {
|
|
||||||
return stack.get(stack.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
<T> void logInvalidation(Var<T> var, T invalid) {
|
|
||||||
getTopStackFrame().invalidate(var, invalid);
|
|
||||||
if (var.getRange().size() == 1) {
|
|
||||||
unsolvedVars.remove(var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishedConstraint(Constraint constraint, Var<?> currentVar) {
|
|
||||||
for (Var<?> var : constraint.getWatchedVars()) {
|
|
||||||
if (var != currentVar) {
|
|
||||||
var.getConstraints().remove(constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
constraints.remove(constraint);
|
|
||||||
getTopStackFrame().finishedConstraints.add(constraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
// This would need rank-2 types which java lacks
|
|
||||||
private void rollback(StackFrame item) {
|
|
||||||
for (Map.Entry<Var<?>, HashSet<?>> entry : item.invalidatedValues
|
|
||||||
.entrySet()) {
|
|
||||||
Var<Object> var = (Var<Object>) entry.getKey();
|
|
||||||
HashSet<Object> values = (HashSet<Object>) entry.getValue();
|
|
||||||
var.getRange().addAll(values);
|
|
||||||
if (var.getRange().size() != 1) {
|
|
||||||
unsolvedVars.add(var);
|
|
||||||
} else {
|
|
||||||
unsolvedVars.remove(var);
|
|
||||||
}
|
|
||||||
var.makeDirty(); // TODO think a bit more about this
|
|
||||||
}
|
|
||||||
for (Constraint constraint : item.finishedConstraints) {
|
|
||||||
for (Var<?> var : constraint.getWatchedVars()) {
|
|
||||||
var.getConstraints().add(constraint);
|
|
||||||
}
|
|
||||||
constraints.add(constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void backtrack() {
|
|
||||||
contradiction = false;
|
|
||||||
StackFrame topFrame = getTopStackFrame();
|
|
||||||
rollback(topFrame);
|
|
||||||
stack.remove(stack.size() - 1);
|
|
||||||
((Var<Object>) topFrame.branchVar).invalidate(topFrame.branchValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// factory methods for vars
|
|
||||||
|
|
||||||
public Var<Integer> makeRangeVar(int low, int high) {
|
|
||||||
ArrayList<Integer> range = new ArrayList<Integer>();
|
|
||||||
for (int i = low; i <= high; i++) {
|
|
||||||
range.add(i);
|
|
||||||
}
|
|
||||||
return makeVar(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Var<Boolean> makeBoolVar() {
|
|
||||||
return makeVar(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Var<T> makeVar(Collection<T> range) {
|
|
||||||
Var<T> var = new Var<T>(this, range);
|
|
||||||
addVar(var);
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Var<T> makeVar(T... range) {
|
|
||||||
return makeVar(Arrays.asList(range));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void record() {
|
|
||||||
for (Var<?> var : vars) {
|
|
||||||
var.record();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restore() {
|
|
||||||
for (Var<?> var : vars) {
|
|
||||||
var.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
|
|
||||||
public class SolverMain {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* eingabe: liste handsteinen + liste tischsteinen
|
|
||||||
*
|
|
||||||
* foreach stein: stein id (durchlaufend nummeriert) Var<Boolean> onTable =
|
|
||||||
* tisch ? {true}, {true, false} Var<Integer> value = {N} Var<StoneColor>
|
|
||||||
* color = {C} Var<Integer> nachbarL,R = {-1, 0...steinanzahl} Var<Boolean>
|
|
||||||
* groupOrRun
|
|
||||||
*
|
|
||||||
* nachbarR != -1 => nachbarL[nachbarR[id]] == id nachbarL != -1 =>
|
|
||||||
* nachbarR[nachbarL[id]] == id
|
|
||||||
*
|
|
||||||
* nachbarR != -1 => gOR[nachbarR[id]] = gOR nachbarL != -1 =>
|
|
||||||
* gOR[nachbarL[id]] = gOR
|
|
||||||
*
|
|
||||||
* nachbar{R,L} != -1 => group <=> value[{R,L}] == value nachbar{R,L} != -1
|
|
||||||
* && group => color[{R,L}] {<,>} color // hier auch <=> ?
|
|
||||||
*
|
|
||||||
* nachbar{R,L} != -1 => run <=> color[{R,L}] == color nachbar{R,L} != -1 =>
|
|
||||||
* run <=> value[{R,L}] == value {+,-} 1
|
|
||||||
*
|
|
||||||
* Var<Integer> pos von {links, rechts} + constraints
|
|
||||||
*
|
|
||||||
* links + rechts >= 3
|
|
||||||
*
|
|
||||||
* foreach handstein: Var<Integer> badness = onTable ? ### (z. B. 1) : 0
|
|
||||||
*
|
|
||||||
* totalBadness = sum(all badness)
|
|
||||||
*
|
|
||||||
* foreach joker: Var<Integer> value = {-1, 1..13} Var<StoneColor> color =
|
|
||||||
* {F, C0..C3} onTable == false <=> value == -1 same with color
|
|
||||||
*
|
|
||||||
* joker sind sortiert
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
public class Var<T> implements Comparable<Var<T>> {
|
|
||||||
private Solver solver;
|
|
||||||
private HashSet<T> range;
|
|
||||||
private HashSet<Constraint> constraints;
|
|
||||||
private T recorded;
|
|
||||||
|
|
||||||
public Var(Solver solver, Collection<T> range) {
|
|
||||||
this.solver = solver;
|
|
||||||
this.range = new HashSet<T>(range);
|
|
||||||
constraints = new HashSet<Constraint>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getValue() {
|
|
||||||
if (range.size() != 1)
|
|
||||||
return null;
|
|
||||||
return range.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashSet<T> getRange() {
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
void choose(T value) {
|
|
||||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
|
||||||
if (i.next() != value) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void makeDirty() {
|
|
||||||
this.solver.dirtyVars.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invalidate(T value) {
|
|
||||||
range.remove(value);
|
|
||||||
solver.logInvalidation(this, value);
|
|
||||||
makeDirty();
|
|
||||||
if (range.size() == 0) {
|
|
||||||
solver.contradiction = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HashSet<Constraint> getConstraints() {
|
|
||||||
return constraints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<T> iterator() {
|
|
||||||
final Iterator<T> iterator = range.iterator();
|
|
||||||
return new Iterator<T>() {
|
|
||||||
T lastValue;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return iterator.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T next() {
|
|
||||||
lastValue = iterator.next();
|
|
||||||
return lastValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
|
||||||
// TODO logging
|
|
||||||
iterator.remove();
|
|
||||||
solver.logInvalidation(Var.this, lastValue);
|
|
||||||
makeDirty();
|
|
||||||
if (range.size() == 0) {
|
|
||||||
solver.contradiction = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void record() {
|
|
||||||
recorded = getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void restore() {
|
|
||||||
range.clear();
|
|
||||||
range.add(recorded);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Var" + range;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int neighborCount() {
|
|
||||||
/* int count = 0;
|
|
||||||
for (Constraint constraint : constraints) {
|
|
||||||
count += constraint.getWatchedVars().size();
|
|
||||||
} */
|
|
||||||
return constraints.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Var<T> other) {
|
|
||||||
int rangeCompare = ((Integer)range.size()).compareTo(other.range.size());
|
|
||||||
if (rangeCompare != 0)
|
|
||||||
return rangeCompare;
|
|
||||||
return -((Integer)neighborCount()).compareTo(other.neighborCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import static jrummikub.ai.fdsolver.Satisfiability.*;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class ComparatorConstraint<T> extends Constraint {
|
|
||||||
Var<T> x, y;
|
|
||||||
Comparator<T> comparator, reverseComparator;
|
|
||||||
ComparatorPropagator<T> trueX, trueY, falseX, falseY;
|
|
||||||
boolean allowEqual;
|
|
||||||
|
|
||||||
ComparatorConstraint(final Comparator<T> comparator, boolean allowEqual,
|
|
||||||
Var<T> x, Var<T> y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.comparator = comparator;
|
|
||||||
this.allowEqual = allowEqual;
|
|
||||||
reverseComparator = new Comparator<T>() {
|
|
||||||
@Override
|
|
||||||
public int compare(T o1, T o2) {
|
|
||||||
return comparator.compare(o2, o1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
trueX = new ComparatorPropagator<T>(comparator, allowEqual, x, y);
|
|
||||||
trueY = new ComparatorPropagator<T>(reverseComparator, allowEqual, y, x);
|
|
||||||
falseX = new ComparatorPropagator<T>(reverseComparator, !allowEqual, x,
|
|
||||||
y);
|
|
||||||
falseY = new ComparatorPropagator<T>(comparator, !allowEqual, y, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>> asList(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
if (negate) {
|
|
||||||
return Arrays.<Propagator> asList(falseX, falseY);
|
|
||||||
} else {
|
|
||||||
return Arrays.<Propagator> asList(trueX, trueY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
try {
|
|
||||||
T maxX = Collections.max(x.getRange(), comparator);
|
|
||||||
T minY = Collections.min(y.getRange(), comparator);
|
|
||||||
if (comparator.compare(maxX, minY) < (allowEqual ? 1 : 0)) {
|
|
||||||
return TAUT;
|
|
||||||
}
|
|
||||||
T minX = Collections.min(x.getRange(), comparator);
|
|
||||||
T maxY = Collections.max(y.getRange(), comparator);
|
|
||||||
if (comparator.compare(maxY, minX) < (allowEqual ? 0 : 1)) {
|
|
||||||
return UNSAT;
|
|
||||||
}
|
|
||||||
return SAT;
|
|
||||||
} catch (NoSuchElementException e) {
|
|
||||||
return UNSAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class ComparatorPropagator<T> implements Propagator {
|
|
||||||
private Var<T> x, y;
|
|
||||||
private Comparator<T> comparator;
|
|
||||||
private boolean allowEqual;
|
|
||||||
public ComparatorPropagator(Comparator<T> comparator, boolean allowEqual, Var<T> x, Var<T> y) {
|
|
||||||
this.comparator = comparator;
|
|
||||||
this.allowEqual = allowEqual;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>>asList(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
T maxY = Collections.max(y.getRange(), comparator);
|
|
||||||
|
|
||||||
for(Iterator<T> i = x.iterator(); i.hasNext();) {
|
|
||||||
T value = i.next();
|
|
||||||
int comparision = comparator.compare(value, maxY);
|
|
||||||
if (comparision > 0 || comparision == 0 && !allowEqual) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
public interface Filter<T> {
|
|
||||||
public boolean accept(T value);
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import static jrummikub.ai.fdsolver.Satisfiability.SAT;
|
|
||||||
import static jrummikub.ai.fdsolver.Satisfiability.TAUT;
|
|
||||||
import static jrummikub.ai.fdsolver.Satisfiability.UNSAT;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class FilterConstraint<T> extends Constraint {
|
|
||||||
private Var<T> var;
|
|
||||||
private Propagator trueProp, falseProp;
|
|
||||||
private Filter<T> filter;
|
|
||||||
|
|
||||||
public FilterConstraint(final Filter<T> filter, Var<T> var) {
|
|
||||||
this.var = var;
|
|
||||||
this.filter = filter;
|
|
||||||
trueProp = new FilterPropagator<T>(filter, var);
|
|
||||||
falseProp = new FilterPropagator<T>(new Filter<T>() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(T value) {
|
|
||||||
return !filter.accept(value);
|
|
||||||
}
|
|
||||||
}, var);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>> asList(var);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
return Arrays.asList(negate ? falseProp : trueProp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
boolean any = false;
|
|
||||||
boolean all = true;
|
|
||||||
|
|
||||||
for (T value : var.getRange()) {
|
|
||||||
boolean accepted = filter.accept(value);
|
|
||||||
if (accepted) {
|
|
||||||
any = true;
|
|
||||||
} else {
|
|
||||||
all = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (all && any) {
|
|
||||||
return TAUT;
|
|
||||||
} else if (any) {
|
|
||||||
return SAT;
|
|
||||||
} else {
|
|
||||||
return UNSAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class FilterPropagator<T> implements Propagator {
|
|
||||||
private Filter<T> filter;
|
|
||||||
private Var<T> var;
|
|
||||||
|
|
||||||
public FilterPropagator(Filter<T> filter, Var<T> var) {
|
|
||||||
this.filter = filter;
|
|
||||||
this.var = var;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>>asList(var);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
for(Iterator<T> i = var.iterator(); i.hasNext();) {
|
|
||||||
if(!filter.accept(i.next()))
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class GreaterThan<T extends Comparable<T>> extends
|
|
||||||
ComparatorConstraint<T> {
|
|
||||||
|
|
||||||
public GreaterThan(boolean allowEqual, Var<T> x, Var<T> y) {
|
|
||||||
super(new Comparator<T>() {
|
|
||||||
@Override
|
|
||||||
public int compare(T o1, T o2) {
|
|
||||||
return o2.compareTo(o1);
|
|
||||||
}
|
|
||||||
}, allowEqual, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class GreaterThanConst<T extends Comparable<T>> extends
|
|
||||||
FilterConstraint<T> {
|
|
||||||
public GreaterThanConst(final boolean allowEqual, Var<T> x, final T y) {
|
|
||||||
super(new Filter<T>() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(T value) {
|
|
||||||
return y.compareTo(value) < (allowEqual ? 1 : 0);
|
|
||||||
}
|
|
||||||
}, x);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.locks.Condition;
|
|
||||||
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class IfConstraint extends Constraint {
|
|
||||||
Var<Boolean> condition;
|
|
||||||
Constraint child;
|
|
||||||
Collection<Var<?>> vars;
|
|
||||||
boolean negateCond;
|
|
||||||
|
|
||||||
public IfConstraint(boolean negateCond, Var<Boolean> condition, Constraint child) {
|
|
||||||
this.condition = condition;
|
|
||||||
this.child = child;
|
|
||||||
this.negateCond = negateCond;
|
|
||||||
vars = new ArrayList<Var<?>>();
|
|
||||||
vars.addAll(child.getWatchedVars());
|
|
||||||
vars.add(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IfPropagator implements Propagator {
|
|
||||||
Propagator child;
|
|
||||||
Collection<Var<?>> vars;
|
|
||||||
public IfPropagator(Propagator child) {
|
|
||||||
this.child = child;
|
|
||||||
vars = new ArrayList<Var<?>>();
|
|
||||||
vars.addAll(child.getWatchedVars());
|
|
||||||
vars.add(condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
if(condition.getRange().contains(negateCond)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
child.propagate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FailPropagator implements Propagator {
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return child.getWatchedVars();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
if (!child.isSatisfiable()) {
|
|
||||||
condition.invalidate(!negateCond);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
List<Propagator> props = new ArrayList<Propagator>();
|
|
||||||
if (negate) {
|
|
||||||
props.add(new FilterPropagator<Boolean>(new Filter<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(Boolean value) {
|
|
||||||
return value ^ negateCond;
|
|
||||||
}
|
|
||||||
}, condition));
|
|
||||||
props.addAll(child.getPropagators(true));
|
|
||||||
} else {
|
|
||||||
for (Propagator p : child.getPropagators(false)) {
|
|
||||||
props.add(new IfPropagator(p));
|
|
||||||
}
|
|
||||||
props.add(new FailPropagator());
|
|
||||||
}
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
if (condition.getRange().contains(negateCond)) {
|
|
||||||
if (condition.getRange().size() == 1) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
if (child.getSatisfiability() == Satisfiability.TAUT) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return child.getSatisfiability();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.w3c.dom.ranges.Range;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class IndexConstraint<T> extends Constraint {
|
|
||||||
Var<T> target;
|
|
||||||
Var<Integer> index;
|
|
||||||
List<Var<T>> list;
|
|
||||||
Collection<Var<?>> vars = new ArrayList<Var<?>>();
|
|
||||||
Collection<Var<?>> varsNoTarget = new ArrayList<Var<?>>();
|
|
||||||
Collection<Var<?>> varsNoIndex = new ArrayList<Var<?>>();
|
|
||||||
|
|
||||||
public IndexConstraint(Var<T> target, Var<Integer> index, List<Var<T>> list) {
|
|
||||||
this.target = target;
|
|
||||||
this.index = index;
|
|
||||||
this.list = list;
|
|
||||||
vars.addAll(list);
|
|
||||||
vars.add(index);
|
|
||||||
vars.add(target);
|
|
||||||
varsNoTarget.addAll(list);
|
|
||||||
varsNoTarget.add(index);
|
|
||||||
varsNoIndex.addAll(list);
|
|
||||||
varsNoIndex.add(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UnionProp implements Propagator {
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return varsNoTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
HashSet<T> invUnion = new HashSet<T>(target.getRange());
|
|
||||||
for (int i : index.getRange()) {
|
|
||||||
invUnion.removeAll(list.get(i).getRange());
|
|
||||||
if (invUnion.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (T val : invUnion) {
|
|
||||||
target.invalidate(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IndexProp implements Propagator {
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return varsNoIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
for (Iterator<Integer> i = index.iterator(); i.hasNext();) {
|
|
||||||
int id = i.next();
|
|
||||||
Var<T> item = list.get(id);
|
|
||||||
if (Collections.disjoint(item.getRange(), target.getRange())) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VarProp implements Propagator {
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.asList(target, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
if (index.getRange().size() != 1)
|
|
||||||
return;
|
|
||||||
int id = index.getValue();
|
|
||||||
Var<T> var = list.get(id);
|
|
||||||
for(Iterator<T> i = var.iterator(); i.hasNext();) {
|
|
||||||
if (!target.getRange().contains(i.next())) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
if (negate) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return Arrays.<Propagator> asList(new UnionProp(), new IndexProp(), new VarProp());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSatisfiable() {
|
|
||||||
for (int i : index.getRange()) {
|
|
||||||
if(!Collections.disjoint(list.get(i).getRange(), target.getRange())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
boolean sat = isSatisfiable();
|
|
||||||
if (!sat) {
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
}
|
|
||||||
if (target.getRange().size() > 1)
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
for (int i : index.getRange()) {
|
|
||||||
Var<T> var = list.get(i);
|
|
||||||
if (var.getRange().size() > 1)
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
if(var.getValue() != target.getValue()) {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class LessThan<T extends Comparable<T>> extends ComparatorConstraint<T> {
|
|
||||||
|
|
||||||
public LessThan(boolean allowEqual, Var<T> x, Var<T> y) {
|
|
||||||
super(new Comparator<T>() {
|
|
||||||
@Override
|
|
||||||
public int compare(T o1, T o2) {
|
|
||||||
return o1.compareTo(o2);
|
|
||||||
}
|
|
||||||
}, allowEqual, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Solver;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class LessThanConst<T extends Comparable<T>> extends FilterConstraint<T> {
|
|
||||||
public LessThanConst(final boolean allowEqual, Var<T> x, final T y) {
|
|
||||||
super(new Filter<T>() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(T value) {
|
|
||||||
return value.compareTo(y) < (allowEqual ? 1 : 0);
|
|
||||||
}
|
|
||||||
}, x);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class ListSumConstraint extends Constraint {
|
|
||||||
Var<Integer> sum;
|
|
||||||
List<Var<Integer>> list;
|
|
||||||
List<Var<?>> vars = new ArrayList<Var<?>>();
|
|
||||||
|
|
||||||
public ListSumConstraint(Var<Integer> sum, List<Var<Integer>> list) {
|
|
||||||
this.sum = sum;
|
|
||||||
this.list = list;
|
|
||||||
vars.add(sum);
|
|
||||||
vars.addAll(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
List<Integer> values = new ArrayList<Integer>();
|
|
||||||
int valueSum = 0;
|
|
||||||
boolean isTaut = sum.getRange().size()==1;
|
|
||||||
for (Var<Integer> var : list) {
|
|
||||||
Iterator<Integer> it = var.getRange().iterator();
|
|
||||||
int value = it.next();
|
|
||||||
if (it.hasNext())
|
|
||||||
isTaut = false;
|
|
||||||
values.add(value);
|
|
||||||
valueSum += value;
|
|
||||||
}
|
|
||||||
if (isTaut) {
|
|
||||||
if (valueSum == sum.getValue()) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Integer> reachableValues = new HashSet<Integer>();
|
|
||||||
Set<Integer> newValues = new HashSet<Integer>();
|
|
||||||
reachableValues.add(valueSum);
|
|
||||||
|
|
||||||
if (sum.getRange().contains(valueSum)) {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < values.size(); i++) {
|
|
||||||
Var<Integer> var = list.get(i);
|
|
||||||
int offset = values.get(i);
|
|
||||||
for (int x : var.getRange()) {
|
|
||||||
if (x == offset)
|
|
||||||
continue;
|
|
||||||
newValues.clear();
|
|
||||||
for (int y : reachableValues) {
|
|
||||||
int newValue = x + y - offset;
|
|
||||||
if (!reachableValues.contains(newValue)) {
|
|
||||||
newValues.add(x + y - offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!Collections.disjoint(sum.getRange(), newValues)) {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
reachableValues.addAll(newValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class OffsetConstraint extends Constraint {
|
|
||||||
private Var<Integer> x, y;
|
|
||||||
int offset;
|
|
||||||
Propagator propX, propY;
|
|
||||||
|
|
||||||
public OffsetConstraint(int offset, Var<Integer> x, Var<Integer> y) {
|
|
||||||
this.offset = offset;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
propX = new OffsetProp(offset, x, y);
|
|
||||||
propY = new OffsetProp(-offset, y, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>> asList(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OffsetProp implements Propagator {
|
|
||||||
private Var<Integer> x, y;
|
|
||||||
private int offset;
|
|
||||||
public OffsetProp(int offset, Var<Integer> x, Var<Integer> y) {
|
|
||||||
this.offset = offset;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>>asList(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
for(Iterator<Integer> i = x.iterator(); i.hasNext();) {
|
|
||||||
if(!y.getRange().contains(i.next() + offset)) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
return Arrays.asList(propX, propY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
boolean disjoint = true;
|
|
||||||
if (x.getRange().size() < y.getRange().size()) {
|
|
||||||
for (int xv : x.getRange()) {
|
|
||||||
if (y.getRange().contains(xv + offset)) {
|
|
||||||
disjoint = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int yv : y.getRange()) {
|
|
||||||
if (x.getRange().contains(yv - offset)) {
|
|
||||||
disjoint = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disjoint) {
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
} else if (x.getRange().size() == 1 && y.getRange().size() == 1) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class SameConstraint<T> extends Constraint {
|
|
||||||
private Var<T> x, y;
|
|
||||||
Propagator propX, propY;
|
|
||||||
|
|
||||||
public SameConstraint(Var<T> x, Var<T> y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
propX = new SameProp<T>(x, y);
|
|
||||||
propY = new SameProp<T>(y, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>> asList(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SameProp<T> implements Propagator {
|
|
||||||
private Var<T> x, y;
|
|
||||||
public SameProp(Var<T> x, Var<T> y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>>asList(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void propagate() {
|
|
||||||
for(Iterator<T> i = x.iterator(); i.hasNext();) {
|
|
||||||
if(!y.getRange().contains(i.next())) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
return Arrays.asList(propX, propY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
if (Collections.disjoint(x.getRange(), y.getRange())) {
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
} else if (x.getRange().size() == 1 && y.getRange().size() == 1) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver.constraint;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import jrummikub.ai.fdsolver.Constraint;
|
|
||||||
import jrummikub.ai.fdsolver.Propagator;
|
|
||||||
import jrummikub.ai.fdsolver.Satisfiability;
|
|
||||||
import jrummikub.ai.fdsolver.Var;
|
|
||||||
|
|
||||||
public class SumConstraint extends Constraint {
|
|
||||||
Var<Integer> x, y, z;
|
|
||||||
|
|
||||||
public SumConstraint(Var<Integer> x, Var<Integer> y, Var<Integer> z) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Var<?>> getWatchedVars() {
|
|
||||||
return Arrays.<Var<?>> asList(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Propagator> getPropagators(boolean negate) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Satisfiability getSatisfiability() {
|
|
||||||
// HashSet<Integer> intersection = new HashSet<Integer>();
|
|
||||||
for (int xv : x.getRange()) {
|
|
||||||
for (int yv : y.getRange()) {
|
|
||||||
if (z.getRange().contains(xv + yv)) {
|
|
||||||
if (z.getRange().size() == 1 && x.getRange().size() == 1
|
|
||||||
&& y.getRange().size() == 1) {
|
|
||||||
return Satisfiability.TAUT;
|
|
||||||
} else {
|
|
||||||
return Satisfiability.SAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Satisfiability.UNSAT;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -93,6 +93,7 @@ public class Hand extends StoneTray<Stone> implements IHand {
|
||||||
|
|
||||||
TurnLogic turnLogic = new TurnLogic(settings,
|
TurnLogic turnLogic = new TurnLogic(settings,
|
||||||
Collections.<Stone> emptyList(), handStones);
|
Collections.<Stone> emptyList(), handStones);
|
||||||
|
turnLogic.needIntialMeldThreshold();
|
||||||
return turnLogic.solve();
|
return turnLogic.solve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package jrummikub.ai.fdsolver;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.LessThan;
|
|
||||||
import jrummikub.ai.fdsolver.constraint.LessThanConst;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class SolverTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
Solver solver = new Solver();
|
|
||||||
|
|
||||||
Var<Integer> x = solver.makeVar(1, 2, 3);
|
|
||||||
Var<Integer> y = solver.makeRangeVar(1, 13);
|
|
||||||
|
|
||||||
solver.addConstraint(new LessThan<Integer>(false, y, x));
|
|
||||||
|
|
||||||
while (solver.solve()) {
|
|
||||||
solver.record();
|
|
||||||
solver.addConstraint(new LessThanConst<Integer>(false, x, x.getValue()));
|
|
||||||
}
|
|
||||||
solver.restore();
|
|
||||||
assertEquals(2, (int)x.getValue());
|
|
||||||
assertEquals(1, (int)y.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -147,13 +147,14 @@ public class HandTest {
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testInvalid() {
|
public void testInvalid() {
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
|
||||||
new Stone(10, RED), new Stone(12, RED), new Stone(13, RED)));
|
RED), new Stone(10, RED), new Stone(12, RED),
|
||||||
|
new Stone(13, RED)));
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(10,
|
testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(10,
|
||||||
BLACK), new Stone(11, RED), new Stone(11, BLACK)));
|
BLACK), new Stone(11, RED), new Stone(11, BLACK)));
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(10, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(10,
|
||||||
new Stone(10, RED), new Stone(10, BLACK), new Stone(11, RED),
|
RED), new Stone(10, BLACK), new Stone(11, RED), new Stone(11,
|
||||||
new Stone(11, BLACK)));
|
BLACK)));
|
||||||
|
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(11,
|
testInitialMeld(false, Arrays.asList(new Stone(10, RED), new Stone(11,
|
||||||
BLACK), new Stone(12, RED)));
|
BLACK), new Stone(12, RED)));
|
||||||
|
@ -162,66 +163,66 @@ public class HandTest {
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testNotEnoughPoints() {
|
public void testNotEnoughPoints() {
|
||||||
testInitialMeld(false,
|
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
|
||||||
Arrays.asList(new Stone(8, RED), new Stone(9, RED), new Stone(10, RED)));
|
RED), new Stone(10, RED)));
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
|
||||||
new Stone(3, RED), new Stone(4, RED)));
|
RED), new Stone(3, RED), new Stone(4, RED)));
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
|
||||||
new Stone(3, RED), new Stone(1, BLACK), new Stone(2, BLACK), new Stone(
|
RED), new Stone(3, RED), new Stone(1, BLACK), new Stone(2,
|
||||||
3, BLACK)));
|
BLACK), new Stone(3, BLACK)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testNotEnoughPointsWithJoker() {
|
public void testNotEnoughPointsWithJoker() {
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(9,
|
||||||
new Stone(RED), new Stone(3, BLACK)));
|
RED), new Stone(RED), new Stone(3, BLACK)));
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(4, RED), new Stone(5, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(4, RED), new Stone(5,
|
||||||
new Stone(4, BLACK), new Stone(5, BLACK), new Stone(RED)));
|
RED), new Stone(4, BLACK), new Stone(5, BLACK), new Stone(RED)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testNotEnoughPointsWithTwoJokers() {
|
public void testNotEnoughPointsWithTwoJokers() {
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2, BLUE),
|
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
|
||||||
new Stone(3, BLACK), new Stone(RED), new Stone(BLACK)));
|
BLUE), new Stone(3, BLACK), new Stone(RED), new Stone(BLACK)));
|
||||||
testInitialMeld(false,
|
testInitialMeld(false, Arrays.asList(new Stone(8, RED), new Stone(RED),
|
||||||
Arrays.asList(new Stone(8, RED), new Stone(RED), new Stone(BLACK)));
|
new Stone(BLACK)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testValid() {
|
public void testValid() {
|
||||||
testInitialMeld(true, Arrays.asList(new Stone(11, RED), new Stone(12, RED),
|
testInitialMeld(true, Arrays.asList(new Stone(11, RED), new Stone(12,
|
||||||
new Stone(13, RED)));
|
RED), new Stone(13, RED)));
|
||||||
testInitialMeld(true, Arrays.asList(new Stone(4, RED), new Stone(5, RED),
|
testInitialMeld(true, Arrays.asList(new Stone(4, RED),
|
||||||
new Stone(6, RED), new Stone(5, ORANGE), new Stone(5, BLACK),
|
new Stone(5, RED), new Stone(6, RED), new Stone(5, ORANGE),
|
||||||
new Stone(5, BLUE)));
|
new Stone(5, BLACK), new Stone(5, BLUE)));
|
||||||
testInitialMeld(true, Arrays.asList(new Stone(10, RED),
|
testInitialMeld(true, Arrays.asList(new Stone(10, RED), new Stone(10,
|
||||||
new Stone(10, BLACK), new Stone(10, ORANGE)));
|
BLACK), new Stone(10, ORANGE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testValidWithJoker() {
|
public void testValidWithJoker() {
|
||||||
testInitialMeld(true,
|
testInitialMeld(true, Arrays.asList(new Stone(11, RED), new Stone(RED),
|
||||||
Arrays.asList(new Stone(11, RED), new Stone(RED), new Stone(13, RED)));
|
new Stone(13, RED)));
|
||||||
testInitialMeld(true, Arrays.asList(new Stone(10, RED), new Stone(BLACK),
|
testInitialMeld(true, Arrays.asList(new Stone(10, RED),
|
||||||
new Stone(10, ORANGE)));
|
new Stone(BLACK), new Stone(10, ORANGE)));
|
||||||
testInitialMeld(true, Arrays.asList(new Stone(4, RED), new Stone(5, RED),
|
testInitialMeld(true, Arrays.asList(new Stone(4, RED),
|
||||||
new Stone(6, RED), new Stone(5, BLACK), new Stone(5, BLUE), new Stone(
|
new Stone(5, RED), new Stone(6, RED), new Stone(5, BLACK),
|
||||||
RED)));
|
new Stone(5, BLUE), new Stone(RED)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test
|
@Test
|
||||||
public void testValidWithTwoJokers() {
|
public void testValidWithTwoJokers() {
|
||||||
testInitialMeld(true,
|
testInitialMeld(true, Arrays.asList(new Stone(9, RED), new Stone(RED),
|
||||||
Arrays.asList(new Stone(9, RED), new Stone(RED), new Stone(BLACK)));
|
new Stone(BLACK)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test(timeout = 1000)
|
@Test
|
||||||
public void testValidHuge() {
|
public void testValidHuge() {
|
||||||
List<Stone> stones = new ArrayList<Stone>();
|
List<Stone> stones = new ArrayList<Stone>();
|
||||||
for (int i = 1; i <= 10; i++) {
|
for (int i = 1; i <= 10; i++) {
|
||||||
|
@ -232,15 +233,15 @@ public class HandTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
@Test(timeout = 1000)
|
@Test
|
||||||
public void testInvalidHuge() {
|
public void testInvalidHuge() {
|
||||||
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2, RED),
|
testInitialMeld(false, Arrays.asList(new Stone(1, RED), new Stone(2,
|
||||||
new Stone(4, RED), new Stone(6, RED), new Stone(8, RED), new Stone(10,
|
RED), new Stone(4, RED), new Stone(6, RED), new Stone(8, RED),
|
||||||
RED), new Stone(13, RED), new Stone(1, BLACK), new Stone(2, BLACK),
|
new Stone(10, RED), new Stone(13, RED), new Stone(1, BLACK),
|
||||||
new Stone(4, BLACK), new Stone(5, BLACK), new Stone(8, BLACK),
|
new Stone(2, BLACK), new Stone(4, BLACK), new Stone(5, BLACK),
|
||||||
new Stone(9, BLACK), new Stone(12, BLACK), new Stone(3, BLUE),
|
new Stone(8, BLACK), new Stone(9, BLACK), new Stone(12, BLACK),
|
||||||
new Stone(5, BLUE), new Stone(7, BLUE), new Stone(11, BLUE), new Stone(
|
new Stone(3, BLUE), new Stone(5, BLUE), new Stone(7, BLUE),
|
||||||
RED)));
|
new Stone(11, BLUE), new Stone(RED)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
|
|
Reference in a new issue