implement dedicated classes for each combination type

main
jbb01 2 years ago
parent ae7890375e
commit 0d1f51d868

@ -37,7 +37,7 @@ public enum Card {
private static final Map<Integer, Set<Card>> CARDS_BY_VALUE = Arrays.stream(values())
.filter(Card::isNormal)
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Card::getValue, Collectors.toUnmodifiableSet()),
Collectors.groupingBy(Card::value, Collectors.toUnmodifiableSet()),
Collections::unmodifiableMap
));
@ -76,7 +76,7 @@ public enum Card {
* </ul>
* @return the value of this card
*/
public @Range(from = 1, to = 15) Integer getValue() {
public @Range(from = 1, to = 15) Integer value() {
return value;
}
@ -85,7 +85,7 @@ public enum Card {
* {@linkplain #isSpecial() special cards}.
* @return the suit of this card
*/
public Suit getSuit() {
public Suit suit() {
return suit;
}
@ -107,7 +107,7 @@ public enum Card {
/**
* Returns an unmodifiable set of all {@linkplain #isNormal() normal cards} with the specified
* {@linkplain #getValue() value}.
* {@linkplain #value() value}.
* @param value the card value between {@code 2} and {@code 14} (inclusive)
* @return a set of cards
*/

@ -1,190 +0,0 @@
package eu.jonahbauer.tichu.common.model;
import eu.jonahbauer.tichu.common.model.exceptions.InvalidCombinationException;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.util.List;
import java.util.Objects;
/**
* A combination of cards.
* @param type the type of the combination
* @param cards the cards composing this combination
*/
@SuppressWarnings("unused")
public record Combination(
@NotNull CombinationType type,
@NotNull @Unmodifiable List<@NotNull Card> cards
) {
/**
* Creates a new combination.
* @param type the type of the new combination
* @param cards the cards composing the new combination
* @throws InvalidCombinationException if the cards do not compose a valid combination of the given type
* @throws NullPointerException if {@code type}, {@code cards} or any element of {@code cards} is {@code null}
*/
public Combination(@NotNull CombinationType type, @NotNull List<@NotNull Card> cards) {
this.type = Objects.requireNonNull(type, "type must not be null");
this.cards = List.copyOf(Objects.requireNonNull(cards, "cards must not be null")); // List#copyOf performs null-checks
if (!type.check(this.cards)) {
throw new InvalidCombinationException(this.type, this.cards);
}
}
/**
* Returns the card at the specified position. This is equivalent to {@code cards().get(index)}
* @param index index of the card to return
* @return the card at the specified position
* @see #cards()
*/
public @NotNull Card card(int index) {
return cards.get(index);
}
@Override
public @NotNull String toString() {
return type + cards.toString();
}
//<editor-fold desc="Named Constructors" defaultstate="collapsed">
/**
* Creates a new {@link CombinationType#SINGLE}-Combination.
* @param card the card
* @return a new combination
* @see CombinationType#SINGLE
*/
@Contract("_ -> new")
public static @NotNull Combination single(@NotNull Card card) {
return new Combination(CombinationType.SINGLE, List.of(card));
}
/**
* Creates a new {@link CombinationType#PAIR}-Combination.
* @param card0 the first card
* @param card1 the second card
* @return a new combination
* @see CombinationType#PAIR
*/
@Contract("_, _ -> new")
public static @NotNull Combination pair(@NotNull Card card0, @NotNull Card card1) {
return new Combination(CombinationType.PAIR, List.of(card0, card1));
}
/**
* Creates a new {@link CombinationType#TRIPLE}-Combination.
* @param card0 the first card
* @param card1 the second card
* @param card2 the third card
* @return a new combination
* @see CombinationType#TRIPLE
*/
@Contract("_, _, _ -> new")
public static @NotNull Combination triple(@NotNull Card card0, @NotNull Card card1, @NotNull Card card2) {
return new Combination(CombinationType.TRIPLE, List.of(card0, card1, card2));
}
/**
* Creates a new {@link CombinationType#FULL_HOUSE}-Combination.
* @param tripleCard0 the first card of the triple
* @param tripleCard1 the second card of the triple
* @param tripleCard2 the third card of the triple
* @param pairCard0 the first card of the pair
* @param pairCard1 the second card of the pair
* @return a new combination
* @see CombinationType#FULL_HOUSE
*/
@Contract("_, _, _, _, _ -> new")
public static @NotNull Combination fullHouse(
@NotNull Card tripleCard0, @NotNull Card tripleCard1, @NotNull Card tripleCard2,
@NotNull Card pairCard0, @NotNull Card pairCard1
) {
return new Combination(CombinationType.FULL_HOUSE, List.of(
tripleCard0, tripleCard1, tripleCard2, pairCard0, pairCard1
));
}
/**
* Creates a new {@link CombinationType#SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination sequence(@NotNull Card @NotNull... cards) {
return new Combination(CombinationType.SEQUENCE, List.of(cards));
}
/**
* Creates a new {@link CombinationType#SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination sequence(@NotNull List<@NotNull Card> cards) {
return new Combination(CombinationType.SEQUENCE, cards);
}
/**
* Creates a new {@link CombinationType#PAIR_SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#PAIR_SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination pairSequence(@NotNull Card @NotNull... cards) {
return new Combination(CombinationType.PAIR_SEQUENCE, List.of(cards));
}
/**
* Creates a new {@link CombinationType#PAIR_SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#PAIR_SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination pairSequence(@NotNull List<@NotNull Card> cards) {
return new Combination(CombinationType.PAIR_SEQUENCE, cards);
}
/**
* Creates a new {@link CombinationType#BOMB}-Combination.
* @param card0 the first card
* @param card1 the second card
* @param card2 the third card
* @param card3 the fourth card
* @return a new combination
* @see CombinationType#BOMB
*/
@Contract("_, _, _, _ -> new")
public static @NotNull Combination bomb(
@NotNull Card card0, @NotNull Card card1, @NotNull Card card2, @NotNull Card card3
) {
return new Combination(CombinationType.BOMB, List.of(card0, card1, card2, card3));
}
/**
* Creates a new {@link CombinationType#BOMB_SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#BOMB_SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination bombSequence(@NotNull Card @NotNull... cards) {
return new Combination(CombinationType.BOMB_SEQUENCE, List.of(cards));
}
/**
* Creates a new {@link CombinationType#BOMB_SEQUENCE}-Combination.
* @param cards the sequence of cards (in correct order)
* @return a new combination
* @see CombinationType#BOMB_SEQUENCE
*/
@Contract("_ -> new")
public static @NotNull Combination bombSequence(@NotNull List<@NotNull Card> cards) {
return new Combination(CombinationType.BOMB_SEQUENCE, cards);
}
//</editor-fold>
}

@ -1,7 +1,6 @@
package eu.jonahbauer.tichu.common.model;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.*;
@ -20,34 +19,9 @@ public enum CombinationType {
public boolean check(@NotNull List<@NotNull Card> cards) {
return checkBasics(cards, 1, SPECIAL_CARDS);
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
if (stack.isEmpty()) return true; // you can play any card onto an empty stack
if (stack.size() == 1 && stack.top().card(0) == Card.HOUND) return false; // you cannot play any card onto a hound
var card = cards.get(0);
return switch (card) {
case HOUND, MAHJONG -> false;
case DRAGON -> true;
case PHOENIX -> stack.top().card(0) != Card.DRAGON;
default -> {
var top = stack.top().card(0);
if (top != Card.PHOENIX) {
yield card.getValue() > top.getValue();
} else if (stack.size() == 1) {
// every normal card has a value of at least 2, which is higher that the phoenix 1½
yield true;
} else {
yield card.getValue() > stack.top(-1).card(0).getValue();
}
}
};
}
},
/**
* A pair, i.e. two {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#getValue() value} or the
* A pair, i.e. two {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#value() value} or the
* {@link Card#PHOENIX} and a normal card.
*/
PAIR {
@ -59,16 +33,9 @@ public enum CombinationType {
return checkBasics(cards, 2, SPECIAL_CARDS)
&& isSameValue(cards, INDICES);
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| getSameValue(cards, INDICES) > getSameValue(stack.top().cards(), INDICES);
}
},
/**
* A triple, i.e. three {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#getValue() value}
* A triple, i.e. three {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#value() value}
* or the {@linkplain Card#PHOENIX} and two normal cards of the same value.
*/
TRIPLE {
@ -80,13 +47,6 @@ public enum CombinationType {
return checkBasics(cards, 3, SPECIAL_CARDS)
&& isSameValue(cards, INDICES);
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| getSameValue(cards, INDICES) > getSameValue(stack.top().cards(), INDICES);
}
},
/**
* A full house, i.e. five cards of which three form a {@link #TRIPLE} and the other two form a {@link #PAIR}.
@ -105,13 +65,6 @@ public enum CombinationType {
&& isSameValue(cards, INDICES_TRIPLE)
&& isSameValue(cards, INDICES_PAIR);
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| getSameValue(cards, INDICES_TRIPLE) > getSameValue(stack.top().cards(), INDICES_TRIPLE);
}
},
/**
* A sequence, i.e. at least five cards with increasing values. The first card in the sequence may be a
@ -124,8 +77,8 @@ public enum CombinationType {
private int getStartValue(@NotNull List<@NotNull Card> cards) {
return cards.get(0) == Card.PHOENIX
? cards.get(1).getValue() - 1
: cards.get(0).getValue();
? cards.get(1).value() - 1
: cards.get(0).value();
}
@Override
@ -141,27 +94,13 @@ public enum CombinationType {
if (start + i > 14) {
return false;
}
} else if (card.getValue() != start + i) {
} else if (card.value() != start + i) {
return false;
}
}
return true;
}
@Override
public boolean isCompatible(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert check(cards);
return stack.isEmpty()
|| stack.top().type() == this && stack.top().cards().size() == cards.size();
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| getStartValue(cards) > getStartValue(stack.top().cards());
}
},
/**
* A sequence of pairs, i.e. at least two pairs with increasing values.
@ -173,8 +112,8 @@ public enum CombinationType {
private int getStartValue(@NotNull List<@NotNull Card> cards) {
return cards.get(0) == Card.PHOENIX
? cards.get(1).getValue()
: cards.get(0).getValue();
? cards.get(1).value()
: cards.get(0).value();
}
@Override
@ -187,58 +126,29 @@ public enum CombinationType {
for (int i = 0; i < cards.size(); i++) {
var card = cards.get(i);
if (card != Card.PHOENIX && card.getValue() != start + i / 2) {
if (card != Card.PHOENIX && card.value() != start + i / 2) {
return false;
}
}
return true;
}
@Override
public boolean isCompatible(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert check(cards);
return stack.isEmpty()
|| stack.top().type() == this && stack.top().cards().size() == cards.size();
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| getStartValue(cards) > getStartValue(stack.top().cards());
}
},
/**
* A bomb, i.e. four {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#getValue() value}.
* A bomb, i.e. four {@linkplain Card#isNormal() normal cards} of the same {@linkplain Card#value() value}.
*/
BOMB {
private static final int[] INDICES = new int[] {0, 1, 2, 3};
private static final Set<Card> SPECIAL_CARDS = Collections.emptySet();
private static final Combination COMBINATION_HOUND = new Combination(SINGLE, List.of(Card.HOUND));
@Override
public boolean check(@NotNull List<@NotNull Card> cards) {
return checkBasics(cards, 4, SPECIAL_CARDS)
&& isSameValue(cards, INDICES);
}
@Override
public boolean isCompatible(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert check(cards);
return stack.isEmpty() || !COMBINATION_HOUND.equals(stack.top());
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| stack.top().type() != BOMB && stack.top().type() != BOMB_SEQUENCE
|| stack.top().type() == BOMB && getSameValue(cards, INDICES) > getSameValue(stack.top().cards(), INDICES);
}
},
/**
* A bomb sequence, i.e. a {@linkplain Card#getSuit() monochrome} {@link #SEQUENCE} that does not contain any
* A bomb sequence, i.e. a {@linkplain Card#suit() monochrome} {@link #SEQUENCE} that does not contain any
* {@linkplain Card#isSpecial() special cards}.
* @apiNote Although not strictly necessary, for consistency with {@link #SEQUENCE} a list of cards representing a
* bomb sequence must be sorted by ascending value.
@ -246,43 +156,27 @@ public enum CombinationType {
*/
BOMB_SEQUENCE {
private static final Set<Card> SPECIAL_CARDS = Collections.emptySet();
private static final Combination COMBINATION_HOUND = new Combination(SINGLE, List.of(Card.HOUND));
private int getStartValue(@NotNull List<@NotNull Card> cards) {
return cards.get(0).getValue();
return cards.get(0).value();
}
@Override
public boolean check(@NotNull List<@NotNull Card> cards) {
if (!checkBasics(cards, null, SPECIAL_CARDS)) return false;
if (cards.size() < 5) return false;
if (cards.stream().map(Card::getSuit).distinct().count() != 1) return false;
if (cards.stream().map(Card::suit).distinct().count() != 1) return false;
int start = getStartValue(cards);
for (int i = 1; i < cards.size(); i++) {
var card = cards.get(i);
if (card.getValue() != start + i) {
if (card.value() != start + i) {
return false;
}
}
return true;
}
@Override
public boolean isCompatible(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert check(cards);
return stack.isEmpty() || !COMBINATION_HOUND.equals(stack.top());
}
@Override
public boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert isCompatible(stack, cards);
return stack.isEmpty()
|| stack.top().type() != BOMB_SEQUENCE
|| cards.size() > stack.top().cards().size()
|| getStartValue(cards) > getStartValue(stack.top().cards());
}
};
/**
@ -293,32 +187,6 @@ public enum CombinationType {
*/
public abstract boolean check(@NotNull List<@NotNull Card> cards);
/**
* <p>Checks whether the {@code cards} are <i>compatible</i> with the {@code stack}, i.e. the {@code cards} can
* theoretically be played on top of the {@code stack}. For example a {@link #SEQUENCE} is compatible with a given
* stack only if the stack is empty, or it consists of sequences of the same length.
* <p>However, this method does not check whether the {@code cards} are actually higher than the top of the
* {@code stack}. If the {@code cards} are not a {@linkplain #check(List) valid} combination of this type, the
* behaviour of this method is undefined and an exception may be thrown.
* @param stack the current stack of cards
* @param cards the cards to be played
* @return {@code true} iff the {@code cards} are compatible with the {@code stack}
*/
public boolean isCompatible(@NotNull Stack stack, @NotNull List<@NotNull Card> cards) {
assert check(cards);
return stack.isEmpty() || stack.top().type() == this;
}
/**
* <p>Checks whether the {@code cards} are higher than the combination currently on top of the {@code stack}.
* <p>If the {@code cards} are not {@linkplain #isCompatible(Stack, List) compatible} with the {@code stack},
* the behaviour of this method is undefined and an exception may be thrown.
* @param stack the current stack of cards
* @param cards the cards to be played
* @return {@code true} iff the {@code cards} can be played on top of the {@code stack}
*/
public abstract boolean isHigher(@NotNull Stack stack, @NotNull List<@NotNull Card> cards);
//<editor-fold desc="Utility Methods" defaultstate="collapsed">
/**
* Performs basic checks on a list of cards, i.e. this method checks
@ -377,31 +245,13 @@ public enum CombinationType {
if (card.isSpecial()) continue;
if (value == null) {
value = card.getValue();
} else if (!value.equals(card.getValue())) {
value = card.value();
} else if (!value.equals(card.value())) {
return false;
}
}
return true;
}
/**
* Assuming that all the {@linkplain Card#isNormal() normal cards} at the specified indices have the same value
* returns that value. The return value is unspecified if the assumption is violated.
* @param cards a list of cards
* @param indices an array of indices of cards in the list
* @see #isSameValue(List, int...)
* @throws NoSuchElementException if there is no normal card among the specified indices
*/
private static @Range(from = 2, to = 14) int getSameValue(@NotNull List<@NotNull Card> cards, int @NotNull... indices) {
for (int index : indices) {
Card card = cards.get(index);
if (card.isSpecial()) continue;
return card.getValue();
}
throw new NoSuchElementException();
}
//</editor-fold>
}

@ -1,5 +1,6 @@
package eu.jonahbauer.tichu.common.model;
import eu.jonahbauer.tichu.common.model.combinations.Combination;
import eu.jonahbauer.tichu.common.model.exceptions.IncompatibleCombinationException;
import eu.jonahbauer.tichu.common.model.exceptions.TooLowCombinationException;
import lombok.EqualsAndHashCode;
@ -16,10 +17,30 @@ public final class Stack {
private final @Unmodifiable @NotNull List<@NotNull Combination> combinations;
/**
* Builds a stack of the given combinations.
* @param combinations an array of combinations from bottom to top
* @return a stack of the given combinations.
* @throws IncompatibleCombinationException if any one of the combinations
* {@link Combination#isCompatibleWith(Stack) is incompatible with} the stack of all the combinations before it
* @throws TooLowCombinationException if any one of the combinations
* {@linkplain Combination#isHigherThan(Stack) is lower than} the stack of alle the combinations before it
* @throws NullPointerException if the array or any of its elements are {@code null}
*/
public static @NotNull Stack of(@NotNull Combination @NotNull... combinations) {
return of(Arrays.asList(combinations));
return of(List.of(combinations));
}
/**
* Builds a stack of the given combinations.
* @param combinations a list of combinations from bottom to top
* @return a stack of the given combinations.
* @throws IncompatibleCombinationException if any one of the combinations
* {@link Combination#isCompatibleWith(Stack) is incompatible with} the stack of all the combinations before it
* @throws TooLowCombinationException if any one of the combinations
* {@linkplain Combination#isHigherThan(Stack) is lower than} the stack of alle the combinations before it
* @throws NullPointerException if the list or any of its elements are {@code null}
*/
public static @NotNull Stack of(@NotNull List<@NotNull Combination> combinations) {
Stack out = new Stack(List.copyOf(combinations)); // List#copyOf performs null-checks
@ -27,14 +48,20 @@ public final class Stack {
Stack lower = out.substack(0, i);
Combination top = out.get(i);
if (!top.type().isHigher(lower, top.cards())) {
throw new IllegalArgumentException("Invalid stack: %s cannot be played on top of %s".formatted(top, lower));
if (!top.isCompatibleWith(lower)) {
throw new IncompatibleCombinationException(lower, top);
} else if (!top.isHigherThan(lower)) {
throw new TooLowCombinationException(lower, top);
}
}
return out;
}
/**
* Returns an empty stack.
* @return an empty stack.
*/
public static @NotNull Stack empty() {
return EMPTY;
}
@ -101,35 +128,35 @@ public final class Stack {
}
/**
* Checks whether the {@code combination} is {@linkplain CombinationType#isCompatible(Stack, List) compatible} with
* Checks whether the {@code combination} {@linkplain Combination#isCompatibleWith(Stack) is compatible with}
* this stack.
* @param combination a combination
* @return {@code true} iff the {@code combination} is compatible
* @see CombinationType#isCompatible(Stack, List)
* @see Combination#isCompatibleWith(Stack)
* @see #isHigher(Combination)
* @see #isCompatibleAndHigher(Combination)
*/
@Contract(pure = true)
public boolean isCompatible(@NotNull Combination combination) {
Objects.requireNonNull(combination, "combination must not be null");
return combination.type().isCompatible(this, combination.cards());
return combination.isCompatibleWith(this);
}
/**
* Checks whether a {@linkplain #isCompatible(Combination) compatible} {@code combination} is
* {@linkplain CombinationType#isHigher(Stack, List) higher} than the {@linkplain #top() topmost} combination
* Checks whether a {@linkplain #isCompatible(Combination) compatible} {@code combination}
* {@linkplain Combination#isHigherThan(Stack) is higher than} the {@linkplain #top() topmost} combination
* on this stack.
* The behaviour of this method is undefined if the {@code combination} is incompatible and an exception may be thrown.
* @param combination a combination
* @return {@code true} iff the combination is higher than the topmost combination on this stack
* @see CombinationType#isHigher(Stack, List)
* @see Combination#isHigherThan(Stack)
* @see #isCompatible(Combination)
* @see #isCompatibleAndHigher(Combination)
*/
@Contract(pure = true)
public boolean isHigher(@NotNull Combination combination) {
Objects.requireNonNull(combination, "combination must not be null");
return combination.type().isHigher(this, combination.cards());
return combination.isHigherThan(this);
}
/**
@ -138,14 +165,14 @@ public final class Stack {
* than the {@linkplain #top() topmost} combination on this stack.
* @param combination a combination
* @return {@code true} iff the combination is compatible with and higher than this stack
* @see Combination#isCompatibleAndHigher(Stack)
* @see #isHigher(Combination)
* @see #isCompatible(Combination)
*/
@Contract(pure = true)
public boolean isCompatibleAndHigher(@NotNull Combination combination) {
Objects.requireNonNull(combination, "combination must not be null");
return combination.type().isCompatible(this, combination.cards())
&& combination.type().isHigher(this, combination.cards());
return combination.isCompatibleAndHigher(this);
}
/**

@ -0,0 +1,62 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import eu.jonahbauer.tichu.common.model.exceptions.InvalidCombinationException;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.util.List;
import java.util.Objects;
@EqualsAndHashCode
abstract sealed class AbstractCombination implements Combination permits Tuple, Sequence, FullHouse, Single {
private final @NotNull CombinationType type;
private final @Unmodifiable @NotNull List<@NotNull Card> cards;
AbstractCombination(@NotNull CombinationType type, @NotNull List<@NotNull Card> cards) {
this.type = Objects.requireNonNull(type, "type must not be null");
this.cards = List.copyOf(Objects.requireNonNull(cards, "cards must not be null"));
if (!this.type.check(this.cards)) {
throw new InvalidCombinationException(type, cards);
}
}
@Override
public @NotNull CombinationType type() {
return type;
}
@Override
public @Unmodifiable @NotNull List<@NotNull Card> cards() {
return cards;
}
@Override
public final @NotNull Card card(int index) {
return cards.get(index);
}
@Override
public final int size() {
return cards.size();
}
@Override
public boolean isCompatibleWith(@NotNull Stack stack) {
return stack.isEmpty() || stack.top().type() == type() && stack.top().size() == size();
}
@Override
public boolean isCompatibleAndHigher(@NotNull Stack stack) {
return isCompatibleWith(stack) && isHigherThan(stack);
}
@Override
public String toString() {
return type + cards.toString();
}
}

@ -0,0 +1,15 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.NotNull;
public sealed interface Bomb extends Combination permits BombTuple, BombSequence{
@Override
default boolean isCompatibleWith(@NotNull Stack stack) {
return stack.isEmpty()
|| stack.top().type() != CombinationType.SINGLE
|| stack.top().card(0) != Card.HOUND;
}
}

@ -0,0 +1,37 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static eu.jonahbauer.tichu.common.model.CombinationType.BOMB_SEQUENCE;
/**
* A combination of type {@link CombinationType#BOMB_SEQUENCE}
*/
public final class BombSequence extends Sequence implements Bomb {
public static BombSequence of(@NotNull List<@NotNull Card> cards) {
return new BombSequence(cards);
}
BombSequence(@NotNull List<@NotNull Card> cards) {
super(BOMB_SEQUENCE, cards, 1);
}
@Override
public boolean isCompatibleWith(@NotNull Stack stack) {
return Bomb.super.isCompatibleWith(stack);
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
return stack.isEmpty()
|| stack.top().type() != BOMB_SEQUENCE
|| size() > stack.top().size()
|| start() > ((BombSequence) stack.top()).start();
}
}

@ -0,0 +1,47 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import static eu.jonahbauer.tichu.common.model.CombinationType.BOMB;
import static eu.jonahbauer.tichu.common.model.CombinationType.BOMB_SEQUENCE;
/**
* A combination of type {@link CombinationType#BOMB}
*/
public final class BombTuple extends Tuple implements Bomb {
public static BombTuple of(@NotNull List<@NotNull Card> cards) {
return new BombTuple(cards);
}
public static BombTuple of(@NotNull Card card1, @NotNull Card card2, @NotNull Card card3, @NotNull Card card4) {
return new BombTuple(List.of(
Objects.requireNonNull(card1, "card1 must not be null"),
Objects.requireNonNull(card2, "card2 must not be null"),
Objects.requireNonNull(card3, "card3 must not be null"),
Objects.requireNonNull(card4, "card4 must not be null")
));
}
BombTuple(@NotNull List<@NotNull Card> cards) {
super(BOMB, cards);
}
@Override
public boolean isCompatibleWith(@NotNull Stack stack) {
return Bomb.super.isCompatibleWith(stack);
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
return stack.isEmpty()
|| stack.top().type() != BOMB && stack.top().type() != BOMB_SEQUENCE
|| stack.top().type() == BOMB && value() > ((BombTuple) stack.top()).value();
}
}

@ -0,0 +1,99 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import org.jetbrains.annotations.Unmodifiable;
import java.io.Serializable;
import java.util.List;
public sealed interface Combination extends Serializable permits AbstractCombination, Bomb {
/**
* Returns the type of this combination.
* @return the type of this combination.
*/
@NotNull CombinationType type();
/**
* Returns the cards composing this combination.
* @return the cards composing this combination.
*/
@Unmodifiable @NotNull List<@NotNull Card> cards();
/**
* Returns the card at the specified index. This is shorthand for {@link #cards()}{@code .}{@link List#get(int) get(index)}.
* @param index the index of the card to return
* @return the card at the specified index.
* @throws IndexOutOfBoundsException if the index is out of bounds ({@code index < 0 || index >= size()})
*/
@NotNull Card card(@Range(from = 0, to = 13) int index);
/**
* Returns the size of this combination, i.e. the number of cards it consists of. This is shorthand for
* {@link #cards()}{@code .}{@link List#size() size()}.
* @return the size of this combination.
*/
@Range(from = 1, to = 14) int size();
/**
* Checks whether this combination is <em>compatible</em> with the given stack. The definition of
* <em>compatible</em> depends on the type of this combination:
* <ul>
* <li>
* a {@link Bomb} is compatible with any stack that does not have a {@linkplain Single single}
* {@link Card#HOUND} on top.
* </li>
* <li>
* any other combination is compatible with an empty stack and all stacks that have a combination
* of the same {@link #type()} and {@link #size()} on top.
* </li>
* </ul>
* @param stack a stack of combinations
* @return {@code true}, iff this combination is compatible with the given stack.
*/
boolean isCompatibleWith(@NotNull Stack stack);
/**
* Checks whether this combination is <em>higher</em> than the given stack, i.e. it could be played on top of
* that stack. This method does not, however, ensure that this combination <em>can</em> be played on top of that
* stack in every circumstance, since mahjong-wishes and right to play are not accounted for.
*
* <p>This method assumes that this combination {@linkplain #isCompatibleWith(Stack) is compatible with} the
* given stack, otherwise the result is undefined and the method may throw an exception.
* @param stack a stack of combinations
* @return {@code true}, iff this combination is higher than the given stack.
*/
boolean isHigherThan(@NotNull Stack stack);
/**
* Checks whether this combination is {@linkplain #isCompatibleWith(Stack) compatible with} and
* {@linkplain #isHigherThan(Stack) higher than} the given stack.
* @param stack a stack of combinations
* @return {@code true}, iff this combination is compatible with and higher than the given stack.
*/
boolean isCompatibleAndHigher(@NotNull Stack stack);
/**
* Creates a new combination of the given type consisting of the given cards.
* @param type a combination type
* @param cards a list of cards forming a {@linkplain CombinationType#check(List) valid} combination
* @return a new combination
*/
@Contract("_, _ -> new")
static Combination of(@NotNull CombinationType type, @NotNull List<@NotNull Card> cards) {
return switch (type) {
case SINGLE -> Single.of(cards);
case PAIR -> Pair.of(cards);
case TRIPLE -> Triple.of(cards);
case FULL_HOUSE -> FullHouse.of(cards);
case SEQUENCE -> SingleSequence.of(cards);
case PAIR_SEQUENCE -> PairSequence.of(cards);
case BOMB -> BombTuple.of(cards);
case BOMB_SEQUENCE -> BombSequence.of(cards);
};
}
}

@ -0,0 +1,63 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
/**
* A combination of type {@link CombinationType#FULL_HOUSE}
*/
public final class FullHouse extends AbstractCombination {
@Contract("_ -> new")
public static @NotNull FullHouse of(@NotNull List<@NotNull Card> cards) {
return new FullHouse(cards);
}
@Contract("_, _, _, _, _ -> new")
public static @NotNull FullHouse of(
@NotNull Card triple1, @NotNull Card triple2, @NotNull Card triple3,
@NotNull Card pair1, @NotNull Card pair2
) {
return new FullHouse(List.of(
Objects.requireNonNull(triple1, "triple1 must not be null"),
Objects.requireNonNull(triple2, "triple2 must not be null"),
Objects.requireNonNull(triple3, "triple3 must not be null"),
Objects.requireNonNull(pair1, "pair1 must not be null"),
Objects.requireNonNull(pair2, "pair2 must not be null")
));
}
private final int triple;
private final int pair;
FullHouse(@NotNull List<@NotNull Card> cards) {
super(CombinationType.FULL_HOUSE, cards);
this.triple = Tuple.getSameValue(cards.subList(0, 3));
this.pair = Tuple.getSameValue(cards.subList(3, 5));
}
/**
* The {@linkplain Card#value() value} of the triple.
*/
public int triple() {
return triple;
}
/**
* The {@linkplain Card#value() value} of the pair.
*/
public int pair() {
return pair;
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
return stack.isEmpty() || triple() > ((FullHouse) stack.top()).triple();
}
}

@ -0,0 +1,31 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
/**
* A combination of type {@link CombinationType#PAIR}
*/
public final class Pair extends Tuple {
@Contract("_ -> new")
public static @NotNull Pair of(@NotNull List<@NotNull Card> cards) {
return new Pair(cards);
}
@Contract("_, _ -> new")
public static @NotNull Pair of(@NotNull Card card1, @NotNull Card card2) {
return new Pair(List.of(
Objects.requireNonNull(card1, "card1 must not be null"),
Objects.requireNonNull(card2, "card2 must not be null")
));
}
Pair(@NotNull List<@NotNull Card> cards) {
super(CombinationType.PAIR, cards);
}
}

@ -0,0 +1,24 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A combination of type {@link CombinationType#PAIR_SEQUENCE}
*/
public final class PairSequence extends Sequence {
public static PairSequence of(@NotNull List<@NotNull Card> cards) {
return new PairSequence(cards);
}
public static PairSequence of(@NotNull Card @NotNull... cards) {
return new PairSequence(List.of(cards));
}
PairSequence(@NotNull List<@NotNull Card> cards) {
super(CombinationType.PAIR_SEQUENCE, cards, 2);
}
}

@ -0,0 +1,48 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.NoSuchElementException;
public abstract sealed class Sequence extends AbstractCombination permits SingleSequence, PairSequence, BombSequence {
private final int start;
private final int length;
Sequence(@NotNull CombinationType type, @NotNull List<@NotNull Card> cards, int n) {
super(type, cards);
this.length = cards.size() / n;
this.start = getStartValue(cards, n);
}
/**
* The {@linkplain Card#value() value} of the lowest card in this sequence.
*/
public int start() {
return start;
}
/**
* The length of this sequence.
*/
public int length() {
return length;
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
return stack.isEmpty() || start() > ((Sequence) stack.top()).start();
}
static int getStartValue(@NotNull List<@NotNull Card> cards, int n) {
for (int i = 0, size = cards.size(); i < size; i++) {
Integer value = cards.get(i).value();
if (value != null) return value - (i / n);
}
throw new NoSuchElementException();
}
}

@ -0,0 +1,70 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
/**
* A combination of type {@link CombinationType#SINGLE}
*/
public final class Single extends AbstractCombination {
@Contract("_ -> new")
public static @NotNull Single of(@NotNull List<@NotNull Card> cards) {
return new Single(cards);
}
@Contract("_ -> new")
public static @NotNull Single of(@NotNull Card card) {
return new Single(List.of(Objects.requireNonNull(card, "card must not be null")));
}
Single(@NotNull List<@NotNull Card> cards) {
super(CombinationType.SINGLE, cards);
}
/**
* Returns the single card this combination consists of. This is shorthand for {@link #card(int) card(0)}.
* @return the single card this combination consists of.
*/
public @NotNull Card card() {
return card(0);
}
/**
* Returns the {@linkplain Card#value() value} of the {@linkplain #card() card}.
* @return the value of the card.
*/
public Integer value() {
return card().value();
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
if (stack.isEmpty()) return true; // you can play any card onto an empty stack
var top = (Single) stack.top();
if (top.card() == Card.HOUND) return false; // you cannot play any card onto a hound
return switch (card()) {
case HOUND, MAHJONG -> false;
case DRAGON -> true;
case PHOENIX -> top.card() != Card.DRAGON;
default -> {
if (top.card() != Card.PHOENIX) {
yield value() > top.value();
} else if (stack.size() == 1) {
// every normal card has a value of at least 2, which is higher that the phoenix 1½
yield true;
} else {
yield value() > ((Single) stack.top(-1)).value();
}
}
};
}
}

@ -0,0 +1,24 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A combination of type {@link CombinationType#SEQUENCE}
*/
public final class SingleSequence extends Sequence {
public static SingleSequence of(@NotNull List<@NotNull Card> cards) {
return new SingleSequence(cards);
}
public static SingleSequence of(@NotNull Card @NotNull... cards) {
return new SingleSequence(List.of(cards));
}
SingleSequence(@NotNull List<@NotNull Card> cards) {
super(CombinationType.SEQUENCE, cards, 1);
}
}

@ -0,0 +1,32 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
/**
* A combination of type {@link CombinationType#TRIPLE}
*/
public final class Triple extends Tuple {
@Contract("_ -> new")
public static @NotNull Triple of(@NotNull List<@NotNull Card> cards) {
return new Triple(cards);
}
@Contract("_, _, _ -> new")
public static @NotNull Triple of(@NotNull Card card1, @NotNull Card card2, @NotNull Card card3) {
return new Triple(List.of(
Objects.requireNonNull(card1, "card1 must not be null"),
Objects.requireNonNull(card2, "card2 must not be null"),
Objects.requireNonNull(card3, "card3 must not be null")
));
}
Triple(@NotNull List<@NotNull Card> cards) {
super(CombinationType.TRIPLE, cards);
}
}

@ -0,0 +1,46 @@
package eu.jonahbauer.tichu.common.model.combinations;
import eu.jonahbauer.tichu.common.model.Card;
import eu.jonahbauer.tichu.common.model.CombinationType;
import eu.jonahbauer.tichu.common.model.Stack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.NoSuchElementException;
public abstract sealed class Tuple extends AbstractCombination permits Pair, Triple, BombTuple {
private final int value;
Tuple(@NotNull CombinationType type, @NotNull List<@NotNull Card> cards) {
super(type, cards);
this.value = getSameValue(cards);
}
/**
* The {@linkplain Card#value() value} of the cards in this tuple.
*/
public int value() {
return value;
}
@Override
public boolean isHigherThan(@NotNull Stack stack) {
assert isCompatibleWith(stack);
return stack.isEmpty() || value() > ((Tuple) stack.top()).value();
}
/**
* Assuming that all the {@linkplain Card#isNormal() normal cards} at the specified indices have the same value
* returns that value. The return value is unspecified if the assumption is violated.
* @param cards a list of cards
* @throws NoSuchElementException if there is no normal card among the specified indices
*/
static int getSameValue(@NotNull List<@NotNull Card> cards) {
for (var card : cards) {
if (card.isSpecial()) continue;
return card.value();
}
throw new NoSuchElementException();
}
}

@ -1,7 +1,7 @@
package eu.jonahbauer.tichu.common.model.exceptions;
import eu.jonahbauer.tichu.common.model.Combination;
import eu.jonahbauer.tichu.common.model.Stack;
import eu.jonahbauer.tichu.common.model.combinations.Combination;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;

@ -1,7 +1,7 @@
package eu.jonahbauer.tichu.common.model.exceptions;
import eu.jonahbauer.tichu.common.model.Combination;
import eu.jonahbauer.tichu.common.model.Stack;
import eu.jonahbauer.tichu.common.model.combinations.Combination;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;

@ -1,6 +1,7 @@
module eu.jonahbauer.tichu.common {
exports eu.jonahbauer.tichu.common.model;
exports eu.jonahbauer.tichu.common.model.exceptions;
exports eu.jonahbauer.tichu.common.model.combinations;
requires static lombok;
requires static org.jetbrains.annotations;

@ -1,5 +1,6 @@
package eu.jonahbauer.tichu.common.model;
import eu.jonahbauer.tichu.common.model.combinations.*;
import eu.jonahbauer.tichu.common.model.exceptions.IncompatibleCombinationException;
import eu.jonahbauer.tichu.common.model.exceptions.InvalidCombinationException;
import eu.jonahbauer.tichu.common.model.exceptions.TooLowCombinationException;
@ -28,7 +29,7 @@ public class CombinationTest {
.map(suit -> IntStream.rangeClosed(2, 14)
.mapToObj(Card::getCardsByValue)
.flatMap(cards -> cards.stream()
.filter(card -> card.getSuit() == suit)
.filter(card -> card.suit() == suit)
)
.map(List::of)
.toList()
@ -191,13 +192,13 @@ public class CombinationTest {
VALID_COMBINATIONS.get(type).forEach(combinations -> {
Stack stack = Stack.empty();
for (int i = 0; i < combinations.size(); i++) {
stack = stack.put(new Combination(type, combinations.get(i)));
stack = stack.put(Combination.of(type, combinations.get(i)));
for (int j = 0; j < i; j++) {
assertIsNotHigher(stack, type, combinations.get(j));
assertIsNotHigher(stack, Combination.of(type, combinations.get(j)));
}
for (int j = i + 1; j < combinations.size(); j++) {
assertIsHigher(stack, type, combinations.get(j));
assertIsHigher(stack, Combination.of(type, combinations.get(j)));
}
}
});
@ -209,30 +210,32 @@ public class CombinationTest {
.flatMap(entry -> entry.getValue().stream()
.flatMap(List::stream)
.filter(not(cards -> cards.size() == 1 && cards.get(0) == HOUND)) // hound cannot be bombed
.map(cards -> new Combination(entry.getKey(), cards))
.map(cards -> Combination.of(entry.getKey(), cards))
)
.map(Stack::of)
.toList();
Stack hound = Stack.of(Combination.single(HOUND));
Stack hound = Stack.of(Single.of(HOUND));
VALID_COMBINATIONS.get(BOMB).stream()
.flatMap(List::stream)
.map(BombTuple::of)
.forEach(bomb -> {
stacks.forEach(stack -> assertIsCompatible(stack, BOMB, bomb));
assertIsIncompatible(hound, BOMB, bomb);
stacks.forEach(stack -> assertIsCompatible(stack, bomb));
assertIsIncompatible(hound, bomb);
});
VALID_COMBINATIONS.get(BOMB_SEQUENCE).stream()
.flatMap(List::stream)
.map(BombSequence::of)
.forEach(bomb -> {
stacks.forEach(stack -> assertIsCompatible(stack, BOMB_SEQUENCE, bomb));
assertIsIncompatible(hound, BOMB_SEQUENCE, bomb);
stacks.forEach(stack -> assertIsCompatible(stack, bomb));
assertIsIncompatible(hound, bomb);
});
}
private static void assertValidCombination(CombinationType type, List<Card> cards) {
try {
new Combination(type, cards);
Combination.of(type, cards);
} catch (InvalidCombinationException e) {
fail("%s should be a valid %s.".formatted(cards, type), e);
}
@ -245,7 +248,7 @@ public class CombinationTest {
private static void assertInvalidCombination(CombinationType type, List<Card> cards) {
assertThrows(InvalidCombinationException.class, () -> {
new Combination(type, cards);
Combination.of(type, cards);
}, "%s should not be a valid %s.".formatted(cards, type));
assertFalse(
@ -255,79 +258,94 @@ public class CombinationTest {
}
private static void assertIsCompatible(CombinationType type, List<Card> stack, List<Card> cards) {
assertIsCompatible(Stack.of(new Combination(type, stack)), type, cards);
assertIsCompatible(Stack.of(Combination.of(type, stack)), Combination.of(type, cards));
}
private static void assertIsCompatible(Stack stack, CombinationType type, List<Card> cards) {
private static void assertIsCompatible(Stack stack, Combination combination) {
assertTrue(
type.isCompatible(stack, cards),
"%s should be compatible with %s".formatted(cards, stack)
combination.isCompatibleWith(stack),
"%s should be compatible with %s".formatted(combination, stack)
);
assertTrue(
stack.isCompatible(new Combination(type, cards)),
"%s should be compatible with %s".formatted(cards, stack)
stack.isCompatible(combination),
"%s should be compatible with %s".formatted(combination, stack)
);
}
private static void assertIsIncompatible(Stack stack, CombinationType type, List<Card> cards) {
private static void assertIsIncompatible(Stack stack, Combination combination) {
assertFalse(
type.isCompatible(stack, cards),
"%s should be incompatible with %s".formatted(cards, stack)
stack.isCompatible(combination),
"%s should be incompatible with %s".formatted(combination, stack)
);
assertFalse(
stack.isCompatible(new Combination(type, cards)),
"%s should be incompatible with %s".formatted(cards, stack)
stack.isCompatibleAndHigher(combination),
"%s should be incompatible with %s".formatted(combination, stack)
);
assertFalse(
stack.isCompatibleAndHigher(new Combination(type, cards)),
"%s should be incompatible with %s".formatted(cards, stack)
combination.isCompatibleWith(stack),
"%s should be incompatible with %s".formatted(combination, stack)
);
assertFalse(
combination.isCompatibleAndHigher(stack),
"%s should be incompatible with %s".formatted(combination, stack)
);
assertThrows(IncompatibleCombinationException.class, () -> {
//noinspection ResultOfMethodCallIgnored
stack.put(new Combination(type, cards));
}, "%s should be incompatible with %s".formatted(cards, stack));
stack.put(combination);
}, "%s should be incompatible with %s".formatted(combination, stack));
}
private static void assertIsHigher(Stack stack, CombinationType type, List<Card> cards) {
private static void assertIsHigher(Stack stack, Combination combination) {
assertTrue(
type.isHigher(stack, cards),
"%s should be higher than %s".formatted(cards, stack)
combination.isHigherThan(stack),
"%s should be higher than %s".formatted(combination, stack)
);
assertTrue(
stack.isHigher(new Combination(type, cards)),
"%s should be higher than %s".formatted(cards, stack)
stack.isHigher(combination),
"%s should be higher than %s".formatted(combination, stack)
);
assertTrue(
stack.isCompatibleAndHigher(new Combination(type, cards)),
"%s should be higher than %s".formatted(cards, stack)
combination.isCompatibleAndHigher(stack),
"%s should be higher than %s".formatted(combination, stack)
);
assertTrue(
stack.isCompatibleAndHigher(combination),
"%s should be higher than %s".formatted(combination, stack)
);
}
private static void assertIsNotHigher(Stack stack, CombinationType type, List<Card> cards) {
private static void assertIsNotHigher(Stack stack, Combination combination) {
assertFalse(
combination.isHigherThan(stack),
"%s should not be higher than %s".formatted(combination, stack)
);
assertFalse(
type.isHigher(stack, cards),
"%s should not be higher than %s".formatted(cards, stack)
stack.isHigher(combination),
"%s should not be higher than %s".formatted(combination, stack)
);
assertFalse(
stack.isHigher(new Combination(type, cards)),
"%s should not be higher than %s".formatted(cards, stack)
combination.isCompatibleAndHigher(stack),
"%s should not be higher than %s".formatted(combination, stack)
);
assertFalse(
stack.isCompatibleAndHigher(new Combination(type, cards)),
"%s should not be higher than %s".formatted(cards, stack)
stack.isCompatibleAndHigher(combination),
"%s should not be higher than %s".formatted(combination, stack)
);
assertThrows(TooLowCombinationException.class, () -> {
//noinspection ResultOfMethodCallIgnored
stack.put(new Combination(type, cards));
stack.put(combination);
});
}
}

Loading…
Cancel
Save