- Jubiläumsedition implementiert (#12)

- Tests verbessert
This commit is contained in:
Jonah
2021-11-04 20:18:26 +01:00
parent eba221525f
commit 2f1794973a
71 changed files with 2472 additions and 812 deletions

View File

@@ -0,0 +1,26 @@
package eu.jonahbauer.wizard.common.messages.observer;
import eu.jonahbauer.wizard.common.model.card.Card;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* A {@link CardMessage} is sent whenever a player plays a card.
*/
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class CardMessage extends ObserverMessage {
/**
* The UUID of the player.
*/
private final @NotNull UUID player;
/**
* The card played.
*/
private final @NotNull Card card;
}

View File

@@ -0,0 +1,31 @@
package eu.jonahbauer.wizard.common.messages.observer;
import eu.jonahbauer.wizard.common.model.card.Card;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
/**
* A {@link HandMessage} is sent when the player receives information about hit own or another player's hand cards.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public final class HandMessage extends ObserverMessage {
/**
* The UUID of player whose hand cards are sent.
*/
private final @NotNull UUID player;
/**
* A list of all the hand cards. May consist only of {@link Card#HIDDEN} if the cars are not visible to the player
* receiving this message
*/
private final @NotNull List<@NotNull Card> hand;
public HandMessage(@NotNull UUID player, @NotNull List<@NotNull Card> hand) {
this.player = player;
this.hand = List.copyOf(hand);
}
}

View File

@@ -0,0 +1,7 @@
package eu.jonahbauer.wizard.common.messages.observer;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public final class JugglingMessage extends ObserverMessage {
}

View File

@@ -0,0 +1,18 @@
package eu.jonahbauer.wizard.common.messages.observer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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 static final Gson GSON = new GsonBuilder()
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
.create();
@Override
public String toString() {
return GSON.toJson(this, ObserverMessage.class);
}
}

View File

@@ -0,0 +1,26 @@
package eu.jonahbauer.wizard.common.messages.observer;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.util.UUID;
/**
* A {@link PredictionMessage} is sent after a player makes (or changes) his prediction.
*/
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class PredictionMessage extends ObserverMessage {
/**
* The UUID of the player who made a prediction.
*/
private final @NotNull UUID player;
/**
* The prediction.
*/
private final @Range(from = 0, to = Integer.MAX_VALUE) int prediction;
}

View File

@@ -0,0 +1,25 @@
package eu.jonahbauer.wizard.common.messages.observer;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.UUID;
/**
* A {@link ScoreMessage} is sent at the end of each round and at the end of the game. It contains the number of points
* gained by each player in this round or the final result.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public final class ScoreMessage extends ObserverMessage {
/**
* The number of points for each player.
*/
private final @NotNull Map<@NotNull UUID, @NotNull Integer> points;
public ScoreMessage(@NotNull Map<@NotNull UUID, @NotNull Integer> points) {
this.points = Map.copyOf(points);
}
}

View File

@@ -0,0 +1,18 @@
package eu.jonahbauer.wizard.common.messages.observer;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* A {@link StateMessage} is sent whenever the game changes its internal state.
*/
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class StateMessage extends ObserverMessage {
/**
* The name of the new state in snake_case.
*/
private final String state;
}

View File

@@ -0,0 +1,30 @@
package eu.jonahbauer.wizard.common.messages.observer;
import eu.jonahbauer.wizard.common.model.card.Card;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
import java.util.UUID;
/**
* A {@link TrickMessage} is sent when a trick is completed. It contains the player who won the trick and a list of the
* cards played.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public final class TrickMessage extends ObserverMessage {
/**
* The UUID of the player who won the trick.
*/
private final UUID player;
/**
* The cards played.
*/
private final List<Card> cards;
public TrickMessage(UUID player, List<Card> cards) {
this.player = player;
this.cards = List.copyOf(cards);
}
}

View File

@@ -0,0 +1,30 @@
package eu.jonahbauer.wizard.common.messages.observer;
import eu.jonahbauer.wizard.common.model.card.Card;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
/**
* A {@link TrumpMessage} is sent when the trump suit of the current round is (being) determined.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public final class TrumpMessage extends ObserverMessage {
/**
* The {@link Card} that was revealed to determine the {@linkplain Card.Suit trump suit} or {@code null} no cards
* were left.
*/
private final @Nullable Card card;
/**
* The trump suit or {@code null} is the dealer has yet to decide.
*/
private final @Nullable Card.Suit suit;
public TrumpMessage(@Nullable Card card, @Nullable Card.Suit suit) {
if (card == null && suit == null) throw new IllegalArgumentException("Card and suit must not both be null");
this.card = card;
this.suit = suit;
}
}

View File

@@ -0,0 +1,54 @@
package eu.jonahbauer.wizard.common.messages.observer;
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 lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.UUID;
/**
* A {@link UserInputMessage} is sent when user input is required.
*/
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class UserInputMessage extends ObserverMessage {
/**
* The UUID of the player whose input is required.
*/
private final UUID player;
/**
* The type of input that is required.
*/
private final Action action;
/**
* A timeout in {@link System#currentTimeMillis() UNIX time} after which a default action is taken.
*/
private final long timeout;
public enum Action {
/**
* An action that indicates that a player should make a prediction. A {@link UserInputMessage} with this
* {@link UserInputMessage#getAction()} should be responded to with a {@link PredictMessage}.
*/
MAKE_PREDICTION,
/**
* An action that indicates that a player should change his a prediction by ±1. A {@link UserInputMessage} with
* this {@link UserInputMessage#getAction()} should be responded to with a {@link PredictMessage}.
*/
CHANGE_PREDICTION,
/**
* An action that indicates that a player should play a card. A {@link UserInputMessage} with this
* {@link UserInputMessage#getAction()} should be responded to with a {@link PlayCardMessage}.
*/
PLAY_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}.
*/
PICK_TRUMP
}
}

View File

@@ -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 PickTrumpMessage extends PlayerMessage {
private final Card.Suit trumpSuit;
}

View File

@@ -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 PlayCardMessage extends PlayerMessage {
private final Card card;
}

View File

@@ -0,0 +1,18 @@
package eu.jonahbauer.wizard.common.messages.player;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public abstract sealed class PlayerMessage permits PickTrumpMessage, PlayCardMessage, PredictMessage {
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(PlayerMessage.class, "Message"))
.create();
@Override
public String toString() {
return GSON.toJson(this);
}
}

View File

@@ -0,0 +1,12 @@
package eu.jonahbauer.wizard.common.messages.player;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class PredictMessage extends PlayerMessage {
private final int prediction;
}

View File

@@ -0,0 +1,31 @@
package eu.jonahbauer.wizard.common.model.card;
public enum Card {
HIDDEN,
BLUE_1, RED_1, GREEN_1, YELLOW_1,
BLUE_2, RED_2, GREEN_2, YELLOW_2,
BLUE_3, RED_3, GREEN_3, YELLOW_3,
BLUE_4, RED_4, GREEN_4, YELLOW_4,
BLUE_5, RED_5, GREEN_5, YELLOW_5,
BLUE_6, RED_6, GREEN_6, YELLOW_6,
BLUE_7, RED_7, GREEN_7, YELLOW_7,
BLUE_8, RED_8, GREEN_8, YELLOW_8,
BLUE_9, RED_9, GREEN_9, YELLOW_9,
BLUE_10, RED_10, GREEN_10, YELLOW_10,
BLUE_11, RED_11, GREEN_11, YELLOW_11,
BLUE_12, RED_12, GREEN_12, YELLOW_12,
BLUE_13, RED_13, GREEN_13, YELLOW_13,
BLUE_WIZARD, RED_WIZARD, GREEN_WIZARD, YELLOW_WIZARD,
BLUE_JESTER, RED_JESTER, GREEN_JESTER, YELLOW_JESTER,
CHANGELING, CHANGELING_WIZARD, CHANGELING_JESTER,
BOMB,
WEREWOLF,
DRAGON,
FAIRY,
CLOUD, CLOUD_BLUE, CLOUD_RED, CLOUD_GREEN, CLOUD_YELLOW,
JUGGLER, JUGGLER_BLUE, JUGGLER_RED, JUGGLER_GREEN, JUGGLER_YELLOW;
public enum Suit {
NONE, YELLOW, RED, GREEN, BLUE
}
}

View File

@@ -0,0 +1,44 @@
package eu.jonahbauer.wizard.common.util;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public final class SealedClassTypeAdapterFactory<T> implements TypeAdapterFactory {
private final RuntimeTypeAdapterFactory<T> factory;
public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz) {
return new SealedClassTypeAdapterFactory<>(clazz, null);
}
public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz, @Nullable String suffix) {
return new SealedClassTypeAdapterFactory<>(clazz, suffix);
}
private SealedClassTypeAdapterFactory(Class<T> clazz, @Nullable String suffix) {
factory = RuntimeTypeAdapterFactory.of(clazz);
for (Class<?> subclass : clazz.getPermittedSubclasses()) {
String name = subclass.getSimpleName();
// remove suffix
if (suffix != null) {
if (name.endsWith(suffix)) name = name.substring(0, name.length() - suffix.length());
}
// transform camelCast to snake_case
name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
factory.registerSubtype(subclass.asSubclass(clazz), name);
}
}
@Override
public <S> TypeAdapter<S> create(Gson gson, TypeToken<S> type) {
return factory.create(gson, type);
}
}