2011-04-30 13:44:17 +02:00
|
|
|
package jrummikub.model;
|
|
|
|
|
2011-06-08 16:29:13 +02:00
|
|
|
import java.io.Serializable;
|
2011-04-30 18:21:22 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
2011-05-01 01:05:56 +02:00
|
|
|
import java.util.HashSet;
|
2011-04-30 18:21:22 +02:00
|
|
|
import java.util.Iterator;
|
2011-04-30 17:20:14 +02:00
|
|
|
import java.util.List;
|
2011-05-01 01:05:56 +02:00
|
|
|
import java.util.Set;
|
2011-04-30 17:20:14 +02:00
|
|
|
|
2011-04-30 15:45:11 +02:00
|
|
|
import jrummikub.util.Pair;
|
2011-05-03 19:06:12 +02:00
|
|
|
import static jrummikub.model.StoneSet.Type.*;
|
2011-04-30 15:45:11 +02:00
|
|
|
|
2011-04-30 14:47:42 +02:00
|
|
|
/** Class managing {@link Stone}s joined together to form sets */
|
2011-06-08 16:29:13 +02:00
|
|
|
public class StoneSet implements Iterable<Stone>, Sizeable, Serializable {
|
|
|
|
private static final long serialVersionUID = -3852631195648599398L;
|
2011-06-08 21:58:16 +02:00
|
|
|
|
2011-05-02 23:33:45 +02:00
|
|
|
static final float VERTICAL_BORDER = 0.5f;
|
|
|
|
static final float HORIZONTAL_BORDER = 0.125f;
|
2011-06-08 16:29:13 +02:00
|
|
|
private ArrayList<Stone> stones;
|
2011-04-30 13:44:17 +02:00
|
|
|
|
2011-05-10 03:54:48 +02:00
|
|
|
/**
|
|
|
|
* Create a new single stone stone set
|
|
|
|
*
|
|
|
|
* @param stone
|
2011-06-11 04:09:27 +02:00
|
|
|
* single stone of the set
|
2011-05-10 03:54:48 +02:00
|
|
|
*/
|
2011-04-30 17:20:14 +02:00
|
|
|
public StoneSet(Stone stone) {
|
2011-06-08 16:29:13 +02:00
|
|
|
stones = new ArrayList<Stone>(Collections.singletonList(stone));
|
2011-04-30 17:20:14 +02:00
|
|
|
}
|
|
|
|
|
2011-05-10 03:54:48 +02:00
|
|
|
/**
|
|
|
|
* Create a stone set from a list of stones
|
|
|
|
*
|
|
|
|
* @param stones
|
2011-06-11 04:09:27 +02:00
|
|
|
* list of stones to build a set of
|
2011-05-10 03:54:48 +02:00
|
|
|
*/
|
2011-04-30 17:20:14 +02:00
|
|
|
public StoneSet(List<Stone> stones) {
|
2011-05-01 01:05:56 +02:00
|
|
|
this.stones = new ArrayList<Stone>(stones);
|
2011-04-30 17:20:14 +02:00
|
|
|
}
|
|
|
|
|
2011-05-10 03:54:48 +02:00
|
|
|
/** Validity type of the set */
|
2011-05-03 19:06:12 +02:00
|
|
|
public enum Type {
|
2011-05-10 03:54:48 +02:00
|
|
|
/** Set is a valid group */
|
|
|
|
GROUP,
|
|
|
|
/** Set is a valid run */
|
|
|
|
RUN,
|
|
|
|
/** Set is invalid */
|
|
|
|
INVALID
|
2011-05-03 19:06:12 +02:00
|
|
|
}
|
|
|
|
|
2011-05-02 04:46:00 +02:00
|
|
|
/**
|
|
|
|
* Test for rule conflict within the StoneSet
|
|
|
|
*
|
2011-05-31 15:29:37 +02:00
|
|
|
* @param settings
|
2011-06-11 04:09:27 +02:00
|
|
|
* GameSettings
|
2011-05-31 15:29:37 +02:00
|
|
|
*
|
2011-05-02 04:46:00 +02:00
|
|
|
* @return true when the set is valid according to the rules
|
|
|
|
*/
|
2011-05-31 00:58:46 +02:00
|
|
|
public boolean isValid(GameSettings settings) {
|
|
|
|
return classify(settings).getFirst() != INVALID;
|
2011-05-03 19:06:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-06-11 04:09:27 +02:00
|
|
|
* Test for rule conflict within the StoneSet and determine whether the set
|
|
|
|
* is a group or a run
|
2011-05-03 19:06:12 +02:00
|
|
|
*
|
2011-05-31 15:29:37 +02:00
|
|
|
* @param settings
|
2011-06-11 04:09:27 +02:00
|
|
|
* GameSettings
|
2011-05-31 15:29:37 +02:00
|
|
|
*
|
2011-05-03 19:06:12 +02:00
|
|
|
* @return GROUP or RUN for valid sets, INVALID otherwise
|
|
|
|
*/
|
2011-05-18 17:07:50 +02:00
|
|
|
|
2011-05-31 00:58:46 +02:00
|
|
|
public Pair<Type, Integer> classify(GameSettings settings) {
|
2011-05-01 01:05:56 +02:00
|
|
|
if (stones.size() < 3) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return new Pair<Type, Integer>(INVALID, 0);
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
2011-05-29 18:51:06 +02:00
|
|
|
int nonJoker = -1;
|
2011-05-01 01:05:56 +02:00
|
|
|
for (int i = 0; i < stones.size(); i++) {
|
|
|
|
if (stones.get(i).isJoker()) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-05-29 18:51:06 +02:00
|
|
|
nonJoker = i;
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
2011-05-31 00:58:48 +02:00
|
|
|
|
2011-05-29 18:51:06 +02:00
|
|
|
if (nonJoker == -1) {
|
2011-06-13 16:29:09 +02:00
|
|
|
return classifyJokersOnly(settings, nonJoker);
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
2011-05-31 00:58:48 +02:00
|
|
|
|
|
|
|
int runScore = isValidRun(nonJoker, settings);
|
|
|
|
int groupScore = isValidGroup(stones.get(nonJoker).getValue(), settings);
|
|
|
|
|
2011-05-29 18:51:06 +02:00
|
|
|
if (runScore > groupScore) {
|
|
|
|
return new Pair<Type, Integer>(RUN, runScore);
|
|
|
|
} else if (groupScore == 0) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return new Pair<Type, Integer>(INVALID, 0);
|
2011-05-29 18:51:06 +02:00
|
|
|
} else {
|
|
|
|
return new Pair<Type, Integer>(GROUP, groupScore);
|
2011-05-02 02:00:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-13 16:29:09 +02:00
|
|
|
private Pair<Type, Integer> classifyJokersOnly(GameSettings settings,
|
|
|
|
int nonJoker) {
|
|
|
|
if (stones.size() > settings.getHighestValue()
|
|
|
|
&& stones.size() > settings.getStoneColors().size()
|
|
|
|
&& !settings.isNoLimits()) {
|
|
|
|
return new Pair<Type, Integer>(INVALID, 0);
|
|
|
|
} else if (stones.size() > settings.getStoneColors().size()) {
|
|
|
|
int value = 0;
|
|
|
|
int stoneValue = settings.getHighestValue();
|
|
|
|
for (int i = 0; i < stones.size(); i++) {
|
|
|
|
value += stoneValue;
|
|
|
|
stoneValue--;
|
|
|
|
if (stoneValue == 0) {
|
|
|
|
stoneValue = settings.getHighestValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new Pair<Type, Integer>(RUN, value);
|
|
|
|
} else {
|
|
|
|
return new Pair<Type, Integer>(GROUP, stones.size()
|
|
|
|
* settings.getHighestValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-02 02:00:50 +02:00
|
|
|
/**
|
|
|
|
* Test for rule conflict within the StoneSet, assuming we have a run
|
|
|
|
*
|
|
|
|
* @param referencePosition
|
2011-06-11 04:09:27 +02:00
|
|
|
* position of stone used as reference (any non-joker stone)
|
2011-05-31 00:58:48 +02:00
|
|
|
* @param settings
|
2011-05-02 02:00:50 +02:00
|
|
|
*/
|
2011-05-31 00:58:48 +02:00
|
|
|
private int isValidRun(int referencePosition, GameSettings settings) {
|
2011-05-02 02:00:50 +02:00
|
|
|
StoneColor runColor = stones.get(referencePosition).getColor();
|
|
|
|
int startValue = stones.get(referencePosition).getValue()
|
|
|
|
- referencePosition;
|
|
|
|
int endValue = startValue + stones.size() - 1;
|
2011-06-11 04:09:29 +02:00
|
|
|
if (!settings.isNoLimits()) {
|
|
|
|
if (startValue < 1 || endValue > settings.getHighestValue()) {
|
|
|
|
return 0;
|
|
|
|
}
|
2011-05-02 02:00:50 +02:00
|
|
|
}
|
2011-06-11 04:09:29 +02:00
|
|
|
int value = 0;
|
2011-05-02 02:00:50 +02:00
|
|
|
for (int i = 0; i < stones.size(); i++) {
|
2011-06-11 04:09:29 +02:00
|
|
|
int expectedValue = i + startValue;
|
|
|
|
while (expectedValue < 1) {
|
|
|
|
expectedValue += settings.getHighestValue();
|
|
|
|
}
|
|
|
|
while (expectedValue > settings.getHighestValue()) {
|
|
|
|
expectedValue -= settings.getHighestValue();
|
|
|
|
}
|
|
|
|
value += expectedValue;
|
|
|
|
|
2011-05-02 02:00:50 +02:00
|
|
|
if (stones.get(i).isJoker()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (stones.get(i).getColor() != runColor) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return 0;
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
2011-06-11 04:09:29 +02:00
|
|
|
|
|
|
|
if (stones.get(i).getValue() != expectedValue) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return 0;
|
2011-05-02 02:00:50 +02:00
|
|
|
}
|
|
|
|
}
|
2011-05-16 20:54:37 +02:00
|
|
|
return value;
|
2011-05-02 02:00:50 +02:00
|
|
|
}
|
2011-05-02 04:46:00 +02:00
|
|
|
|
2011-05-02 02:00:50 +02:00
|
|
|
/**
|
|
|
|
* Test for rule conflict within the StoneSet, assuming we have a group
|
2011-05-31 00:58:48 +02:00
|
|
|
*
|
|
|
|
* @param settings
|
2011-05-02 02:00:50 +02:00
|
|
|
*/
|
2011-05-31 00:58:48 +02:00
|
|
|
private int isValidGroup(int value, GameSettings settings) {
|
|
|
|
if (stones.size() > settings.getStoneColors().size()) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return 0;
|
2011-05-02 02:00:50 +02:00
|
|
|
}
|
|
|
|
Set<StoneColor> seenColors = new HashSet<StoneColor>();
|
|
|
|
for (Stone i : stones) {
|
|
|
|
if (i.isJoker()) {
|
|
|
|
continue;
|
|
|
|
}
|
2011-05-09 20:27:48 +02:00
|
|
|
if (i.getValue() != value) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return 0;
|
2011-05-09 20:27:48 +02:00
|
|
|
}
|
2011-05-02 02:00:50 +02:00
|
|
|
if (seenColors.contains(i.getColor())) {
|
2011-05-16 20:54:37 +02:00
|
|
|
return 0;
|
2011-05-02 02:00:50 +02:00
|
|
|
} else {
|
|
|
|
seenColors.add(i.getColor());
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
|
|
|
}
|
2011-05-16 20:54:37 +02:00
|
|
|
return value * stones.size();
|
2011-04-30 13:44:17 +02:00
|
|
|
}
|
|
|
|
|
2011-04-30 14:47:42 +02:00
|
|
|
/**
|
|
|
|
* Splits the StoneSet at a specified {@link Position} and returns two new
|
|
|
|
* Stone Sets
|
|
|
|
*
|
|
|
|
* @param position
|
2011-06-11 04:09:27 +02:00
|
|
|
* Splitting {@link Position}
|
2011-05-02 04:46:00 +02:00
|
|
|
* @return A pair of StoneSets, one for each split part
|
2011-04-30 14:47:42 +02:00
|
|
|
*/
|
2011-04-30 15:45:11 +02:00
|
|
|
public Pair<StoneSet, StoneSet> splitAt(int position) {
|
2011-05-03 19:06:14 +02:00
|
|
|
if (position == 0) {
|
|
|
|
return new Pair<StoneSet, StoneSet>(null, this);
|
|
|
|
} else if (position == stones.size()) {
|
|
|
|
return new Pair<StoneSet, StoneSet>(this, null);
|
2011-04-30 17:20:14 +02:00
|
|
|
}
|
2011-05-01 01:23:15 +02:00
|
|
|
StoneSet firstSet = new StoneSet(stones.subList(0, position));
|
2011-06-11 04:09:27 +02:00
|
|
|
StoneSet secondSet = new StoneSet(stones.subList(position,
|
|
|
|
stones.size()));
|
2011-05-01 01:23:15 +02:00
|
|
|
return new Pair<StoneSet, StoneSet>(firstSet, secondSet);
|
2011-04-30 13:44:17 +02:00
|
|
|
}
|
|
|
|
|
2011-04-30 14:47:42 +02:00
|
|
|
/**
|
|
|
|
* Joins StoneSet to another StoneSet and returns the resulting new StoneSet
|
|
|
|
*
|
|
|
|
* @param other
|
2011-06-11 04:09:27 +02:00
|
|
|
* StoneSet to be joined to active StoneSet
|
2011-05-02 04:46:00 +02:00
|
|
|
* @return the combined StoneSet
|
2011-04-30 14:47:42 +02:00
|
|
|
*/
|
2011-04-30 13:44:17 +02:00
|
|
|
public StoneSet join(StoneSet other) {
|
2011-05-01 01:23:15 +02:00
|
|
|
List<Stone> joinedList = new ArrayList<Stone>();
|
|
|
|
joinedList.addAll(stones);
|
|
|
|
joinedList.addAll(other.stones);
|
|
|
|
return new StoneSet(joinedList);
|
2011-04-30 17:20:14 +02:00
|
|
|
}
|
|
|
|
|
2011-05-02 04:46:00 +02:00
|
|
|
/**
|
|
|
|
* Returns the number of stones in the set.
|
|
|
|
*
|
|
|
|
* @return number of stones
|
|
|
|
*/
|
2011-05-31 22:06:23 +02:00
|
|
|
public int getSize() {
|
2011-04-30 17:20:14 +02:00
|
|
|
return stones.size();
|
|
|
|
}
|
2011-04-30 13:44:17 +02:00
|
|
|
|
2011-05-02 04:46:00 +02:00
|
|
|
/**
|
|
|
|
* Returns the i-th stone of the set (starting with 0)
|
|
|
|
*
|
|
|
|
* @param i
|
2011-06-11 04:09:27 +02:00
|
|
|
* number of the stone to return
|
2011-05-02 04:46:00 +02:00
|
|
|
* @return the i-th stone
|
|
|
|
*/
|
2011-04-30 17:20:14 +02:00
|
|
|
public Stone get(int i) {
|
|
|
|
return stones.get(i);
|
2011-04-30 13:44:17 +02:00
|
|
|
}
|
|
|
|
|
2011-05-01 01:05:56 +02:00
|
|
|
@Override
|
|
|
|
public Iterator<Stone> iterator() {
|
|
|
|
final Iterator<Stone> it = stones.iterator();
|
|
|
|
|
|
|
|
return new Iterator<Stone>() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean hasNext() {
|
|
|
|
return it.hasNext();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Stone next() {
|
|
|
|
return it.next();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remove() {
|
|
|
|
// removing stones is impossible
|
|
|
|
|
2011-05-10 16:55:25 +02:00
|
|
|
throw new UnsupportedOperationException();
|
2011-05-01 01:05:56 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2011-04-30 18:21:22 +02:00
|
|
|
|
2011-05-01 01:32:19 +02:00
|
|
|
@Override
|
|
|
|
public float getWidth() {
|
2011-05-02 23:33:45 +02:00
|
|
|
return stones.size() + 2 * VERTICAL_BORDER;
|
2011-05-01 01:32:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public float getHeight() {
|
2011-05-02 23:33:45 +02:00
|
|
|
return 1 + 2 * HORIZONTAL_BORDER;
|
2011-05-01 01:32:19 +02:00
|
|
|
}
|
|
|
|
|
2011-05-03 21:34:58 +02:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
String ret = "StoneSet[";
|
|
|
|
boolean first = true;
|
|
|
|
|
|
|
|
for (Stone stone : stones) {
|
|
|
|
if (!first)
|
|
|
|
ret += ",";
|
|
|
|
|
|
|
|
ret += stone.toString();
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret + "]";
|
|
|
|
}
|
2011-04-30 13:44:17 +02:00
|
|
|
}
|