From 9f067fae1ae51ea21c267fe5ea4bd636e9c47175 Mon Sep 17 00:00:00 2001 From: Jonah Bauer Date: Fri, 29 Oct 2021 22:58:11 +0200 Subject: [PATCH] - refactored GameData - removed reference to Context in State --- .../java/eu/jonahbauer/wizard/core/CLI.java | 13 +- .../wizard/core/machine/Context.java | 36 +- .../jonahbauer/wizard/core/machine/Game.java | 18 +- .../wizard/core/machine/GameData.java | 334 ------------------ .../wizard/core/machine/states/GameData.java | 219 ++++++++++++ .../wizard/core/machine/states/GameState.java | 58 ++- .../machine/states/InvalidDataException.java | 7 + .../wizard/core/machine/states/State.java | 10 +- .../core/machine/states/game/Finishing.java | 14 +- .../core/machine/states/game/Starting.java | 17 +- .../core/machine/states/round/Dealing.java | 44 ++- .../states/round/DeterminingTrump.java | 68 ++-- .../machine/states/round/FinishingRound.java | 54 +-- .../core/machine/states/round/Predicting.java | 91 +++-- .../core/machine/states/round/RoundState.java | 11 +- .../machine/states/round/StartingRound.java | 10 +- .../machine/states/trick/FinishingTrick.java | 78 ++-- .../machine/states/trick/PlayingCard.java | 98 ++--- .../machine/states/trick/StartingTrick.java | 17 +- .../core/machine/states/trick/TrickState.java | 19 +- .../eu/jonahbauer/wizard/core/util/Pair.java | 19 +- .../states/round/FinishingRoundTest.java | 7 +- .../machine/states/round/PredictingTest.java | 29 +- .../states/trick/FinishingTrickTest.java | 46 +-- .../machine/states/trick/PlayingCardTest.java | 35 +- .../machine/states/trick/TrickStateTest.java | 12 +- 26 files changed, 657 insertions(+), 707 deletions(-) delete mode 100644 wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/GameData.java create mode 100644 wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameData.java create mode 100644 wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/InvalidDataException.java diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/CLI.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/CLI.java index bca3c31..c792460 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/CLI.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/CLI.java @@ -1,10 +1,13 @@ package eu.jonahbauer.wizard.core; import eu.jonahbauer.wizard.core.machine.Game; +import eu.jonahbauer.wizard.core.machine.states.GameState; +import eu.jonahbauer.wizard.core.messages.Observer; import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage; import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage; import eu.jonahbauer.wizard.core.messages.player.PredictMessage; import eu.jonahbauer.wizard.core.model.Card; +import eu.jonahbauer.wizard.core.model.Configuration; import eu.jonahbauer.wizard.core.model.Configurations; import java.util.List; @@ -15,7 +18,9 @@ import java.util.regex.Pattern; public class CLI { public static void main(String[] args) { - Game game = new Game(Configurations.DEFAULT, (player, msg) -> System.out.println(msg)); + Configuration config = Configurations.DEFAULT.withTimeout(0); + Observer observer = (player, msg) -> System.out.println(msg); + Game game = new Game(config, observer); var players = List.of( UUID.randomUUID(), UUID.randomUUID(), @@ -25,13 +30,15 @@ public class CLI { game.start(players); + GameState state = null; + Scanner scanner = new Scanner(System.in); - Pattern pattern = Pattern.compile("(\\d) (predict|play|trump) (.*)"); + Pattern pattern = Pattern.compile("(\\d) ([a-z]+) (.*)"); while (scanner.hasNextLine()) { try { Matcher matcher = pattern.matcher(scanner.nextLine()); if (!matcher.find()) { - System.err.println("Format is \"(\\\\d) (predict|play|trump) (.*)\""); + System.err.println("Format is \"(\\\\d) ([a-z]+) (.*)\""); continue; } String player = matcher.group(1); diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Context.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Context.java index e333aed..90617a8 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Context.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Context.java @@ -8,15 +8,15 @@ import java.util.concurrent.*; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -public abstract class Context { +public abstract class Context, C extends Context> { protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - protected T state; + protected S state; protected final ReentrantLock lock = new ReentrantLock(); private final Condition finishCondition = lock.newCondition(); private boolean finished; private Throwable exception; - protected void start(@NotNull T state) { + protected void start(@NotNull S state) { lock.lock(); try { if (finished) throw new IllegalStateException("Context has already finished."); @@ -26,14 +26,16 @@ public abstract class Context { } } - public void transition(T currentState, T newState) { + public void transition(S currentState, S newState) { lock.lock(); try { if (state == currentState) { state = newState; - if (currentState != null) currentState.onExit(); + if (currentState != null) //noinspection unchecked + currentState.onExit((C) this); onTransition(currentState, newState); - if (newState != null) newState.onEnter(); + if (newState != null) //noinspection unchecked + newState.onEnter((C) this); } else { throw new IllegalStateException("Current state does not match."); } @@ -65,6 +67,11 @@ public abstract class Context { finish(new CancellationException()); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean isDone() { + return finished; + } + @Blocking public void await() throws InterruptedException, ExecutionException, CancellationException { lock.lock(); @@ -84,12 +91,13 @@ public abstract class Context { } } - public void timeout(@NotNull T currentState, long delay) { + public void timeout(@NotNull S currentState, long delay) { scheduler.schedule(() -> { lock.lock(); try { if (state == currentState) { - state.onTimeout(); + //noinspection unchecked + state.onTimeout((C) this); } } catch (Throwable t) { handleError(t); @@ -100,8 +108,16 @@ public abstract class Context { } protected void handleError(Throwable t) { - finish(t); + lock.lock(); + try { + if (!isDone()) { + finish(t); + t.printStackTrace(); + } + } finally { + lock.unlock(); + } } - protected void onTransition(T from, T to) {} + protected void onTransition(S from, S to) {} } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Game.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Game.java index f60f2a3..33c04c6 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Game.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/Game.java @@ -1,5 +1,6 @@ package eu.jonahbauer.wizard.core.machine; +import eu.jonahbauer.wizard.core.machine.states.GameData; import eu.jonahbauer.wizard.core.machine.states.GameState; import eu.jonahbauer.wizard.core.machine.states.game.Starting; import eu.jonahbauer.wizard.core.messages.Observer; @@ -12,7 +13,9 @@ import lombok.Getter; import java.util.List; import java.util.UUID; -public final class Game extends Context { +import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS; + +public final class Game extends Context { @Getter private final Configuration config; private final Observer observer; @@ -23,17 +26,26 @@ public final class Game extends Context { } public void start(List players) { - start(new Starting(this, GameData.builder().players(players).build())); + start(new Starting(new GameData().with(PLAYERS, List.copyOf(players)))); } public void resume(GameState state) { start(state); } + public GameState stop() { + GameState state = this.state; + if (state != null) { + finish(); + return state; + } + return null; + } + public void onMessage(UUID player, PlayerMessage message) { lock.lock(); try { - state.onMessage(player, message); + state.onMessage(this, player, message); } catch (IllegalStateException | IllegalArgumentException e) { throw e; } catch (Throwable t) { diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/GameData.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/GameData.java deleted file mode 100644 index f4ddea3..0000000 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/GameData.java +++ /dev/null @@ -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 players; - private final @Unmodifiable Map score; - - private final Integer round; - private final @Unmodifiable Map> hands; - private final Optional trumpCard; - private final Card.Suit trumpSuit; - private final @Unmodifiable Map predictions; - private final @Unmodifiable Map tricks; - - private final Integer trick; - private final @Unmodifiable List> 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 players, - @Unmodifiable Map score, - Integer round, - @Unmodifiable Map> hands, - Optional trumpCard, - Card.Suit trumpSuit, - @Unmodifiable Map predictions, - @Unmodifiable Map tricks, - Integer trick, - @Unmodifiable List> 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; - } - - // - public List 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> 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> getStack() { - if (stack == null) throw new UnsupportedOperationException(); - return stack; - } - - public Map getPredictions() { - if (predictions == null) throw new UnsupportedOperationException(); - return predictions; - } - - public Map getTricks() { - if (tricks == null) throw new UnsupportedOperationException(); - return tricks; - } - - public Map 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(); - } - // - - // - 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(); - } - // - - // - public GameData withPrediction(UUID player, int prediction) { - Map 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 hand = new ArrayList<>(getHands().get(player)); - hand.remove(card); - Map> hands = new HashMap<>(getHands()); - hands.put(player, hand); - List> stack = new ArrayList<>(getStack()); - stack.add(Pair.of(player, card)); - return withStack(stack).withHands(hands); - } - // - - 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 getPlayers(); - @NotNull Map getScore(); - - int getRound(); - @NotNull Map> getHands(); - @Nullable Card getTrumpCard(); - @NotNull Card.Suit getTrumpSuit(); - @NotNull Map getPredictions(); - @NotNull Map getTricks(); - - int getTrick(); - @NotNull List> getStack(); - - @NotNull UUID getCurrentPlayer(); - @NotNull UUID getPlayer(int index); - int getPlayerCount(); - } -} diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameData.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameData.java new file mode 100644 index 0000000..c777951 --- /dev/null +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameData.java @@ -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> PLAYERS = new Key<>("players", 0); + public static final Key> SCORE = new Key<>("score", 1); + public static final Key ROUND = new Key<>("round", 2); + public static final Key>> HANDS = new Key<>("hands", 3); + public static final Key TRUMP_CARD = new Key<>("trumpCard", 4); + public static final Key TRUMP_SUIT = new Key<>("trumpSuit", 5); + public static final Key> PREDICTIONS = new Key<>("predictions", 6); + public static final Key> TRICKS = new Key<>("tricks", 7); + public static final Key TRICK = new Key<>("trick", 8); + public static final Key>> STACK = new Key<>("stack", 9); + public static final Key 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 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 get(@NotNull Key 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 the value type + * @return the newly generated map or {@code this} is no changes had to be made + */ + public GameData with(@NotNull Key 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 GameData with(@NotNull Key key1, T1 value1, Key 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 GameData with(@NotNull Key key1, T1 value1, Key key2, T2 value2, Key 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 key type + * @param 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 GameData requireEach(Key> list, Key> 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 { + String name; + int index; + + @Override + public String toString() { + return name(); + } + } +} diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameState.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameState.java index 32ba9ac..2d3c9bd 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameState.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/GameState.java @@ -1,40 +1,37 @@ package eu.jonahbauer.wizard.core.machine.states; import eu.jonahbauer.wizard.core.machine.Game; -import eu.jonahbauer.wizard.core.machine.GameData; import eu.jonahbauer.wizard.core.messages.player.PlayerMessage; import lombok.Getter; -import lombok.experimental.Delegate; import org.jetbrains.annotations.Unmodifiable; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.CURRENT_PLAYER; +import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS; + @Unmodifiable -public abstract class GameState implements State { - @Getter - private final Game game; +public abstract class GameState implements State { @Getter - @Delegate(types = GameData.Getters.class) private final GameData data; - public GameState(Game game, GameData data) { - this.game = game; - this.data = data.requirePlayers().onlyRequired(); + public GameState(GameData data) { + this.data = data.require(PLAYERS).clean(); } - protected final void transition(GameState state) { - getGame().transition(this, state); + protected final void transition(Game game, GameState state) { + game.transition(this, state); } - protected final void timeout() { - getGame().timeout(this, getTimeout(false)); + protected final void timeout(Game game) { + game.timeout(this, getTimeout(game, false)); } - protected final long getTimeout(boolean absolute) { - return (absolute ? System.currentTimeMillis() : 0) + getGame().getConfig().timeout(); + protected final long getTimeout(Game game, boolean absolute) { + return (absolute ? System.currentTimeMillis() : 0) + game.getConfig().timeout(); } - public void onMessage(UUID player, PlayerMessage message) { + public void onMessage(Game game, UUID player, PlayerMessage message) { throw new IllegalStateException("You cannot do that right now."); } @@ -42,4 +39,33 @@ public abstract class GameState implements State { public String toString() { return getClass().getSimpleName(); } + + public T get(GameData.Key key) { + return getData().get(key); + } + + public int getPlayerCount() { + return get(PLAYERS).size(); + } + + public UUID getPlayer(int index) { + return get(PLAYERS).get(index); + } + + public UUID getNextPlayer() { + return getNextPlayer(get(CURRENT_PLAYER)); + } + + public UUID getNextPlayer(UUID player) { + var players = get(PLAYERS); + var index = players.indexOf(player); + if (index == -1) { + throw new AssertionError("Player is not a valid player."); + } else if (index == players.size() - 1) { + return players.get(0); + } else { + return players.get(index + 1); + } + + } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/InvalidDataException.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/InvalidDataException.java new file mode 100644 index 0000000..8eb034c --- /dev/null +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/InvalidDataException.java @@ -0,0 +1,7 @@ +package eu.jonahbauer.wizard.core.machine.states; + +public class InvalidDataException extends RuntimeException { + public InvalidDataException(String message) { + super(message); + } +} diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/State.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/State.java index 88f8801..92eadae 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/State.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/State.java @@ -1,7 +1,9 @@ package eu.jonahbauer.wizard.core.machine.states; -public interface State { - default void onEnter() {} - default void onTimeout() {} - default void onExit() {} +import eu.jonahbauer.wizard.core.machine.Context; + +public interface State, C extends Context> { + default void onEnter(C context) {} + default void onTimeout(C context) {} + default void onExit(C context) {} } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Finishing.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Finishing.java index 212ef10..f6ceb60 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Finishing.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Finishing.java @@ -1,19 +1,21 @@ 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.GameData; import eu.jonahbauer.wizard.core.machine.states.GameState; import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; + public final class Finishing extends GameState { - public Finishing(Game game, GameData data) { - super(game, data.requireScore()); + public Finishing(GameData data) { + super(data.require(SCORE)); } @Override - public void onEnter() { - getGame().notify(new ScoreMessage(getScore())); - getGame().finish(); + public void onEnter(Game game) { + game.notify(new ScoreMessage(get(SCORE))); + game.finish(); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Starting.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Starting.java index 40b953f..9b2dc1c 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Starting.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/game/Starting.java @@ -1,20 +1,25 @@ 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.GameData; import eu.jonahbauer.wizard.core.machine.states.GameState; 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 Starting(Game game, GameData data) { - super(game, data); + public Starting(GameData data) { + super(data); } @Override - public void onEnter() { - transition(new StartingRound(getGame(), getData().withRound(0).withScore(Collections.emptyMap()))); + public void onEnter(Game game) { + transition(game, new StartingRound(getData().with( + ROUND, 0, + SCORE, Map.of() + ))); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Dealing.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Dealing.java index 20f5c7e..acb2c59 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Dealing.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Dealing.java @@ -1,50 +1,46 @@ 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.GameData; import eu.jonahbauer.wizard.core.messages.observer.HandMessage; import eu.jonahbauer.wizard.core.model.Card; import eu.jonahbauer.wizard.core.model.Deck; import java.util.*; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; + public final class Dealing extends RoundState { - private transient Map> hands; - public Dealing(Game game, GameData data) { - super(game, data); + public Dealing(GameData data) { + super(data); } @Override - public void onEnter() { - Deck deck = new Deck(getGame().getConfig().cards()); + public void onEnter(Game game) { + Deck deck = new Deck(game.getConfig().cards()); deck.shuffle(); - hands = new HashMap<>(); + var hands = new HashMap>(); - int dealer = getRound(); + int round = get(ROUND); int playerCount = getPlayerCount(); - int cardCount = getRound() + 1; + int cardCount = round + 1; for (int i = 1; i <= playerCount; i++) { - int player = (dealer + i) % playerCount; + int player = (round + i) % playerCount; hands.put(getPlayer(player), deck.draw(cardCount)); } - Optional trumpCard = Optional.ofNullable(deck.draw()); + for (UUID player : get(PLAYERS)) { + game.notify(player, new HandMessage(player, hands.get(player))); + } - transition(new DeterminingTrump( - getGame(), - getData().withHands(hands) - .withTrumpCard(trumpCard) + Card trumpCard = deck.draw(); + transition(game, new DeterminingTrump( + getData().with( + 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))); - } - } - } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/DeterminingTrump.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/DeterminingTrump.java index 862306b..2d61e89 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/DeterminingTrump.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/DeterminingTrump.java @@ -1,87 +1,71 @@ 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.GameData; import eu.jonahbauer.wizard.core.messages.observer.TrumpMessage; import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage; import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage; import eu.jonahbauer.wizard.core.messages.player.PlayerMessage; import eu.jonahbauer.wizard.core.model.Card; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.VisibleForTesting; import java.util.Arrays; -import java.util.Collections; +import java.util.Map; 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; -/** - * - */ public final class DeterminingTrump extends RoundState { - private transient Card.Suit trumpSuit; - - public DeterminingTrump(Game game, GameData data) { - super(game, data.requireAllHands().requireTrumpCard()); + public DeterminingTrump(GameData data) { + super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS)); } @Override - public void onEnter() { - Card trumpCard = getTrumpCard(); + public void onEnter(Game game) { + Card trumpCard = getData().get(TRUMP_CARD); Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE; if (trumpSuit == null) { - getGame().notify(new TrumpMessage(getTrumpCard(), null)); - getGame().notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(true))); - timeout(); + game.notify(new TrumpMessage(trumpCard, null)); + game.notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(game, true))); + timeout(game); } else { - this.trumpSuit = trumpSuit; - transition(); + transition(game, trumpSuit); } } @Override - public void onTimeout() { - 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(); - } - - @Override - public void onExit() { - if (trumpSuit != null) { - getGame().notify(new TrumpMessage(getTrumpCard(), trumpSuit)); - } + public void onTimeout(Game game) { + Card.Suit[] suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW}; + transition(game, suits[(int) (Math.random() * suits.length)]); } @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) { checkTrumpSuit(trumpMessage.getTrumpSuit()); - this.trumpSuit = trumpMessage.getTrumpSuit(); - transition(); + transition(game, trumpMessage.getTrumpSuit()); } else { - super.onMessage(player, message); + super.onMessage(game, player, message); } } @VisibleForTesting 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) { if (s == suit) return; } throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + "."); } - private void transition() { - if (trumpSuit == null) throw new AssertionError(); - - transition(new Predicting( - getGame(), - getData().withTrumpSuit(trumpSuit) - .withPredictions(Collections.emptyMap()) - .withCurrentPlayer(getDealer()) - .withNextPlayer() - )); + private void transition(Game game, @NotNull Card.Suit trumpSuit) { + game.notify(new TrumpMessage(getData().get(TRUMP_CARD), trumpSuit)); + transition(game, new Predicting(getData().with( + TRUMP_SUIT, trumpSuit, + PREDICTIONS, Map.of(), + CURRENT_PLAYER, getNextPlayer(getDealer()) + ))); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRound.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRound.java index 85f75a8..49b347e 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRound.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRound.java @@ -1,7 +1,8 @@ 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.GameData; +import eu.jonahbauer.wizard.core.machine.states.InvalidDataException; import eu.jonahbauer.wizard.core.machine.states.game.Finishing; import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage; import org.jetbrains.annotations.VisibleForTesting; @@ -10,47 +11,38 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -public final class FinishingRound extends RoundState { - private transient Map points; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; - public FinishingRound(Game game, GameData data) { - super(game, data.requireAllPredictions().requireTricks().requireScore()); +public final class FinishingRound extends RoundState { - int tricks = data.getTricks().values().stream().mapToInt(i -> i).sum(); - if (tricks != data.getRound() + 1) { - throw new AssertionError("Unexpected number of tricks in round " + data.getRound() + ": " + tricks + "."); - } + public FinishingRound(GameData data) { + super(data.requireEach(PLAYERS, PREDICTIONS).require(TRICKS, SCORE)); + checkData(data); } @Override - public void onEnter() { - points = getPoints(); + public void onEnter(Game game) { + var points = getPoints(); + game.notify(new ScoreMessage(points)); - var score = new HashMap<>(getScore()); - points.forEach((uuid, p) -> score.compute(uuid, (u, total) -> total == null ? p : total + p)); + var score = new HashMap<>(get(SCORE)); + points.forEach((uuid, amount) -> score.merge(uuid, amount, Integer::sum)); - GameData data = getData().withScore(score); + GameData data = getData().with(SCORE, Map.copyOf(score)); - if (60 / getPlayerCount() == getRound() + 1) { - transition(new Finishing(getGame(), data)); + if (60 / getPlayerCount() == get(ROUND) + 1) { + transition(game, new Finishing(data)); } else { - transition(new StartingRound(getGame(), data.withNextRound())); - } - } - - @Override - public void onExit() { - if (points != null) { - getGame().notify(new ScoreMessage(points)); + transition(game, new StartingRound(data.with(ROUND, get(ROUND) + 1))); } } @VisibleForTesting Map getPoints() { var points = new HashMap(); - for (UUID player : getPlayers()) { - int prediction = getPredictions().get(player); - int tricks = getTricks().getOrDefault(player, 0); + for (UUID player : get(PLAYERS)) { + int prediction = get(PREDICTIONS).get(player); + int tricks = get(TRICKS).getOrDefault(player, 0); if (tricks == prediction) { points.put(player, 20 + 10 * tricks); @@ -60,4 +52,12 @@ public final class FinishingRound extends RoundState { } return points; } + + private static void checkData(GameData data) { + // the number of tricks played should be equal to the number of tricks in total + int tricks = data.get(TRICKS).values().stream().mapToInt(i -> i).sum(); + if (tricks != data.get(ROUND) + 1) { + throw new InvalidDataException("Unexpected number of tricks in round " + data.get(ROUND) + ": " + tricks + "."); + } + } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Predicting.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Predicting.java index 8e606f5..2142031 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Predicting.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/Predicting.java @@ -1,7 +1,7 @@ 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.GameData; import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick; import eu.jonahbauer.wizard.core.messages.observer.PredictionMessage; import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage; @@ -10,36 +10,34 @@ import eu.jonahbauer.wizard.core.messages.player.PredictMessage; import lombok.Getter; import org.jetbrains.annotations.VisibleForTesting; -import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.MAKE_PREDICTION; @Getter public final class Predicting extends RoundState { - private transient Integer prediction; - - public Predicting(Game game, GameData data) { - super(game, data.requireAllHands().requirePredictions().requireTrumpSuit().requireCurrentPlayer()); + public Predicting(GameData data) { + super(data.requireEach(PLAYERS, HANDS).require(PREDICTIONS, TRUMP_SUIT, CURRENT_PLAYER)); } @Override - public void onEnter() { - getGame().notify(new UserInputMessage(getCurrentPlayer(), MAKE_PREDICTION, getTimeout(true))); - timeout(); + public void onEnter(Game game) { + game.notify(new UserInputMessage(getData().get(CURRENT_PLAYER), MAKE_PREDICTION, getTimeout(game, true))); + timeout(game); } @Override - public void onTimeout() { + public void onTimeout(Game game) { try { - checkPrediction(0); - this.prediction = 0; - transition(); + checkPrediction(game, 0); + transition(game, 0); } catch (IllegalArgumentException e) { try { - checkPrediction(1); - this.prediction = 1; - transition(); + checkPrediction(game, 1); + transition(game, 1); } catch (IllegalArgumentException e2) { throw new AssertionError(e2); } @@ -47,57 +45,54 @@ public final class Predicting extends RoundState { } @Override - public void onExit() { - if (prediction != null) { - getGame().notify(new PredictionMessage(getCurrentPlayer(), prediction)); - } - } - - @Override - public void onMessage(UUID player, PlayerMessage message) { - if (getCurrentPlayer().equals(player) && message instanceof PredictMessage predictMessage) { - checkPrediction(predictMessage.getPrediction()); - this.prediction = predictMessage.getPrediction(); - transition(); + public void onMessage(Game game, UUID player, PlayerMessage message) { + if (getData().get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) { + checkPrediction(game, predictMessage.getPrediction()); + transition(game, predictMessage.getPrediction()); } else { - super.onMessage(player, message); + super.onMessage(game, player, message); } } @VisibleForTesting - void checkPrediction(int prediction) { - if (prediction < 0 || prediction > getRound() + 1) { - throw new IllegalArgumentException("Prediction must be between 0 and " + (getRound() + 1) + "."); + void checkPrediction(Game game, int prediction) { + int round = get(ROUND); + if (prediction < 0 || prediction > round + 1) { + throw new IllegalArgumentException("Prediction must be between 0 and " + (round + 1) + "."); } - if (!getGame().getConfig().allowExactPredictions() && isLastPlayer()) { - int sum = getPredictions().values().stream().mapToInt(i -> i).sum(); - if (sum + prediction == getRound() + 1) { + if (!game.getConfig().allowExactPredictions() && isLastPlayer()) { + int sum = get(PREDICTIONS).values().stream().mapToInt(i -> i).sum(); + if (sum + prediction == round + 1) { throw new IllegalArgumentException("Predictions must not add up."); } } } - private void transition() { - if (prediction == null) throw new AssertionError(); + private void transition(Game game, int prediction) { + game.notify(new PredictionMessage(get(CURRENT_PLAYER), prediction)); + + // add prediction + var predictions = new HashMap<>(get(PREDICTIONS)); + predictions.put(get(CURRENT_PLAYER), prediction); - GameData data = getData().withPrediction(getCurrentPlayer(), prediction) - .withNextPlayer(); + GameData data = getData().with( + PREDICTIONS, Map.copyOf(predictions), + CURRENT_PLAYER, getNextPlayer() + ); if (isLastPlayer()) { - transition(new StartingTrick( - getGame(), - data.withTrick(0).withTricks(Collections.emptyMap()) - )); + data = data.with( + TRICK, 0, + TRICKS, Map.of() + ); + transition(game, new StartingTrick(data)); } else { - transition(new Predicting( - getGame(), - data - )); + transition(game, new Predicting(data)); } } private boolean isLastPlayer() { - return getDealer().equals(getCurrentPlayer()); + return getDealer().equals(get(CURRENT_PLAYER)); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/RoundState.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/RoundState.java index b9a5321..88588a8 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/RoundState.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/RoundState.java @@ -1,17 +1,18 @@ package eu.jonahbauer.wizard.core.machine.states.round; -import eu.jonahbauer.wizard.core.machine.Game; -import eu.jonahbauer.wizard.core.machine.GameData; +import eu.jonahbauer.wizard.core.machine.states.GameData; import eu.jonahbauer.wizard.core.machine.states.GameState; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; + public abstract class RoundState extends GameState { - public RoundState(Game game, GameData data) { - super(game, data.requireRound().requireScore()); + public RoundState(GameData data) { + super(data.require(ROUND, SCORE)); } protected UUID getDealer() { - return getPlayer(getRound() % getPlayerCount()); + return getPlayer(getData().get(ROUND) % getPlayerCount()); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/StartingRound.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/StartingRound.java index 9c36b2e..d8242ae 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/StartingRound.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/round/StartingRound.java @@ -1,15 +1,15 @@ 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.GameData; public final class StartingRound extends RoundState { - public StartingRound(Game game, GameData data) { - super(game, data); + public StartingRound(GameData data) { + super(data); } @Override - public void onEnter() { - transition(new Dealing(getGame(), getData())); + public void onEnter(Game game) { + transition(game, new Dealing(getData())); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrick.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrick.java index f9e7ef3..231e3c2 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrick.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrick.java @@ -1,55 +1,48 @@ 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.GameData; +import eu.jonahbauer.wizard.core.machine.states.InvalidDataException; import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound; import eu.jonahbauer.wizard.core.messages.observer.TrickMessage; import eu.jonahbauer.wizard.core.model.Card; import eu.jonahbauer.wizard.core.util.Pair; import org.jetbrains.annotations.VisibleForTesting; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; +import java.util.*; + +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; public final class FinishingTrick extends TrickState { - private transient UUID winner; - public FinishingTrick(Game game, GameData data) { - super(game, data.requireStack()); - assert assertions(); + public FinishingTrick(GameData data) { + super(data.require(STACK)); + checkData(data); } @Override - public void onEnter() { - this.winner = getWinner(); + public void onEnter(Game game) { + var winner = getWinner(); + game.notify(new TrickMessage(winner, get(STACK).stream().map(Pair::second).toList())); - var tricks = new HashMap<>(getTricks()); - tricks.compute(winner, (k, t) -> t == null ? 1 : t + 1); - GameData data = getData().withTricks(tricks); + var tricks = new HashMap<>(get(TRICKS)); + tricks.merge(winner, 1, Integer::sum); - if (getTrick() < getRound()) { - transition(new StartingTrick( - getGame(), - data.withCurrentPlayer(winner) - .withNextTrick() - )); - } else { - transition(new FinishingRound(getGame(), data)); - } - } + GameData data = getData().with(TRICKS, Map.copyOf(tricks)); - @Override - public void onExit() { - if (winner != null) { - getGame().notify(new TrickMessage(winner, getStack().stream().map(Pair::second).toList())); + if (get(TRICK) < get(ROUND)) { + transition(game, new StartingTrick(data.with( + CURRENT_PLAYER, winner, + TRICK, get(TRICK) + 1 + ))); + } else { + transition(game, new FinishingRound(data)); } } @VisibleForTesting UUID getWinner() { - var wizard = getStack().stream() + var wizard = get(STACK).stream() .filter(pair -> pair.second().getSuit() == Card.Suit.WIZARD) .findFirst() .orElse(null); @@ -57,16 +50,15 @@ public final class FinishingTrick extends TrickState { return wizard.first(); } - if (getStack().stream().allMatch(pair -> pair.second().getSuit() == Card.Suit.JESTER)) { - return getStack().get(0).first(); + if (get(STACK).stream().allMatch(pair -> pair.second().getSuit() == Card.Suit.JESTER)) { + return get(STACK).get(0).first(); } - var trumpSuit = getTrumpSuit(); + var trumpSuit = get(TRUMP_SUIT); var suit = getTrickSuit(); - return getStack().stream() + return get(STACK).stream() .max( - Comparator.>comparingInt(pair -> pair.second() - .getSuit() == trumpSuit ? 1 : 0) + Comparator.>comparingInt(pair -> pair.second().getSuit() == trumpSuit ? 1 : 0) .thenComparing(pair -> pair.second().getSuit() == suit ? 1 : 0) .thenComparing(pair -> pair.second().getValue()) ) @@ -74,16 +66,20 @@ public final class FinishingTrick extends TrickState { .first(); } - private boolean assertions() { - if (getStack().size() != getPlayerCount()) { - throw new AssertionError("Unexpected stack size: " + getStack().size() + " (expected " + getPlayerCount() + ")."); + private static void checkData(GameData data) { + // every player has played exactly one card + int stackSize = data.get(STACK).size(); + int playerCount = data.get(PLAYERS).size(); + if (stackSize != playerCount) { + throw new InvalidDataException("Unexpected stack size: " + stackSize + " (expected " + playerCount + ")."); } - int expectedSize = getRound() - getTrick(); - for (List hand : getHands().values()) { + + // every player has round - trick cards left on his hand + int expectedSize = data.get(ROUND) - data.get(TRICK); + for (List hand : data.get(HANDS).values()) { if (hand.size() != expectedSize) { throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + ")."); } } - return true; } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCard.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCard.java index 5df0abb..9d2b738 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCard.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCard.java @@ -1,49 +1,49 @@ 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.GameData; +import eu.jonahbauer.wizard.core.machine.states.InvalidDataException; import eu.jonahbauer.wizard.core.messages.observer.CardMessage; import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage; import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage; import eu.jonahbauer.wizard.core.messages.player.PlayerMessage; import eu.jonahbauer.wizard.core.model.Card; +import eu.jonahbauer.wizard.core.util.Pair; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.VisibleForTesting; -import java.util.Collection; -import java.util.List; -import java.util.UUID; +import java.util.*; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PLAY_CARD; public final class PlayingCard extends TrickState { - private transient Card card; - public PlayingCard(Game game, GameData data) { - super(game, data.requireStack()); - assert assertions(); + public PlayingCard(GameData data) { + super(data.require(STACK)); + checkData(data); } @Override - public void onEnter() { - getGame().notify(new UserInputMessage(getCurrentPlayer(), PLAY_CARD, getTimeout(true))); - timeout(); + public void onEnter(Game game) { + game.notify(new UserInputMessage(get(CURRENT_PLAYER), PLAY_CARD, getTimeout(game, true))); + timeout(game); } @Override - public void onMessage(UUID player, PlayerMessage message) { - if (getCurrentPlayer().equals(player) && message instanceof PlayCardMessage cardMessage) { + public void onMessage(Game game, UUID player, PlayerMessage message) { + if (get(CURRENT_PLAYER).equals(player) && message instanceof PlayCardMessage cardMessage) { checkCard(cardMessage.getCard()); - this.card = cardMessage.getCard(); - transition(); + transition(game, cardMessage.getCard()); } else { - super.onMessage(player, message); + super.onMessage(game, player, message); } } @Override - public void onTimeout() { - var hand = getHands().get(getCurrentPlayer()); - this.card = hand.stream().filter(c -> { + public void onTimeout(Game game) { + var hand = get(HANDS).get(get(CURRENT_PLAYER)); + var card = hand.stream().filter(c -> { try { checkCard(c); return true; @@ -51,19 +51,12 @@ public final class PlayingCard extends TrickState { return false; } }).findAny().orElseThrow(() -> new AssertionError("Cannot play any card.")); - transition(); - } - - @Override - public void onExit() { - if (card != null) { - getGame().notify(new CardMessage(getCurrentPlayer(), card)); - } + transition(game, card); } @VisibleForTesting void checkCard(Card card) { - var hand = getHands().get(getCurrentPlayer()); + var hand = get(HANDS).get(get(CURRENT_PLAYER)); if (!hand.contains(card)) { throw new IllegalArgumentException("You do not have this card on your hand."); } @@ -76,34 +69,51 @@ public final class PlayingCard extends TrickState { } } - private void transition() { - if (card == null) throw new AssertionError(); - - GameData data = getData().withCardPlayed(getCurrentPlayer(), card); - - var summary = data.getHands().values().stream() + private void transition(Game game, @NotNull Card card) { + var currentPlayer = get(CURRENT_PLAYER); + game.notify(new CardMessage(currentPlayer, card)); + + // add card to stack + var stack = get(STACK); + var mutableStack = new ArrayList>(stack.size() + 1); + mutableStack.addAll(stack); + mutableStack.add(Pair.of(currentPlayer, card)); + + // remove card from hand + var hands = get(HANDS); + var mutableHands = new HashMap<>(hands); + var hand = hands.get(currentPlayer); + var mutableHand = new ArrayList<>(hand); + mutableHand.remove(card); + mutableHands.put(currentPlayer, List.copyOf(mutableHand)); + + GameData data = getData().with( + STACK, List.copyOf(mutableStack), + HANDS, Map.copyOf(mutableHands) + ); + + var summary = data.get(HANDS).values().stream() .mapToInt(Collection::size) .summaryStatistics(); - if (summary.getMax() == summary.getMin()) { - transition(new FinishingTrick(getGame(), data)); + if (summary.getMax() == summary.getMin()) { // everybody has the same amount of cards + transition(game, new FinishingTrick(data)); } else { - transition(new PlayingCard(getGame(), data.withNextPlayer())); + transition(game, new PlayingCard(data.with(CURRENT_PLAYER, getNextPlayer()))); } } - private boolean assertions() { - int expectedSize = getRound() - getTrick(); - for (List hand : getHands().values()) { + private static void checkData(GameData data) { + int expectedSize = data.get(ROUND) - data.get(TRICK); + for (List hand : data.get(HANDS).values()) { if (hand.size() != expectedSize && hand.size() != expectedSize + 1) { - throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + " or " + (expectedSize + 1) + ")."); + throw new InvalidDataException("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + " or " + (expectedSize + 1) + ")."); } } - int size = getHands().get(getCurrentPlayer()).size(); + int size = data.get(HANDS).get(data.get(CURRENT_PLAYER)).size(); if (size != expectedSize + 1) { - throw new AssertionError("Unexpected current player's hand size: " + size + " (expected " + (expectedSize + 1) + ")"); + throw new InvalidDataException("Unexpected current player's hand size: " + size + " (expected " + (expectedSize + 1) + ")"); } - return true; } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/StartingTrick.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/StartingTrick.java index 671f096..50923b2 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/StartingTrick.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/StartingTrick.java @@ -1,20 +1,19 @@ 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.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 StartingTrick(Game game, GameData data) { - super(game, data); + public StartingTrick(GameData data) { + super(data); } @Override - public void onEnter() { - transition(new PlayingCard( - getGame(), - getData().withStack(Collections.emptyList()) - )); + public void onEnter(Game game) { + transition(game, new PlayingCard(getData().with(STACK, List.of()))); } } diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickState.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickState.java index cb330b8..eddc5c1 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickState.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickState.java @@ -1,25 +1,22 @@ package eu.jonahbauer.wizard.core.machine.states.trick; -import eu.jonahbauer.wizard.core.machine.Game; -import eu.jonahbauer.wizard.core.machine.GameData; +import eu.jonahbauer.wizard.core.machine.states.GameData; import eu.jonahbauer.wizard.core.machine.states.round.RoundState; import eu.jonahbauer.wizard.core.model.Card; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; + public abstract class TrickState extends RoundState { - public TrickState(Game game, GameData data) { + public TrickState(GameData data) { super( - game, - data.requireAllHands() - .requireAllPredictions() - .requireTrumpSuit() - .requireTrick() - .requireTricks() - .requireCurrentPlayer() + data.requireEach(PLAYERS, HANDS) + .requireEach(PLAYERS, PREDICTIONS) + .require(TRUMP_SUIT, TRICK, TRICKS, CURRENT_PLAYER) ); } protected Card.Suit getTrickSuit() { - for (var pair : getStack()) { + for (var pair : get(STACK)) { Card.Suit suit = pair.second().getSuit(); if (suit == Card.Suit.WIZARD) { return Card.Suit.NONE; diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/Pair.java b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/Pair.java index d723f47..4d2cd48 100644 --- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/Pair.java +++ b/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/Pair.java @@ -1,7 +1,24 @@ package eu.jonahbauer.wizard.core.util; -public record Pair(F first, S second) { +import java.util.Map; + +public record Pair(F first, S second) implements Map.Entry { public static Pair of(F first, S 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(); + } } diff --git a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRoundTest.java b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRoundTest.java index 028027d..fa2b354 100644 --- a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRoundTest.java +++ b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/FinishingRoundTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -37,9 +38,9 @@ public class FinishingRoundTest { FinishingRound state = mock(FinishingRound.class); when(state.getPoints()).thenCallRealMethod(); - when(state.getPredictions()).thenReturn(predictions); - when(state.getTricks()).thenReturn(tricks); - when(state.getPlayers()).thenReturn(players); + when(state.get(PREDICTIONS)).thenReturn(predictions); + when(state.get(TRICKS)).thenReturn(tricks); + when(state.get(PLAYERS)).thenReturn(players); assertEquals(Map.of( player0, 20, diff --git a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/PredictingTest.java b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/PredictingTest.java index 7ab3021..0e350ed 100644 --- a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/PredictingTest.java +++ b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/round/PredictingTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.util.Map; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @@ -22,18 +23,17 @@ public class PredictingTest { when(game.getConfig()).thenReturn(config); Predicting state = mock(Predicting.class); - doCallRealMethod().when(state).checkPrediction(anyInt()); - when(state.getGame()).thenReturn(game); - when(state.getPredictions()).thenReturn(predictions); + doCallRealMethod().when(state).checkPrediction(any(), anyInt()); + when(state.get(PREDICTIONS)).thenReturn(predictions); when(state.getDealer()).thenReturn(new UUID(0,0)); - when(state.getCurrentPlayer()).thenReturn(new UUID(0,1)); - when(state.getRound()).thenReturn(10); + when(state.get(CURRENT_PLAYER)).thenReturn(new UUID(0,1)); + when(state.get(ROUND)).thenReturn(10); - assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(-1)); - assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(12)); + assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, -1)); + assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 12)); for (int i = 0; i < 12; i++) { - state.checkPrediction(i); + state.checkPrediction(game, i); } } @@ -49,18 +49,17 @@ public class PredictingTest { when(game.getConfig()).thenReturn(config); Predicting state = mock(Predicting.class); - doCallRealMethod().when(state).checkPrediction(anyInt()); - when(state.getGame()).thenReturn(game); - when(state.getPredictions()).thenReturn(predictions); + doCallRealMethod().when(state).checkPrediction(any(), anyInt()); + when(state.get(PREDICTIONS)).thenReturn(predictions); when(state.getDealer()).thenReturn(player); - when(state.getCurrentPlayer()).thenReturn(player); - when(state.getRound()).thenReturn(10); + when(state.get(CURRENT_PLAYER)).thenReturn(player); + when(state.get(ROUND)).thenReturn(10); - assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(1)); + assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 1)); for (int i = 0; i < 12; i++) { if (i == 1) continue; - state.checkPrediction(i); + state.checkPrediction(game, i); } } } diff --git a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrickTest.java b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrickTest.java index 8ed88e6..2937513 100644 --- a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrickTest.java +++ b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/FinishingTrickTest.java @@ -1,6 +1,6 @@ package eu.jonahbauer.wizard.core.machine.states.trick; -import eu.jonahbauer.wizard.core.machine.states.trick.FinishingTrick; +import eu.jonahbauer.wizard.core.machine.states.GameData; import eu.jonahbauer.wizard.core.model.Card; import eu.jonahbauer.wizard.core.util.Pair; import org.junit.jupiter.api.Test; @@ -8,11 +8,23 @@ import org.junit.jupiter.api.Test; import java.util.List; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.STACK; +import static eu.jonahbauer.wizard.core.machine.states.GameData.TRUMP_SUIT; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class FinishingTrickTest { + private void performTest(List> stack, Card.Suit trumpSuit, Card.Suit trickSuit) { + FinishingTrick state = mock(FinishingTrick.class); + when(state.getWinner()).thenCallRealMethod(); + when(state.get(STACK)).thenReturn(stack); + when(state.get(TRUMP_SUIT)).thenReturn(trumpSuit); + when(state.getTrickSuit()).thenReturn(trickSuit); + + assertNotNull(state.getWinner()); + } + @Test public void getWinner_ReturnsFirstWizard() { List> stack = List.of( @@ -23,13 +35,7 @@ public class FinishingTrickTest { Pair.of(null, Card.GREEN_11) ); - FinishingTrick state = mock(FinishingTrick.class); - when(state.getWinner()).thenCallRealMethod(); - when(state.getStack()).thenReturn(stack); - when(state.getTrickSuit()).thenReturn(Card.Suit.RED); - when(state.getTrumpSuit()).thenReturn(Card.Suit.YELLOW); - - assertNotNull(state.getWinner()); + performTest(stack, Card.Suit.YELLOW, Card.Suit.RED); } @Test @@ -42,13 +48,7 @@ public class FinishingTrickTest { Pair.of(null, Card.GREEN_11) ); - FinishingTrick state = mock(FinishingTrick.class); - when(state.getWinner()).thenCallRealMethod(); - when(state.getStack()).thenReturn(stack); - when(state.getTrickSuit()).thenReturn(Card.Suit.RED); - when(state.getTrumpSuit()).thenReturn(Card.Suit.YELLOW); - - assertNotNull(state.getWinner()); + performTest(stack, Card.Suit.YELLOW, Card.Suit.RED); } @Test @@ -61,13 +61,7 @@ public class FinishingTrickTest { Pair.of(null, Card.GREEN_11) ); - FinishingTrick state = mock(FinishingTrick.class); - when(state.getWinner()).thenCallRealMethod(); - when(state.getStack()).thenReturn(stack); - when(state.getTrickSuit()).thenReturn(Card.Suit.RED); - when(state.getTrumpSuit()).thenReturn(Card.Suit.BLUE); - - assertNotNull(state.getWinner()); + performTest(stack, Card.Suit.BLUE, Card.Suit.RED); } @Test @@ -79,12 +73,6 @@ public class FinishingTrickTest { Pair.of(null, Card.YELLOW_JESTER) ); - FinishingTrick state = mock(FinishingTrick.class); - when(state.getWinner()).thenCallRealMethod(); - when(state.getStack()).thenReturn(stack); - when(state.getTrickSuit()).thenReturn(Card.Suit.NONE); - when(state.getTrumpSuit()).thenReturn(Card.Suit.NONE); - - assertNotNull(state.getWinner()); + performTest(stack, Card.Suit.NONE, Card.Suit.NONE); } } diff --git a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCardTest.java b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCardTest.java index cf6b8d4..49be417 100644 --- a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCardTest.java +++ b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/PlayingCardTest.java @@ -1,6 +1,6 @@ package eu.jonahbauer.wizard.core.machine.states.trick; -import eu.jonahbauer.wizard.core.machine.states.trick.PlayingCard; +import eu.jonahbauer.wizard.core.machine.Game; import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage; import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage; import eu.jonahbauer.wizard.core.messages.player.PredictMessage; @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.*; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @@ -22,9 +23,9 @@ public class PlayingCardTest { PlayingCard state = mock(PlayingCard.class); doCallRealMethod().when(state).checkCard(any()); - when(state.getHands()).thenReturn(Map.of(player, hand)); - when(state.getStack()).thenReturn(List.of()); - when(state.getCurrentPlayer()).thenReturn(player); + when(state.get(HANDS)).thenReturn(Map.of(player, hand)); + when(state.get(STACK)).thenReturn(List.of()); + when(state.get(CURRENT_PLAYER)).thenReturn(player); for (Card value : Card.values()) { if (hand.contains(value)) continue; @@ -42,8 +43,8 @@ public class PlayingCardTest { PlayingCard state = mock(PlayingCard.class); doCallRealMethod().when(state).checkCard(any()); when(state.getTrickSuit()).thenReturn(Card.Suit.YELLOW); - when(state.getHands()).thenReturn(Map.of(player, hand)); - when(state.getCurrentPlayer()).thenReturn(player); + when(state.get(HANDS)).thenReturn(Map.of(player, hand)); + when(state.get(CURRENT_PLAYER)).thenReturn(player); assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.BLUE_1)); assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.RED_1)); @@ -58,24 +59,28 @@ public class PlayingCardTest { UUID player = new UUID(0, 0); UUID player2 = new UUID(0, 1); + Game game = mock(Game.class); + PlayingCard state = mock(PlayingCard.class); - doCallRealMethod().when(state).onMessage(any(), any()); - when(state.getCurrentPlayer()).thenReturn(player); + doCallRealMethod().when(state).onMessage(any(), any(), any()); + when(state.get(CURRENT_PLAYER)).thenReturn(player); - assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PlayCardMessage(Card.BLUE_WIZARD))); - assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PredictMessage(1))); - assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PickTrumpMessage(Card.Suit.BLUE))); + assertThrows(IllegalStateException.class, () -> state.onMessage(game, player2, new PlayCardMessage(Card.BLUE_WIZARD))); + assertThrows(IllegalStateException.class, () -> state.onMessage(game, player2, new PredictMessage(1))); + assertThrows(IllegalStateException.class, () -> state.onMessage(game, player2, new PickTrumpMessage(Card.Suit.BLUE))); } @Test public void onMessage_ThrowsIllegalState_IfNotPlayCard() { UUID player = new UUID(0, 0); + Game game = mock(Game.class); + PlayingCard state = mock(PlayingCard.class); - doCallRealMethod().when(state).onMessage(any(), any()); - when(state.getCurrentPlayer()).thenReturn(player); + doCallRealMethod().when(state).onMessage(any(), any(), any()); + when(state.get(CURRENT_PLAYER)).thenReturn(player); - assertThrows(IllegalStateException.class, () -> state.onMessage(player, new PredictMessage(1))); - assertThrows(IllegalStateException.class, () -> state.onMessage(player, new PickTrumpMessage(Card.Suit.BLUE))); + assertThrows(IllegalStateException.class, () -> state.onMessage(game, player, new PredictMessage(1))); + assertThrows(IllegalStateException.class, () -> state.onMessage(game, player, new PickTrumpMessage(Card.Suit.BLUE))); } } diff --git a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickStateTest.java b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickStateTest.java index 58462f0..12c8171 100644 --- a/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickStateTest.java +++ b/wizard-core/src/test/java/eu/jonahbauer/wizard/core/machine/states/trick/TrickStateTest.java @@ -1,6 +1,5 @@ package eu.jonahbauer.wizard.core.machine.states.trick; -import eu.jonahbauer.wizard.core.machine.states.trick.TrickState; import eu.jonahbauer.wizard.core.model.Card; import eu.jonahbauer.wizard.core.util.Pair; import org.junit.jupiter.api.Test; @@ -8,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.util.List; import java.util.UUID; +import static eu.jonahbauer.wizard.core.machine.states.GameData.STACK; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -19,7 +19,7 @@ public class TrickStateTest { TrickState trickState = mock(TrickState.class); when(trickState.getTrickSuit()).thenCallRealMethod(); - when(trickState.getStack()).thenReturn(stack); + when(trickState.get(STACK)).thenReturn(stack); assertEquals(trickState.getTrickSuit(), Card.Suit.NONE); } @@ -36,7 +36,7 @@ public class TrickStateTest { TrickState trickState = mock(TrickState.class); when(trickState.getTrickSuit()).thenCallRealMethod(); - when(trickState.getStack()).thenReturn(stack); + when(trickState.get(STACK)).thenReturn(stack); assertEquals(trickState.getTrickSuit(), Card.Suit.RED); } @@ -53,7 +53,7 @@ public class TrickStateTest { TrickState trickState = mock(TrickState.class); when(trickState.getTrickSuit()).thenCallRealMethod(); - when(trickState.getStack()).thenReturn(stack); + when(trickState.get(STACK)).thenReturn(stack); assertEquals(trickState.getTrickSuit(), Card.Suit.NONE); } @@ -70,7 +70,7 @@ public class TrickStateTest { TrickState trickState = mock(TrickState.class); when(trickState.getTrickSuit()).thenCallRealMethod(); - when(trickState.getStack()).thenReturn(stack); + when(trickState.get(STACK)).thenReturn(stack); assertEquals(trickState.getTrickSuit(), Card.Suit.NONE); @@ -83,7 +83,7 @@ public class TrickStateTest { ); TrickState trickState2 = mock(TrickState.class); when(trickState2.getTrickSuit()).thenCallRealMethod(); - when(trickState2.getStack()).thenReturn(stack2); + when(trickState2.get(STACK)).thenReturn(stack2); assertEquals(trickState2.getTrickSuit(), Card.Suit.YELLOW); }