Fehler in Spiellogik von Jongleur und Wolke behoben (#12)
This commit is contained in:
parent
daa64e0393
commit
25ca90ddf5
@ -1,8 +0,0 @@
|
||||
package eu.jonahbauer.wizard.common.messages.observer;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class JugglingMessage extends ObserverMessage {
|
||||
|
||||
}
|
@ -6,7 +6,7 @@ import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class ObserverMessage permits CardMessage, HandMessage, JugglingMessage, PredictionMessage, ScoreMessage, StateMessage, TrickMessage, TrumpMessage, UserInputMessage {
|
||||
public abstract sealed class ObserverMessage permits CardMessage, HandMessage, PredictionMessage, ScoreMessage, StateMessage, TrickMessage, TrumpMessage, UserInputMessage {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
|
||||
.create();
|
||||
|
@ -1,8 +1,10 @@
|
||||
package eu.jonahbauer.wizard.common.messages.observer;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -17,7 +19,8 @@ import java.util.UUID;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class UserInputMessage extends ObserverMessage {
|
||||
/**
|
||||
* The UUID of the player whose input is required.
|
||||
* The UUID of the player whose input is required. May be {@code null} to indicate that an input is required from
|
||||
* every player.
|
||||
*/
|
||||
private final UUID player;
|
||||
/**
|
||||
@ -45,6 +48,12 @@ public final class UserInputMessage extends ObserverMessage {
|
||||
* {@link UserInputMessage#getAction()} should be responded to with a {@link PlayCardMessage}.
|
||||
*/
|
||||
PLAY_CARD,
|
||||
/**
|
||||
* An action that indicates that a player should choose a card that will be given to his left neighbor as a
|
||||
* result of a played {@link Card#JUGGLER}. A {@link UserInputMessage} with this
|
||||
* {@link UserInputMessage#getAction()} should be responded to with a {@link JuggleMessage}.
|
||||
*/
|
||||
JUGGLE_CARD,
|
||||
/**
|
||||
* An action that indicates that a player should pick a trump suit. A {@link UserInputMessage} with this
|
||||
* {@link UserInputMessage#getAction()} should be responded to with a {@link PickTrumpMessage}.
|
||||
|
@ -0,0 +1,13 @@
|
||||
package eu.jonahbauer.wizard.common.messages.player;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class JuggleMessage extends PlayerMessage {
|
||||
private final Card card;
|
||||
}
|
@ -6,7 +6,7 @@ import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class PlayerMessage permits PickTrumpMessage, PlayCardMessage, PredictMessage {
|
||||
public abstract sealed class PlayerMessage permits JuggleMessage, PickTrumpMessage, PlayCardMessage, PredictMessage {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(PlayerMessage.class, "Message"))
|
||||
.create();
|
||||
|
@ -12,26 +12,28 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Unmodifiable
|
||||
@EqualsAndHashCode(of = {"values", "present"})
|
||||
public final class GameData {
|
||||
private static final int SIZE = 11;
|
||||
private static final int SIZE = 12;
|
||||
|
||||
public static final GameData EMPTY = new GameData();
|
||||
|
||||
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);
|
||||
|
||||
public static final Key<List<UUID>> PLAYERS = new Key<>("players", 0, null);
|
||||
public static final Key<Map<UUID, Integer>> SCORE = new Key<>("score", 1, Map::of);
|
||||
public static final Key<Integer> ROUND = new Key<>("round", 2, () -> 0);
|
||||
public static final Key<Map<UUID, List<Card>>> HANDS = new Key<>("hands", 3, null);
|
||||
public static final Key<Card> TRUMP_CARD = new Key<>("trumpCard", 4, null);
|
||||
public static final Key<Card.Suit> TRUMP_SUIT = new Key<>("trumpSuit", 5, null);
|
||||
public static final Key<Map<UUID, Integer>> PREDICTIONS = new Key<>("predictions", 6, Map::of);
|
||||
public static final Key<Map<UUID, Integer>> TRICKS = new Key<>("tricks", 7, Map::of);
|
||||
public static final Key<Integer> TRICK = new Key<>("trick", 8, () -> 0);
|
||||
public static final Key<List<Pair<UUID, Card>>> STACK = new Key<>("stack", 9, List::of);
|
||||
public static final Key<UUID> CURRENT_PLAYER = new Key<>("currentPlayer", 10, null);
|
||||
public static final Key<UUID> CLOUDED_PLAYER = new Key<>("cloudedPlayer", 11, null);
|
||||
|
||||
private final Object[] values;
|
||||
private final boolean[] present;
|
||||
private transient final boolean[] required = new boolean[SIZE];
|
||||
@ -47,18 +49,35 @@ public final class GameData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped or {@code null} if this map contains no mapping for the
|
||||
* key.
|
||||
* Returns the value to which the specified key is mapped, the default value for the specified key, or throws
|
||||
* a {@link NoSuchElementException} if this map neither contains a mapping for the key nor does the key have a
|
||||
* default value.
|
||||
* @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();
|
||||
if (present[index]) {
|
||||
//noinspection unchecked
|
||||
return (T) values[index];
|
||||
} else if (key.defaultValue() != null) {
|
||||
present[index] = true;
|
||||
values[index] = key.defaultValue().get();
|
||||
//noinspection unchecked
|
||||
return (T) values[index];
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
return (T) values[index];
|
||||
/**
|
||||
* Returns {@code true} if this map contains a mapping for the specified key.
|
||||
* @param key key whose presence in this map is to be tested
|
||||
* @return {@code true} if this map contains a mapping for the specified key
|
||||
*/
|
||||
public boolean has(@NotNull Key<?> key) {
|
||||
return present[key.index()];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,13 +191,21 @@ public final class GameData {
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
public GameData require(@NotNull Key<?> key) {
|
||||
if (!present[key.index()]) {
|
||||
throw new NoSuchElementException("Could not find required value '" + key + "'.");
|
||||
if (!present[key.index()] && key.defaultValue() == null) {
|
||||
throw new InvalidDataException("Could not find required value '" + key + "'.");
|
||||
}
|
||||
required[key.index()] = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #clean()
|
||||
*/
|
||||
public GameData keep(@NotNull Key<?> 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.
|
||||
@ -213,13 +240,13 @@ public final class GameData {
|
||||
var mapValue = get(map);
|
||||
var listValue = get(list);
|
||||
for (K k : listValue) {
|
||||
if (!mapValue.containsKey(k)) throw new NoSuchElementException("Could not find required value: " + map.name() + "[" + k + "].");
|
||||
if (!mapValue.containsKey(k)) throw new InvalidDataException("Could not find required value: " + map.name() + "[" + k + "].");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retains only the mappings that have been required since object creation.
|
||||
* Retains only the mappings that have been required or kept since object creation.
|
||||
* @return {@code this}
|
||||
*/
|
||||
public GameData clean() {
|
||||
@ -246,6 +273,7 @@ public final class GameData {
|
||||
public static class Key<T> {
|
||||
String name;
|
||||
int index;
|
||||
Supplier<T> defaultValue;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -1,14 +1,10 @@
|
||||
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.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
public final class Starting extends GameState {
|
||||
|
||||
public Starting(GameData data) {
|
||||
@ -17,9 +13,6 @@ public final class Starting extends GameState {
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
transition(game, new StartingRound(getData().with(
|
||||
ROUND, 0,
|
||||
SCORE, Map.of()
|
||||
)));
|
||||
transition(game, new StartingRound(getData()));
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,38 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
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.states.round.FinishingRound;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.CHANGE_PREDICTION;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
|
||||
|
||||
public final class ChangingPrediction extends TrickState {
|
||||
public final class ChangingPrediction extends RoundState {
|
||||
private transient final int oldPrediction;
|
||||
|
||||
public ChangingPrediction(GameData data) {
|
||||
super(data);
|
||||
oldPrediction = get(PREDICTIONS).get(get(CURRENT_PLAYER));
|
||||
super(data.requireEach(PLAYERS, PREDICTIONS).require(TRICKS, SCORE, CLOUDED_PLAYER));
|
||||
checkData(data);
|
||||
oldPrediction = get(PREDICTIONS).get(get(CLOUDED_PLAYER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(get(CURRENT_PLAYER), CHANGE_PREDICTION, getTimeout(game, true)));
|
||||
game.notify(new UserInputMessage(get(CLOUDED_PLAYER), CHANGE_PREDICTION, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
if (get(CLOUDED_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
checkPrediction(predictMessage.getPrediction());
|
||||
transition(game, predictMessage.getPrediction());
|
||||
} else {
|
||||
@ -53,21 +54,22 @@ public final class ChangingPrediction extends TrickState {
|
||||
}
|
||||
|
||||
private void transition(Game game, int prediction) {
|
||||
game.notify(new PredictionMessage(get(CURRENT_PLAYER), prediction));
|
||||
game.notify(new PredictionMessage(get(CLOUDED_PLAYER), prediction));
|
||||
|
||||
// add prediction
|
||||
var predictions = new HashMap<>(get(PREDICTIONS));
|
||||
predictions.put(get(CURRENT_PLAYER), prediction);
|
||||
predictions.put(get(CLOUDED_PLAYER), prediction);
|
||||
|
||||
GameData data = getData().with(
|
||||
PREDICTIONS, Map.copyOf(predictions)
|
||||
);
|
||||
|
||||
boolean hasNextTrick = get(TRICK) < get(ROUND);
|
||||
if (hasNextTrick) {
|
||||
transition(game, new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
|
||||
} else {
|
||||
transition(game, new FinishingRound(data));
|
||||
transition(game, new FinishingRound(data));
|
||||
}
|
||||
|
||||
private void checkData(GameData data) {
|
||||
if (data.get(CLOUDED_PLAYER) == null) {
|
||||
throw new InvalidDataException("Clouded player is null.");
|
||||
}
|
||||
}
|
||||
}
|
@ -80,7 +80,6 @@ public final class DeterminingTrump extends RoundState {
|
||||
private void transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||
GameData data = getData().with(
|
||||
TRUMP_SUIT, trumpSuit,
|
||||
PREDICTIONS, Map.of(),
|
||||
CURRENT_PLAYER, getNextPlayer(getDealer())
|
||||
);
|
||||
|
||||
|
@ -80,10 +80,6 @@ public final class Predicting extends RoundState {
|
||||
);
|
||||
|
||||
if (isLastPlayer()) {
|
||||
data = data.with(
|
||||
TRICK, 0,
|
||||
TRICKS, Map.of()
|
||||
);
|
||||
transition(game, new StartingTrick(data));
|
||||
} else {
|
||||
transition(game, new Predicting(data));
|
||||
|
@ -4,9 +4,8 @@ import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.ChangingPrediction;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.JugglingMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.TrickMessage;
|
||||
import eu.jonahbauer.wizard.core.model.card.*;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
@ -43,32 +42,30 @@ public final class FinishingTrick extends TrickState {
|
||||
|| cards.contains(Card.CLOUD_YELLOW);
|
||||
boolean hasNextTrick = get(TRICK) < get(ROUND);
|
||||
|
||||
// juggle hands
|
||||
if (juggler && hasNextTrick) {
|
||||
game.notify(new JugglingMessage());
|
||||
var hands = get(HANDS);
|
||||
Map<UUID, List<Card>> juggledHands = new HashMap<>();
|
||||
hands.forEach((player, hand) -> juggledHands.put(getNextPlayer(player), hand));
|
||||
data = data.with(HANDS, Map.copyOf(juggledHands));
|
||||
juggledHands.forEach((player, hand) -> game.notify(player, new HandMessage(player, hand)));
|
||||
}
|
||||
|
||||
// trick is not counted when a bomb is present
|
||||
if (!bomb) {
|
||||
// trick is not counted when a bomb is present
|
||||
var tricks = new HashMap<>(get(TRICKS));
|
||||
tricks.merge(winner, 1, Integer::sum);
|
||||
data = data.with(TRICKS, Map.copyOf(tricks));
|
||||
|
||||
// mark "clouded player"
|
||||
if (cloud) {
|
||||
data = data.with(CLOUDED_PLAYER, winner);
|
||||
}
|
||||
}
|
||||
|
||||
data = data.with(CURRENT_PLAYER, winner);
|
||||
|
||||
if (cloud && !bomb) {
|
||||
// adjust prediction
|
||||
transition(game, new ChangingPrediction(data));
|
||||
} else if (hasNextTrick) {
|
||||
transition(game, new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
|
||||
if (!hasNextTrick) {
|
||||
if (data.has(CLOUDED_PLAYER)) {
|
||||
transition(game, new ChangingPrediction(data));
|
||||
} else {
|
||||
transition(game, new FinishingRound(data));
|
||||
}
|
||||
} else if (juggler) {
|
||||
transition(game, new Juggling(data));
|
||||
} else {
|
||||
transition(game, new FinishingRound(data));
|
||||
transition(game, new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,82 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
public final class Juggling extends TrickState {
|
||||
private final transient Map<UUID, Card> juggledCards = new ConcurrentHashMap<>();
|
||||
|
||||
public Juggling(GameData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(null, JUGGLE_CARD, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
for (UUID player : get(PLAYERS)) {
|
||||
juggledCards.computeIfAbsent(player, p -> {
|
||||
var hand = get(HANDS).get(p);
|
||||
return hand.get(game.getRandom().nextInt(hand.size()));
|
||||
});
|
||||
}
|
||||
juggle(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(PLAYERS).contains(player) && message instanceof JuggleMessage juggleMessage) {
|
||||
Card card = juggleMessage.getCard();
|
||||
|
||||
if (!get(HANDS).get(player).contains(card)) {
|
||||
throw new IllegalArgumentException("You do not have this card on your hand.");
|
||||
}
|
||||
|
||||
juggledCards.put(player, card);
|
||||
if (juggledCards.size() == get(PLAYERS).size()) {
|
||||
juggle(game);
|
||||
}
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void juggle(Game game) {
|
||||
Map<UUID, Card> newCards = new HashMap<>();
|
||||
juggledCards.forEach((player, card) -> newCards.put(getNextPlayer(player), card));
|
||||
|
||||
var mutableHands = new HashMap<>(get(HANDS));
|
||||
|
||||
for (UUID player : get(PLAYERS)) {
|
||||
var mutableHand = new ArrayList<>(mutableHands.get(player));
|
||||
var oldCard = juggledCards.get(player);
|
||||
var newCard = newCards.get(player);
|
||||
mutableHand.set(mutableHand.indexOf(oldCard), newCard);
|
||||
var immutableHand = List.copyOf(mutableHand);
|
||||
mutableHands.put(player, immutableHand);
|
||||
game.notify(player, new HandMessage(player, immutableHand));
|
||||
}
|
||||
|
||||
var data = getData().with(
|
||||
HANDS, Map.copyOf(mutableHands),
|
||||
TRICK, get(TRICK) + 1
|
||||
);
|
||||
|
||||
transition(game, new StartingTrick(data));
|
||||
}
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
|
||||
public final class StartingTrick extends TrickState {
|
||||
public StartingTrick(GameData data) {
|
||||
@ -14,6 +10,6 @@ public final class StartingTrick extends TrickState {
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
transition(game, new PlayingCard(getData().with(STACK, List.of())));
|
||||
transition(game, new PlayingCard(getData()));
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ public abstract class TrickState extends RoundState {
|
||||
data.requireEach(PLAYERS, HANDS)
|
||||
.requireEach(PLAYERS, PREDICTIONS)
|
||||
.require(TRUMP_SUIT, TRICK, TRICKS, CURRENT_PLAYER)
|
||||
.keep(CLOUDED_PLAYER)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.*;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import lombok.Getter;
|
||||
@ -31,8 +28,14 @@ public class MessageQueue implements Observer {
|
||||
@Setter
|
||||
private Game game;
|
||||
|
||||
private BulkQueuedMessage bulk;
|
||||
|
||||
public MessageQueue add(UUID player, UserInputMessage.Action action, PlayerMessage message) {
|
||||
messages.add(new QueuedMessage(player, action, message));
|
||||
if (bulk != null) {
|
||||
bulk.getMessages().add(new SingleQueuedMessage(player, action, message));
|
||||
} else {
|
||||
messages.add(new SingleQueuedMessage(player, action, message));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -40,6 +43,10 @@ public class MessageQueue implements Observer {
|
||||
return add(player, UserInputMessage.Action.PLAY_CARD, new PlayCardMessage(card));
|
||||
}
|
||||
|
||||
public MessageQueue addJuggle(UUID player, Card card) {
|
||||
return add(player, UserInputMessage.Action.JUGGLE_CARD, new JuggleMessage(card));
|
||||
}
|
||||
|
||||
public MessageQueue addPrediction(UUID player, int prediction) {
|
||||
return add(player, UserInputMessage.Action.MAKE_PREDICTION, new PredictMessage(prediction));
|
||||
}
|
||||
@ -76,7 +83,27 @@ public class MessageQueue implements Observer {
|
||||
}
|
||||
|
||||
public MessageQueue assertThrows(Class<? extends Exception> exception) {
|
||||
messages.getLast().setException(exception);
|
||||
var message = messages.getLast();
|
||||
if (message instanceof SingleQueuedMessage singleMessage) {
|
||||
singleMessage.setException(exception);
|
||||
} else if (message instanceof BulkQueuedMessage bulkMessage) {
|
||||
bulkMessage.getMessages().get(bulkMessage.getMessages().size() - 1).setException(exception);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageQueue begin() {
|
||||
if (bulk != null) {
|
||||
end();
|
||||
}
|
||||
bulk = new BulkQueuedMessage();
|
||||
messages.add(bulk);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageQueue end() {
|
||||
if (bulk == null) throw new IllegalStateException();
|
||||
bulk = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -85,18 +112,27 @@ public class MessageQueue implements Observer {
|
||||
System.out.println(om);
|
||||
if (om instanceof UserInputMessage message) {
|
||||
UUID player = message.getPlayer();
|
||||
while (true) {
|
||||
assertFalse(messages.isEmpty(), "User input is required but none is provided.");
|
||||
assertFalse(messages.isEmpty(), "User input is required but none is provided.");
|
||||
|
||||
var queued = messages.poll();
|
||||
var queued = messages.poll();
|
||||
|
||||
var queuedPlayer = queued.getPlayer();
|
||||
var queuedAction = queued.getAction();
|
||||
var queuedMessage = queued.getMessage();
|
||||
var exception = queued.getException();
|
||||
List<SingleQueuedMessage> list;
|
||||
if (queued instanceof SingleQueuedMessage s) {
|
||||
list = List.of(s);
|
||||
} else if (queued instanceof BulkQueuedMessage b) {
|
||||
list = b.getMessages();
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
for (SingleQueuedMessage queuedSingle : list) {
|
||||
var queuedPlayer = queuedSingle.getPlayer();
|
||||
var queuedAction = queuedSingle.getAction();
|
||||
var queuedMessage = queuedSingle.getMessage();
|
||||
var exception = queuedSingle.getException();
|
||||
|
||||
if (exception == null) {
|
||||
assertEquals(queuedPlayer, player);
|
||||
if (player != null) assertEquals(queuedPlayer, player);
|
||||
assertEquals(queuedAction, message.getAction());
|
||||
}
|
||||
|
||||
@ -114,7 +150,6 @@ public class MessageQueue implements Observer {
|
||||
);
|
||||
} else {
|
||||
Assertions.assertDoesNotThrow(() -> game.onMessage(queuedPlayer, queuedMessage).get());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,13 +168,20 @@ public class MessageQueue implements Observer {
|
||||
|
||||
}
|
||||
|
||||
private interface QueuedMessage {}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@RequiredArgsConstructor
|
||||
private static class QueuedMessage {
|
||||
private static class SingleQueuedMessage implements QueuedMessage {
|
||||
private final UUID player;
|
||||
private final UserInputMessage.Action action;
|
||||
private final PlayerMessage message;
|
||||
private Class<? extends Exception> exception;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class BulkQueuedMessage implements QueuedMessage {
|
||||
private final List<SingleQueuedMessage> messages = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
@ -103,16 +103,21 @@ public class PredictingTest {
|
||||
public void predicting_WithWrongInput() {
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPrediction(players[0], -1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[0], 6).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[0], 4)
|
||||
.addPrediction(players[2], 3).assertThrows(IllegalStateException.class)
|
||||
.addPrediction(players[1], 3)
|
||||
.addPrediction(players[2], 5).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[2], 3)
|
||||
.addCard(players[3], Card.GREEN_WIZARD).assertThrows(IllegalStateException.class)
|
||||
.addPickTrump(players[3], Card.Suit.GREEN).assertThrows(IllegalStateException.class)
|
||||
.addPrediction(players[3], 0);
|
||||
.begin()
|
||||
.addPrediction(players[0], -1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[0], 6).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[0], 4)
|
||||
.begin()
|
||||
.addPrediction(players[2], 3).assertThrows(IllegalStateException.class)
|
||||
.addPrediction(players[1], 3)
|
||||
.begin()
|
||||
.addPrediction(players[2], 5).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[2], 3)
|
||||
.begin()
|
||||
.addCard(players[3], Card.GREEN_WIZARD).assertThrows(IllegalStateException.class)
|
||||
.addPickTrump(players[3], Card.Suit.GREEN).assertThrows(IllegalStateException.class)
|
||||
.addPrediction(players[3], 0)
|
||||
.end();
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
|
||||
|
||||
@ -143,8 +148,10 @@ public class PredictingTest {
|
||||
.addPrediction(players[0], 1)
|
||||
.addPrediction(players[1], 1)
|
||||
.addPrediction(players[2], 1)
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[3], 0);
|
||||
.begin()
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[3], 0)
|
||||
.end();
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021_PM1, 3, queue);
|
||||
|
||||
|
@ -142,45 +142,56 @@ public class RoundTest {
|
||||
.addPrediction(players[3], 2)
|
||||
.addPrediction(players[0], 2)
|
||||
.addPrediction(players[1], 2)
|
||||
.addPrediction(players[2], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[2], 3)
|
||||
.begin()
|
||||
.addPrediction(players[2], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[2], 3)
|
||||
.end()
|
||||
// trick 0
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[3], Card.BLUE_2)
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[0], Card.YELLOW_8)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[3], Card.BLUE_2)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[0], Card.YELLOW_8)
|
||||
.end()
|
||||
.addCard(players[1], Card.BLUE_9)
|
||||
.addCard(players[2], Card.GREEN_WIZARD)
|
||||
// trick 1
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[2], Card.YELLOW_4)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[2], Card.YELLOW_4)
|
||||
.end()
|
||||
.addCard(players[3], Card.YELLOW_3)
|
||||
.addCard(players[0], Card.YELLOW_WIZARD)
|
||||
.addCard(players[1], Card.BOMB)
|
||||
// trick 2
|
||||
.addCard(players[0], Card.RED_3)
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.RED_12)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.RED_12)
|
||||
.end()
|
||||
.addCard(players[2], Card.RED_2)
|
||||
.addCard(players[3], Card.DRAGON)
|
||||
// trick 3
|
||||
.addCard(players[3], Card.BLUE_13)
|
||||
.addCard(players[0], Card.CLOUD).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[0], Card.CLOUD_YELLOW)
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.YELLOW_13)
|
||||
.begin()
|
||||
.addCard(players[0], Card.CLOUD).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[0], Card.CLOUD_YELLOW)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.YELLOW_13)
|
||||
.end()
|
||||
.addCard(players[2], Card.BLUE_1)
|
||||
.addChangePrediction(players[1], 0).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 2).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 4).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 1)
|
||||
// trick 4
|
||||
.addCard(players[1], Card.RED_7)
|
||||
.addCard(players[2], Card.YELLOW_11)
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[3], Card.RED_5)
|
||||
.addCard(players[0], Card.CLOUD).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[0], Card.CHANGELING_WIZARD)
|
||||
.begin()
|
||||
.addCard(players[3], Card.RED_11).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[3], Card.RED_5)
|
||||
.begin()
|
||||
.addCard(players[0], Card.CLOUD).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[0], Card.CHANGELING_WIZARD)
|
||||
.end()
|
||||
// trick 5
|
||||
.addCard(players[0], Card.GREEN_7)
|
||||
.addCard(players[1], Card.FAIRY)
|
||||
@ -190,7 +201,14 @@ public class RoundTest {
|
||||
.addCard(players[2], Card.BLUE_4)
|
||||
.addCard(players[3], Card.BLUE_11)
|
||||
.addCard(players[0], Card.GREEN_1)
|
||||
.addCard(players[1], Card.GREEN_11);
|
||||
.addCard(players[1], Card.GREEN_11)
|
||||
// cloud
|
||||
.begin()
|
||||
.addChangePrediction(players[1], 0).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 2).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 4).assertThrows(IllegalArgumentException.class)
|
||||
.addChangePrediction(players[1], 1)
|
||||
.end();
|
||||
|
||||
int round = 6;
|
||||
Game game = performTest(227L, Configurations.ANNIVERSARY_2021_PM1, round, queue);
|
||||
@ -219,12 +237,10 @@ public class RoundTest {
|
||||
}
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing_trick
|
||||
order.verify(game).notify(any(TrickMessage.class)); // trick
|
||||
if (i == 3) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // change prediction
|
||||
order.verify(game).notify(any(UserInputMessage.class));
|
||||
order.verify(game).notify(any(PredictionMessage.class));
|
||||
}
|
||||
}
|
||||
order.verify(game).notify(any(StateMessage.class)); // change prediction
|
||||
order.verify(game).notify(any(UserInputMessage.class));
|
||||
order.verify(game).notify(any(PredictionMessage.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing_round
|
||||
order.verify(game).notify(argThat(message ->
|
||||
message instanceof ScoreMessage score
|
||||
|
@ -102,12 +102,16 @@ public class TrickTest {
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCard(players[0], Card.RED_1)
|
||||
.addCard(players[2], Card.GREEN_1).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.GREEN_1).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[1], Card.YELLOW_1)
|
||||
.begin()
|
||||
.addCard(players[2], Card.GREEN_1).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[1], Card.GREEN_1).assertThrows(IllegalArgumentException.class)
|
||||
.addCard(players[1], Card.YELLOW_1)
|
||||
.end()
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[3], Card.BLUE_1);
|
||||
.begin()
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[3], Card.BLUE_1)
|
||||
.end();
|
||||
|
||||
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
|
||||
|
||||
@ -178,7 +182,7 @@ public class TrickTest {
|
||||
players[0], List.of(Card.RED_1, Card.GREEN_12),
|
||||
players[1], List.of(Card.JUGGLER, Card.YELLOW_3),
|
||||
players[2], List.of(Card.GREEN_1, Card.BLUE_4),
|
||||
players[3], List.of(Card.BLUE_1, Card.RED_5)
|
||||
players[3], List.of(Card.RED_5, Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
@ -186,7 +190,13 @@ public class TrickTest {
|
||||
.addCard(players[0], Card.RED_1)
|
||||
.addCard(players[1], Card.JUGGLER_RED)
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addCard(players[3], Card.RED_5);
|
||||
.addCard(players[3], Card.RED_5)
|
||||
.begin()
|
||||
.addJuggle(players[0], Card.GREEN_12)
|
||||
.addJuggle(players[1], Card.YELLOW_3)
|
||||
.addJuggle(players[2], Card.BLUE_4)
|
||||
.addJuggle(players[3], Card.BLUE_1)
|
||||
.end();
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 1, 0, hands, Card.Suit.YELLOW, queue);
|
||||
|
||||
@ -207,7 +217,8 @@ public class TrickTest {
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing trick
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrickMessage trick && trick.getPlayer() == players[1])); // trick with correct winner
|
||||
order.verify(game).notify(any(JugglingMessage.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // juggling
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game, times(4)).notify(any(), any(HandMessage.class));
|
||||
order.verify(game).transition(any(), any(StartingTrick.class)); // there is another trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
|
Loading…
x
Reference in New Issue
Block a user