parent
3ff2fdfeda
commit
9f067fae1a
@ -1,334 +0,0 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine;
|
|
||||||
|
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
|
||||||
import eu.jonahbauer.wizard.core.util.Pair;
|
|
||||||
import lombok.*;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@With
|
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
@SuppressWarnings({"unused", "UnusedReturnValue", "OptionalUsedAsFieldOrParameterType", "OptionalAssignedToNull"})
|
|
||||||
public final class GameData {
|
|
||||||
private final @Unmodifiable List<UUID> players;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> score;
|
|
||||||
|
|
||||||
private final Integer round;
|
|
||||||
private final @Unmodifiable Map<UUID, @Unmodifiable List<Card>> hands;
|
|
||||||
private final Optional<Card> trumpCard;
|
|
||||||
private final Card.Suit trumpSuit;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> predictions;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> tricks;
|
|
||||||
|
|
||||||
private final Integer trick;
|
|
||||||
private final @Unmodifiable List<Pair<UUID, Card>> stack;
|
|
||||||
|
|
||||||
private final UUID currentPlayer;
|
|
||||||
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredPlayers;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredRound;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrick;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredHands;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrumpCard;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrumpSuit;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredStack;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredPredictions;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTricks;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredScore;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredCurrentPlayer;
|
|
||||||
|
|
||||||
@Builder(toBuilder = true)
|
|
||||||
private GameData(@Unmodifiable List<UUID> players,
|
|
||||||
@Unmodifiable Map<UUID, Integer> score,
|
|
||||||
Integer round,
|
|
||||||
@Unmodifiable Map<UUID, @Unmodifiable List<Card>> hands,
|
|
||||||
Optional<Card> trumpCard,
|
|
||||||
Card.Suit trumpSuit,
|
|
||||||
@Unmodifiable Map<UUID, Integer> predictions,
|
|
||||||
@Unmodifiable Map<UUID, Integer> tricks,
|
|
||||||
Integer trick,
|
|
||||||
@Unmodifiable List<Pair<UUID, Card>> stack,
|
|
||||||
UUID currentPlayer)
|
|
||||||
{
|
|
||||||
this.players = players != null ? List.copyOf(players) : null;
|
|
||||||
this.score = score != null ? Map.copyOf(score) : null;
|
|
||||||
|
|
||||||
this.round = round;
|
|
||||||
this.hands = hands != null ? Map.copyOf(hands) : null;
|
|
||||||
this.trumpCard = trumpCard;
|
|
||||||
this.trumpSuit = trumpSuit;
|
|
||||||
this.predictions = predictions != null ? Map.copyOf(predictions) : null;
|
|
||||||
this.tricks = tricks != null ? Map.copyOf(tricks) : null;
|
|
||||||
|
|
||||||
this.trick = trick;
|
|
||||||
this.stack = stack != null ? List.copyOf(stack) : null;
|
|
||||||
|
|
||||||
this.currentPlayer = currentPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
//<editor-fold desc="Getters" defaultstate="collapsed">
|
|
||||||
public List<UUID> getPlayers() {
|
|
||||||
if (players == null) throw new UnsupportedOperationException();
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRound() {
|
|
||||||
if (round == null) throw new UnsupportedOperationException();
|
|
||||||
return round;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTrick() {
|
|
||||||
if (trick == null) throw new UnsupportedOperationException();
|
|
||||||
return trick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, List<Card>> getHands() {
|
|
||||||
if (hands == null) throw new UnsupportedOperationException();
|
|
||||||
return hands;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Card getTrumpCard() {
|
|
||||||
if (trumpCard == null) throw new UnsupportedOperationException();
|
|
||||||
return trumpCard.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Card.Suit getTrumpSuit() {
|
|
||||||
if (trumpSuit == null) throw new UnsupportedOperationException();
|
|
||||||
return trumpSuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Pair<UUID, Card>> getStack() {
|
|
||||||
if (stack == null) throw new UnsupportedOperationException();
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getPredictions() {
|
|
||||||
if (predictions == null) throw new UnsupportedOperationException();
|
|
||||||
return predictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getTricks() {
|
|
||||||
if (tricks == null) throw new UnsupportedOperationException();
|
|
||||||
return tricks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getScore() {
|
|
||||||
if (score == null) throw new UnsupportedOperationException();
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getCurrentPlayer() {
|
|
||||||
if (currentPlayer == null) throw new UnsupportedOperationException();
|
|
||||||
return currentPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getPlayer(int index) {
|
|
||||||
return getPlayers().get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPlayerCount() {
|
|
||||||
return getPlayers().size();
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
//<editor-fold desc="Requirements" defaultstate="collapsed">
|
|
||||||
public GameData requirePlayers() {
|
|
||||||
if (players == null) throw new AssertionError();
|
|
||||||
requiredPlayers = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireRound() {
|
|
||||||
if (round == null) throw new AssertionError();
|
|
||||||
requiredRound = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrick() {
|
|
||||||
if (trick == null) throw new AssertionError();
|
|
||||||
requiredTrick = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireHands() {
|
|
||||||
if (hands == null) throw new AssertionError();
|
|
||||||
requiredHands = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireAllHands() {
|
|
||||||
requirePlayers();
|
|
||||||
requireHands();
|
|
||||||
for (UUID uuid : players) {
|
|
||||||
if (!hands.containsKey(uuid)) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrumpCard() {
|
|
||||||
if (trumpCard == null) throw new AssertionError();
|
|
||||||
requiredTrumpCard = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrumpSuit() {
|
|
||||||
if (trumpSuit == null) throw new AssertionError();
|
|
||||||
requiredTrumpSuit = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireStack() {
|
|
||||||
if (stack == null) throw new AssertionError();
|
|
||||||
requiredStack = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requirePredictions() {
|
|
||||||
if (predictions == null) throw new AssertionError();
|
|
||||||
requiredPredictions = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireAllPredictions() {
|
|
||||||
requirePlayers();
|
|
||||||
requirePredictions();
|
|
||||||
for (UUID uuid : players) {
|
|
||||||
if (!predictions.containsKey(uuid)) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTricks() {
|
|
||||||
if (tricks == null) throw new AssertionError();
|
|
||||||
requiredTricks = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireScore() {
|
|
||||||
if (score == null) throw new AssertionError();
|
|
||||||
requiredScore = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireCurrentPlayer() {
|
|
||||||
if (currentPlayer == null) throw new AssertionError();
|
|
||||||
requiredCurrentPlayer = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData onlyRequired() {
|
|
||||||
var builder = toBuilder();
|
|
||||||
if (!requiredPlayers) builder.players(null);
|
|
||||||
if (!requiredRound) builder.round(null);
|
|
||||||
if (!requiredTrick) builder.trick(null);
|
|
||||||
if (!requiredHands) builder.hands(null);
|
|
||||||
if (!requiredTrumpCard) builder.trumpCard(null);
|
|
||||||
if (!requiredTrumpSuit) builder.trumpSuit(null);
|
|
||||||
if (!requiredStack) builder.stack(null);
|
|
||||||
if (!requiredPredictions) builder.predictions(null);
|
|
||||||
if (!requiredTricks) builder.tricks(null);
|
|
||||||
if (!requiredScore) builder.score(null);
|
|
||||||
if (!requiredCurrentPlayer) builder.currentPlayer(null);
|
|
||||||
requiredPlayers = false;
|
|
||||||
requiredRound = false;
|
|
||||||
requiredTrick = false;
|
|
||||||
requiredHands = false;
|
|
||||||
requiredTrumpCard = false;
|
|
||||||
requiredTrumpSuit = false;
|
|
||||||
requiredStack = false;
|
|
||||||
requiredPredictions = false;
|
|
||||||
requiredTricks = false;
|
|
||||||
requiredScore = false;
|
|
||||||
requiredCurrentPlayer = false;
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
//<editor-fold desc="Wither" defaultstate="collapsed">
|
|
||||||
public GameData withPrediction(UUID player, int prediction) {
|
|
||||||
Map<UUID, Integer> predictions = new HashMap<>(getPredictions());
|
|
||||||
predictions.put(player, prediction);
|
|
||||||
return withPredictions(predictions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextPlayer() {
|
|
||||||
int index = getPlayers().indexOf(getCurrentPlayer());
|
|
||||||
UUID next;
|
|
||||||
if (index == -1) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
} else if (index == getPlayerCount() - 1) {
|
|
||||||
next = getPlayers().get(0);
|
|
||||||
} else {
|
|
||||||
next = getPlayers().get(index + 1);
|
|
||||||
}
|
|
||||||
return withCurrentPlayer(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextTrick() {
|
|
||||||
int trick = getTrick();
|
|
||||||
return withTrick(trick + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextRound() {
|
|
||||||
int round = getRound();
|
|
||||||
return withRound(round + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withCardPlayed(UUID player, Card card) {
|
|
||||||
List<Card> hand = new ArrayList<>(getHands().get(player));
|
|
||||||
hand.remove(card);
|
|
||||||
Map<UUID, List<Card>> hands = new HashMap<>(getHands());
|
|
||||||
hands.put(player, hand);
|
|
||||||
List<Pair<UUID, Card>> stack = new ArrayList<>(getStack());
|
|
||||||
stack.add(Pair.of(player, card));
|
|
||||||
return withStack(stack).withHands(hands);
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder("(");
|
|
||||||
if (players != null) builder.append("players=").append(players).append(", ");
|
|
||||||
if (round != null) builder.append("round=").append(round).append(", ");
|
|
||||||
if (trick != null) builder.append("trick=").append(trick).append(", ");
|
|
||||||
if (hands != null) builder.append("hands=").append(hands).append(", ");
|
|
||||||
if (trumpCard != null) builder.append("trumpCard=").append(trumpCard).append(", ");
|
|
||||||
if (trumpSuit != null) builder.append("trumpSuit=").append(trumpSuit).append(", ");
|
|
||||||
if (stack != null) builder.append("stack=").append(stack).append(", ");
|
|
||||||
if (predictions != null) builder.append("predictions=").append(predictions).append(", ");
|
|
||||||
if (tricks != null) builder.append("tricks=").append(tricks).append(", ");
|
|
||||||
if (currentPlayer != null) builder.append("currentPlayer=").append(currentPlayer).append(", ");
|
|
||||||
if (builder.length() > 1) builder.setLength(builder.length() - 2);
|
|
||||||
builder.append(")");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An interface for @lombok.experimental.Delegate
|
|
||||||
public interface Getters {
|
|
||||||
@NotNull List<UUID> getPlayers();
|
|
||||||
@NotNull Map<UUID, Integer> getScore();
|
|
||||||
|
|
||||||
int getRound();
|
|
||||||
@NotNull Map<UUID, List<Card>> getHands();
|
|
||||||
@Nullable Card getTrumpCard();
|
|
||||||
@NotNull Card.Suit getTrumpSuit();
|
|
||||||
@NotNull Map<UUID, Integer> getPredictions();
|
|
||||||
@NotNull Map<UUID, Integer> getTricks();
|
|
||||||
|
|
||||||
int getTrick();
|
|
||||||
@NotNull List<Pair<UUID, Card>> getStack();
|
|
||||||
|
|
||||||
@NotNull UUID getCurrentPlayer();
|
|
||||||
@NotNull UUID getPlayer(int index);
|
|
||||||
int getPlayerCount();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,219 @@
|
|||||||
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Unmodifiable
|
||||||
|
@EqualsAndHashCode(of = "values")
|
||||||
|
public final class GameData {
|
||||||
|
private static final int SIZE = 11;
|
||||||
|
|
||||||
|
public static final Key<List<UUID>> PLAYERS = new Key<>("players", 0);
|
||||||
|
public static final Key<Map<UUID, Integer>> SCORE = new Key<>("score", 1);
|
||||||
|
public static final Key<Integer> ROUND = new Key<>("round", 2);
|
||||||
|
public static final Key<Map<UUID, List<Card>>> HANDS = new Key<>("hands", 3);
|
||||||
|
public static final Key<Card> TRUMP_CARD = new Key<>("trumpCard", 4);
|
||||||
|
public static final Key<Card.Suit> TRUMP_SUIT = new Key<>("trumpSuit", 5);
|
||||||
|
public static final Key<Map<UUID, Integer>> PREDICTIONS = new Key<>("predictions", 6);
|
||||||
|
public static final Key<Map<UUID, Integer>> TRICKS = new Key<>("tricks", 7);
|
||||||
|
public static final Key<Integer> TRICK = new Key<>("trick", 8);
|
||||||
|
public static final Key<List<Pair<UUID, Card>>> STACK = new Key<>("stack", 9);
|
||||||
|
public static final Key<UUID> CURRENT_PLAYER = new Key<>("currentPlayer", 10);
|
||||||
|
|
||||||
|
private final Object[] values;
|
||||||
|
private final boolean[] present;
|
||||||
|
private transient final boolean[] required = new boolean[SIZE];
|
||||||
|
|
||||||
|
public GameData() {
|
||||||
|
this.values = new Object[SIZE];
|
||||||
|
this.present = new boolean[SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameData(Object[] values, boolean[] present) {
|
||||||
|
this.values = values;
|
||||||
|
this.present = present;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value to which the specified key is mapped or {@code null} if this map contains no mapping for the
|
||||||
|
* key.
|
||||||
|
* @param key the key whose associated value is to be returned
|
||||||
|
* @param <T> the value type
|
||||||
|
* @return the value to which the specified key is mapped, or null if this map contains no mapping for the key
|
||||||
|
*/
|
||||||
|
public <T> T get(@NotNull Key<T> key) {
|
||||||
|
int index = key.index();
|
||||||
|
if (!present[index]) throw new NoSuchElementException();
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this map with the value for the specified key changed or {@code this} if the current value
|
||||||
|
* is equal to the new value.
|
||||||
|
* @param key the key whose associated value is to be changed
|
||||||
|
* @param value value to be associated with the specified key
|
||||||
|
* @param <T> the value type
|
||||||
|
* @return the newly generated map or {@code this} is no changes had to be made
|
||||||
|
*/
|
||||||
|
public <T> GameData with(@NotNull Key<T> key, T value) {
|
||||||
|
int index = key.index();
|
||||||
|
|
||||||
|
if (present[index] && Objects.equals(values[index], value)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index] = value;
|
||||||
|
newPresent[index] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T1, T2> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2) {
|
||||||
|
int index1 = key1.index();
|
||||||
|
int index2 = key2.index();
|
||||||
|
|
||||||
|
if (present[index1]
|
||||||
|
&& present[index2]
|
||||||
|
&& Objects.equals(values[index1], value1)
|
||||||
|
&& Objects.equals(values[index2], value2)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index1] = value1;
|
||||||
|
newPresent[index1] = true;
|
||||||
|
newValues[index2] = value2;
|
||||||
|
newPresent[index2] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T1, T2, T3> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2, Key<T3> key3, T3 value3) {
|
||||||
|
int index1 = key1.index();
|
||||||
|
int index2 = key2.index();
|
||||||
|
int index3 = key3.index();
|
||||||
|
|
||||||
|
if (present[index1]
|
||||||
|
&& present[index2]
|
||||||
|
&& present[index3]
|
||||||
|
&& Objects.equals(values[index1], value1)
|
||||||
|
&& Objects.equals(values[index2], value2)
|
||||||
|
&& Objects.equals(values[index3], value3)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index1] = value1;
|
||||||
|
newPresent[index1] = true;
|
||||||
|
newValues[index2] = value2;
|
||||||
|
newPresent[index2] = true;
|
||||||
|
newValues[index3] = value3;
|
||||||
|
newPresent[index3] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for the specified key or throws a
|
||||||
|
* {@link NoSuchElementException} otherwise.
|
||||||
|
* @param key key whose presence in this map is to be tested
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for the specified key
|
||||||
|
* @see Map#containsKey(Object)
|
||||||
|
*/
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public GameData require(@NotNull Key<?> key) {
|
||||||
|
if (!present[key.index()]) {
|
||||||
|
throw new NoSuchElementException("Could not find required value '" + key + "'.");
|
||||||
|
}
|
||||||
|
required[key.index()] = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for each of the specified keys or throws a
|
||||||
|
* {@link NoSuchElementException} otherwise.
|
||||||
|
* @param keys keys whose presence in this map is to be tested
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for at least one of the specified keys
|
||||||
|
* @see #require(Key)
|
||||||
|
*/
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public GameData require(Key<?>...keys) {
|
||||||
|
for (Key<?> key : keys) {
|
||||||
|
require(key);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for both the specified keys and the specified map contains
|
||||||
|
* a mapping for each entry in the specified list.
|
||||||
|
* @param list key to a list whose entries' presence is to be tested
|
||||||
|
* @param map key to a map which must contain the specified keys
|
||||||
|
* @param <K> key type
|
||||||
|
* @param <V> value type
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for at least one of the specified keys or if
|
||||||
|
* the associated map contains no mapping for at least one of the entries of
|
||||||
|
* the associated list
|
||||||
|
*/
|
||||||
|
@Contract("_,_ -> this")
|
||||||
|
public <K, V> GameData requireEach(Key<? extends Collection<K>> list, Key<Map<K,V>> map) {
|
||||||
|
require(list, map);
|
||||||
|
var mapValue = get(map);
|
||||||
|
var listValue = get(list);
|
||||||
|
for (K k : listValue) {
|
||||||
|
if (!mapValue.containsKey(k)) throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retains only the mappings that have been required since object creation.
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public GameData clean() {
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
|
||||||
|
boolean modified = false;
|
||||||
|
for (int i = 0; i < SIZE; i++) {
|
||||||
|
if (!required[i]) {
|
||||||
|
newValues[i] = null;
|
||||||
|
newPresent[i] = false;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) return new GameData(newValues, newPresent);
|
||||||
|
else return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Value
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Key<T> {
|
||||||
|
String name;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
|
public class InvalidDataException extends RuntimeException {
|
||||||
|
public InvalidDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states;
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
public interface State {
|
import eu.jonahbauer.wizard.core.machine.Context;
|
||||||
default void onEnter() {}
|
|
||||||
default void onTimeout() {}
|
public interface State<S extends State<S,C>, C extends Context<S,C>> {
|
||||||
default void onExit() {}
|
default void onEnter(C context) {}
|
||||||
|
default void onTimeout(C context) {}
|
||||||
|
default void onExit(C context) {}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
package eu.jonahbauer.wizard.core.machine.states.game;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class Finishing extends GameState {
|
public final class Finishing extends GameState {
|
||||||
|
|
||||||
public Finishing(Game game, GameData data) {
|
public Finishing(GameData data) {
|
||||||
super(game, data.requireScore());
|
super(data.require(SCORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
getGame().notify(new ScoreMessage(getScore()));
|
game.notify(new ScoreMessage(get(SCORE)));
|
||||||
getGame().finish();
|
game.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
package eu.jonahbauer.wizard.core.machine.states.game;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class Starting extends GameState {
|
public final class Starting extends GameState {
|
||||||
|
|
||||||
public Starting(Game game, GameData data) {
|
public Starting(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new StartingRound(getGame(), getData().withRound(0).withScore(Collections.emptyMap())));
|
transition(game, new StartingRound(getData().with(
|
||||||
|
ROUND, 0,
|
||||||
|
SCORE, Map.of()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,46 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.HandMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.HandMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
import eu.jonahbauer.wizard.core.model.Deck;
|
import eu.jonahbauer.wizard.core.model.Deck;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class Dealing extends RoundState {
|
public final class Dealing extends RoundState {
|
||||||
private transient Map<UUID, List<Card>> hands;
|
|
||||||
|
|
||||||
public Dealing(Game game, GameData data) {
|
public Dealing(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
Deck deck = new Deck(getGame().getConfig().cards());
|
Deck deck = new Deck(game.getConfig().cards());
|
||||||
deck.shuffle();
|
deck.shuffle();
|
||||||
|
|
||||||
hands = new HashMap<>();
|
var hands = new HashMap<UUID, List<Card>>();
|
||||||
|
|
||||||
int dealer = getRound();
|
int round = get(ROUND);
|
||||||
int playerCount = getPlayerCount();
|
int playerCount = getPlayerCount();
|
||||||
int cardCount = getRound() + 1;
|
int cardCount = round + 1;
|
||||||
for (int i = 1; i <= playerCount; i++) {
|
for (int i = 1; i <= playerCount; i++) {
|
||||||
int player = (dealer + i) % playerCount;
|
int player = (round + i) % playerCount;
|
||||||
hands.put(getPlayer(player), deck.draw(cardCount));
|
hands.put(getPlayer(player), deck.draw(cardCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Card> trumpCard = Optional.ofNullable(deck.draw());
|
for (UUID player : get(PLAYERS)) {
|
||||||
|
game.notify(player, new HandMessage(player, hands.get(player)));
|
||||||
|
}
|
||||||
|
|
||||||
transition(new DeterminingTrump(
|
Card trumpCard = deck.draw();
|
||||||
getGame(),
|
transition(game, new DeterminingTrump(
|
||||||
getData().withHands(hands)
|
getData().with(
|
||||||
.withTrumpCard(trumpCard)
|
HANDS, Map.copyOf(hands),
|
||||||
|
TRUMP_CARD, trumpCard
|
||||||
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (hands != null) {
|
|
||||||
for (UUID player : getPlayers()) {
|
|
||||||
getGame().notify(player, new HandMessage(player, hands.get(player)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,71 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.TrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.TrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PICK_TRUMP;
|
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PICK_TRUMP;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class DeterminingTrump extends RoundState {
|
public final class DeterminingTrump extends RoundState {
|
||||||
private transient Card.Suit trumpSuit;
|
public DeterminingTrump(GameData data) {
|
||||||
|
super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS));
|
||||||
public DeterminingTrump(Game game, GameData data) {
|
|
||||||
super(game, data.requireAllHands().requireTrumpCard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
Card trumpCard = getTrumpCard();
|
Card trumpCard = getData().get(TRUMP_CARD);
|
||||||
Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE;
|
Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE;
|
||||||
if (trumpSuit == null) {
|
if (trumpSuit == null) {
|
||||||
getGame().notify(new TrumpMessage(getTrumpCard(), null));
|
game.notify(new TrumpMessage(trumpCard, null));
|
||||||
getGame().notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(true)));
|
game.notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(game, true)));
|
||||||
timeout();
|
timeout(game);
|
||||||
} else {
|
} else {
|
||||||
this.trumpSuit = trumpSuit;
|
transition(game, trumpSuit);
|
||||||
transition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimeout() {
|
public void onTimeout(Game game) {
|
||||||
Card.Suit[] suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
Card.Suit[] suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
||||||
this.trumpSuit = suits[(int)(Math.random() * suits.length)];
|
transition(game, suits[(int) (Math.random() * suits.length)]);
|
||||||
transition();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (trumpSuit != null) {
|
|
||||||
getGame().notify(new TrumpMessage(getTrumpCard(), trumpSuit));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
if (getDealer().equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
if (getDealer().equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
||||||
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
||||||
this.trumpSuit = trumpMessage.getTrumpSuit();
|
transition(game, trumpMessage.getTrumpSuit());
|
||||||
transition();
|
|
||||||
} else {
|
} else {
|
||||||
super.onMessage(player, message);
|
super.onMessage(game, player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void checkTrumpSuit(Card.Suit suit) {
|
void checkTrumpSuit(Card.Suit suit) {
|
||||||
Card.Suit[] suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
Card.Suit[] suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
||||||
for (Card.Suit s : suits) {
|
for (Card.Suit s : suits) {
|
||||||
if (s == suit) return;
|
if (s == suit) return;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + ".");
|
throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transition() {
|
private void transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||||
if (trumpSuit == null) throw new AssertionError();
|
game.notify(new TrumpMessage(getData().get(TRUMP_CARD), trumpSuit));
|
||||||
|
transition(game, new Predicting(getData().with(
|
||||||
transition(new Predicting(
|
TRUMP_SUIT, trumpSuit,
|
||||||
getGame(),
|
PREDICTIONS, Map.of(),
|
||||||
getData().withTrumpSuit(trumpSuit)
|
CURRENT_PLAYER, getNextPlayer(getDealer())
|
||||||
.withPredictions(Collections.emptyMap())
|
)));
|
||||||
.withCurrentPlayer(getDealer())
|
|
||||||
.withNextPlayer()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public abstract class RoundState extends GameState {
|
public abstract class RoundState extends GameState {
|
||||||
public RoundState(Game game, GameData data) {
|
public RoundState(GameData data) {
|
||||||
super(game, data.requireRound().requireScore());
|
super(data.require(ROUND, SCORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UUID getDealer() {
|
protected UUID getDealer() {
|
||||||
return getPlayer(getRound() % getPlayerCount());
|
return getPlayer(getData().get(ROUND) % getPlayerCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
|
|
||||||
public final class StartingRound extends RoundState {
|
public final class StartingRound extends RoundState {
|
||||||
public StartingRound(Game game, GameData data) {
|
public StartingRound(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new Dealing(getGame(), getData()));
|
transition(game, new Dealing(getData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class StartingTrick extends TrickState {
|
public final class StartingTrick extends TrickState {
|
||||||
public StartingTrick(Game game, GameData data) {
|
public StartingTrick(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new PlayingCard(
|
transition(game, new PlayingCard(getData().with(STACK, List.of())));
|
||||||
getGame(),
|
|
||||||
getData().withStack(Collections.emptyList())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
package eu.jonahbauer.wizard.core.util;
|
package eu.jonahbauer.wizard.core.util;
|
||||||
|
|
||||||
public record Pair<F,S>(F first, S second) {
|
import java.util.Map;
|
||||||
|
|
||||||
|
public record Pair<F,S>(F first, S second) implements Map.Entry<F,S> {
|
||||||
public static <F,S> Pair<F,S> of(F first, S second) {
|
public static <F,S> Pair<F,S> of(F first, S second) {
|
||||||
return new Pair<>(first, second);
|
return new Pair<>(first, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public F getKey() {
|
||||||
|
return first();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public S getValue() {
|
||||||
|
return second();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public S setValue(S value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue