diff --git a/pom.xml b/pom.xml
index a2026d7..b18690d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
wizard-core
wizard-client
wizard-server
+ wizard-common
diff --git a/wizard-client/pom.xml b/wizard-client/pom.xml
index 31c05c8..e9e8659 100644
--- a/wizard-client/pom.xml
+++ b/wizard-client/pom.xml
@@ -19,8 +19,8 @@
- eu.jonahbauer
- wizard-core
+ ${project.groupId}
+ wizard-common
${project.version}
diff --git a/wizard-common/pom.xml b/wizard-common/pom.xml
new file mode 100644
index 0000000..11dc79c
--- /dev/null
+++ b/wizard-common/pom.xml
@@ -0,0 +1,19 @@
+
+
+
+ wizard
+ eu.jonahbauer
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ wizard-common
+
+
+ 17
+ 17
+
+
+
\ No newline at end of file
diff --git a/wizard-core/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/wizard-common/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
similarity index 100%
rename from wizard-core/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
rename to wizard-common/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/CardMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/CardMessage.java
similarity index 72%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/CardMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/CardMessage.java
index 1dba761..3dd271d 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/CardMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/CardMessage.java
@@ -1,6 +1,7 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
-import eu.jonahbauer.wizard.core.model.Card;
+import eu.jonahbauer.wizard.common.model.card.Card;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -12,6 +13,7 @@ import java.util.UUID;
*/
@Getter
@RequiredArgsConstructor
+@EqualsAndHashCode(callSuper = true)
public final class CardMessage extends ObserverMessage {
/**
* The UUID of the player.
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/HandMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/HandMessage.java
similarity index 81%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/HandMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/HandMessage.java
index 50f186f..396626c 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/HandMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/HandMessage.java
@@ -1,6 +1,7 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
-import eu.jonahbauer.wizard.core.model.Card;
+import eu.jonahbauer.wizard.common.model.card.Card;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@@ -11,6 +12,7 @@ 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.
diff --git a/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/JugglingMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/JugglingMessage.java
new file mode 100644
index 0000000..20823fd
--- /dev/null
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/JugglingMessage.java
@@ -0,0 +1,7 @@
+package eu.jonahbauer.wizard.common.messages.observer;
+
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+public final class JugglingMessage extends ObserverMessage {
+}
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ObserverMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ObserverMessage.java
similarity index 58%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ObserverMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ObserverMessage.java
index a55efcc..76b282a 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ObserverMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ObserverMessage.java
@@ -1,10 +1,12 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import eu.jonahbauer.wizard.core.util.SealedClassTypeAdapterFactory;
+import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
+import lombok.EqualsAndHashCode;
-public abstract sealed class ObserverMessage permits CardMessage, HandMessage, PredictionMessage, ScoreMessage, StateMessage, TrickMessage, TrumpMessage, UserInputMessage {
+@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();
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/PredictionMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/PredictionMessage.java
similarity index 82%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/PredictionMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/PredictionMessage.java
index 9dd1d6d..6dbcb65 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/PredictionMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/PredictionMessage.java
@@ -1,5 +1,6 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -12,6 +13,7 @@ import java.util.UUID;
*/
@Getter
@RequiredArgsConstructor
+@EqualsAndHashCode(callSuper = true)
public final class PredictionMessage extends ObserverMessage {
/**
* The UUID of the player who made a prediction.
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ScoreMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ScoreMessage.java
similarity index 83%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ScoreMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ScoreMessage.java
index 5301146..a0ee775 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/ScoreMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/ScoreMessage.java
@@ -1,5 +1,6 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@@ -11,6 +12,7 @@ import java.util.UUID;
* 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.
diff --git a/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/StateMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/StateMessage.java
new file mode 100644
index 0000000..5973472
--- /dev/null
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/StateMessage.java
@@ -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;
+}
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrickMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrickMessage.java
similarity index 76%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrickMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrickMessage.java
index d6b66fb..f91d570 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrickMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrickMessage.java
@@ -1,6 +1,7 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
-import eu.jonahbauer.wizard.core.model.Card;
+import eu.jonahbauer.wizard.common.model.card.Card;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
@@ -11,6 +12,7 @@ import java.util.UUID;
* cards played.
*/
@Getter
+@EqualsAndHashCode(callSuper = true)
public final class TrickMessage extends ObserverMessage {
/**
* The UUID of the player who won the trick.
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrumpMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrumpMessage.java
similarity index 78%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrumpMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrumpMessage.java
index 141f3a2..888647c 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/TrumpMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/TrumpMessage.java
@@ -1,6 +1,7 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.messages.observer;
-import eu.jonahbauer.wizard.core.model.Card;
+import eu.jonahbauer.wizard.common.model.card.Card;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
@@ -8,6 +9,7 @@ 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
@@ -20,7 +22,6 @@ public final class TrumpMessage extends ObserverMessage {
private final @Nullable Card.Suit suit;
public TrumpMessage(@Nullable Card card, @Nullable Card.Suit suit) {
- if (suit != null && !suit.isColor()) throw new IllegalArgumentException("The trump suit must be a color or null.");
if (card == null && suit == null) throw new IllegalArgumentException("Card and suit must not both be null");
this.card = card;
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/UserInputMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/UserInputMessage.java
similarity index 70%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/UserInputMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/UserInputMessage.java
index 4b99008..04879b9 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/observer/UserInputMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/observer/UserInputMessage.java
@@ -1,8 +1,9 @@
-package eu.jonahbauer.wizard.core.messages.observer;
+package eu.jonahbauer.wizard.common.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.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;
@@ -13,6 +14,7 @@ import java.util.UUID;
*/
@Getter
@RequiredArgsConstructor
+@EqualsAndHashCode(callSuper = true)
public final class UserInputMessage extends ObserverMessage {
/**
* The UUID of the player whose input is required.
@@ -33,6 +35,11 @@ public final class UserInputMessage extends ObserverMessage {
* {@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}.
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PickTrumpMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PickTrumpMessage.java
similarity index 52%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PickTrumpMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PickTrumpMessage.java
index 6de50f6..a10c544 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PickTrumpMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PickTrumpMessage.java
@@ -1,11 +1,13 @@
-package eu.jonahbauer.wizard.core.messages.player;
+package eu.jonahbauer.wizard.common.messages.player;
-import eu.jonahbauer.wizard.core.model.Card;
+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;
}
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayCardMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayCardMessage.java
similarity index 51%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayCardMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayCardMessage.java
index 8bfeabb..c0f3afb 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayCardMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayCardMessage.java
@@ -1,11 +1,13 @@
-package eu.jonahbauer.wizard.core.messages.player;
+package eu.jonahbauer.wizard.common.messages.player;
-import eu.jonahbauer.wizard.core.model.Card;
+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;
}
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayerMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayerMessage.java
similarity index 71%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayerMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayerMessage.java
index 677dde5..044e677 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PlayerMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PlayerMessage.java
@@ -1,9 +1,11 @@
-package eu.jonahbauer.wizard.core.messages.player;
+package eu.jonahbauer.wizard.common.messages.player;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import eu.jonahbauer.wizard.core.util.SealedClassTypeAdapterFactory;
+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"))
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PredictMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PredictMessage.java
similarity index 60%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PredictMessage.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PredictMessage.java
index f930c20..7fbe509 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/messages/player/PredictMessage.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/player/PredictMessage.java
@@ -1,10 +1,12 @@
-package eu.jonahbauer.wizard.core.messages.player;
+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;
}
diff --git a/wizard-common/src/main/java/eu/jonahbauer/wizard/common/model/card/Card.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/model/card/Card.java
new file mode 100644
index 0000000..f785866
--- /dev/null
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/model/card/Card.java
@@ -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
+ }
+}
diff --git a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/SealedClassTypeAdapterFactory.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/util/SealedClassTypeAdapterFactory.java
similarity index 97%
rename from wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/SealedClassTypeAdapterFactory.java
rename to wizard-common/src/main/java/eu/jonahbauer/wizard/common/util/SealedClassTypeAdapterFactory.java
index 40a08ab..f483de7 100644
--- a/wizard-core/src/main/java/eu/jonahbauer/wizard/core/util/SealedClassTypeAdapterFactory.java
+++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/util/SealedClassTypeAdapterFactory.java
@@ -1,4 +1,4 @@
-package eu.jonahbauer.wizard.core.util;
+package eu.jonahbauer.wizard.common.util;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
diff --git a/wizard-core/pom.xml b/wizard-core/pom.xml
index 76b9c2b..48302c1 100644
--- a/wizard-core/pom.xml
+++ b/wizard-core/pom.xml
@@ -16,4 +16,11 @@
${java.version}
+
+
+ ${project.groupId}
+ wizard-common
+ ${project.version}
+
+
\ No newline at end of file
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 c792460..f1d3c4d 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,12 +1,11 @@
package eu.jonahbauer.wizard.core;
+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 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;
@@ -30,15 +29,13 @@ public class CLI {
game.start(players);
- GameState state = null;
-
Scanner scanner = new Scanner(System.in);
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) ([a-z]+) (.*)\"");
+ System.err.println("Format is \"(\\d) ([a-z]+) (.*)\"");
continue;
}
String player = matcher.group(1);
@@ -55,15 +52,24 @@ public class CLI {
switch (command) {
case "predict" -> {
int prediction = Integer.parseInt(param);
- game.onMessage(players.get(id), new PredictMessage(prediction));
+ game.onMessage(players.get(id), new PredictMessage(prediction))
+ .whenComplete((v, err) -> {
+ if (err != null) err.printStackTrace();
+ });
}
case "play" -> {
Card card = Card.valueOf(param);
- game.onMessage(players.get(id), new PlayCardMessage(card));
+ game.onMessage(players.get(id), new PlayCardMessage(card))
+ .whenComplete((v, err) -> {
+ if (err != null) err.printStackTrace();
+ });
}
case "trump" -> {
Card.Suit suit = Card.Suit.valueOf(param);
- game.onMessage(players.get(id), new PickTrumpMessage(suit));
+ game.onMessage(players.get(id), new PickTrumpMessage(suit))
+ .whenComplete((v, err) -> {
+ if (err != null) err.printStackTrace();
+ });
}
default -> System.err.println("Unknown command: " + command);
}
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 90617a8..0eb6e91 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
@@ -1,123 +1,155 @@
package eu.jonahbauer.wizard.core.machine;
import eu.jonahbauer.wizard.core.machine.states.State;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Blocking;
+import org.jetbrains.annotations.NonBlocking;
import org.jetbrains.annotations.NotNull;
+import java.util.Comparator;
import java.util.concurrent.*;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.atomic.AtomicReference;
public abstract class Context, C extends Context> {
- protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ 1, 1,
+ 0, TimeUnit.SECONDS,
+ new PriorityBlockingQueue<>(
+ 11,
+ Comparator.comparingInt(r -> r instanceof PriorityRunnable prio ? prio.getPriority() : Integer.MAX_VALUE)
+ .thenComparingLong(r -> r instanceof PriorityRunnable prio ? prio.getTimestamp() : Long.MAX_VALUE)
+ ),
+ r -> {
+ var t = new Thread(r);
+ t.setUncaughtExceptionHandler((t1, e) -> finish(e));
+ return t;
+ }
+ );
+
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 S state) {
- lock.lock();
- try {
- if (finished) throw new IllegalStateException("Context has already finished.");
- transition(null, state);
- } finally {
- lock.unlock();
- }
+ private final CompletableFuture future = new CompletableFuture<>();
+ private final CompletableFuture finished = future.whenComplete((v, t) -> {
+ executor.shutdownNow();
+ scheduler.shutdownNow();
+ });
+
+ @NonBlocking
+ protected CompletableFuture submit(Runnable runnable) {
+ var future = new CompletableFuture();
+ executor.execute(new PriorityRunnable(100, () -> {
+ try {
+ runnable.run();
+ future.complete(null);
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+ }
+ }));
+ return future;
}
- public void transition(S currentState, S newState) {
- lock.lock();
- try {
- if (state == currentState) {
- state = newState;
- if (currentState != null) //noinspection unchecked
- currentState.onExit((C) this);
- onTransition(currentState, newState);
- if (newState != null) //noinspection unchecked
- newState.onEnter((C) this);
+ @Blocking
+ protected void start(@NotNull S state) {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference exception = new AtomicReference<>();
+
+ executor.execute(new PriorityRunnable(0, () -> {
+ if (future.isDone()) {
+ exception.set(new IllegalStateException("Context has already finished."));
+ latch.countDown();
} else {
- throw new IllegalStateException("Current state does not match.");
+ latch.countDown();
+ doTransition(null, state);
}
- } catch (Throwable t) {
- handleError(t);
- } finally {
- lock.unlock();
+ }));
+
+ while (true) {
+ try {
+ latch.await();
+ if (exception.get() != null) {
+ throw exception.get();
+ }
+ break;
+ } catch (InterruptedException ignored) {}
}
}
+ @NonBlocking
+ public void transition(S currentState, S newState) {
+ executor.execute(new PriorityRunnable(0, () -> doTransition(currentState, newState)));
+ }
+
+ @NonBlocking
public void finish() {
finish(null);
}
+ @NonBlocking
public void finish(Throwable exception) {
- lock.lock();
- try {
- finished = true;
- this.exception = exception;
- finishCondition.signalAll();
- transition(state, null);
- scheduler.shutdown();
- } finally {
- lock.unlock();
- }
+ executor.execute(new PriorityRunnable(0, () -> doFinish(exception)));
}
+ @NonBlocking
public void cancel() {
finish(new CancellationException());
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- public boolean isDone() {
- return finished;
+ /*
+ * internal methods that are called on the executor
+ */
+
+ private void doTransition(S currentState, S newState) {
+ if (state == currentState) {
+ state = newState;
+ if (currentState != null) //noinspection unchecked
+ currentState.onExit((C) this);
+ onTransition(currentState, newState);
+ if (newState != null) //noinspection unchecked
+ newState.onEnter((C) this);
+ }
+ }
+
+ private void doFinish(Throwable t) {
+ if (future.isDone()) return;
+
+ doTransition(state, null);
+ if (t != null) {
+ future.completeExceptionally(t);
+ } else {
+ future.complete(null);
+ }
}
+ protected void onTransition(S from, S to) {}
+
@Blocking
public void await() throws InterruptedException, ExecutionException, CancellationException {
- lock.lock();
- try {
- while (!finished) {
- finishCondition.await();
- }
- if (exception != null) {
- if (exception instanceof CancellationException cancelled) {
- throw cancelled;
- } else {
- throw new ExecutionException(exception);
- }
- }
- } finally {
- lock.unlock();
- }
+ finished.get();
}
public void timeout(@NotNull S currentState, long delay) {
scheduler.schedule(() -> {
- lock.lock();
- try {
+ submit(() -> {
if (state == currentState) {
//noinspection unchecked
state.onTimeout((C) this);
}
- } catch (Throwable t) {
- handleError(t);
- } finally {
- lock.unlock();
- }
+ });
}, delay, TimeUnit.MILLISECONDS);
}
- protected void handleError(Throwable t) {
- lock.lock();
- try {
- if (!isDone()) {
- finish(t);
- t.printStackTrace();
- }
- } finally {
- lock.unlock();
+ @Getter
+ @RequiredArgsConstructor
+ private static class PriorityRunnable implements Runnable {
+ private final int priority;
+ private final long timestamp = System.nanoTime();
+ private final Runnable runnable;
+
+ @Override
+ public void run() {
+ runnable.run();
}
}
-
- 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 33c04c6..df635e1 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
@@ -4,75 +4,73 @@ 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;
-import eu.jonahbauer.wizard.core.messages.observer.ObserverMessage;
-import eu.jonahbauer.wizard.core.messages.observer.StateMessage;
-import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
+import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
+import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
+import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.model.Configuration;
+import eu.jonahbauer.wizard.core.util.Util;
import lombok.Getter;
import java.util.List;
+import java.util.Random;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
public final class Game extends Context {
+ @Getter
+ private final Random random;
@Getter
private final Configuration config;
private final Observer observer;
public Game(Configuration config, Observer observer) {
+ this.random = new Random();
+ this.config = config;
+ this.observer = observer;
+ }
+
+ public Game(long seed, Configuration config, Observer observer) {
+ this.random = new Random(seed);
this.config = config;
this.observer = observer;
}
public void start(List players) {
- start(new Starting(new GameData().with(PLAYERS, List.copyOf(players))));
+ start(new Starting(GameData.EMPTY.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(this, player, message);
- } catch (IllegalStateException | IllegalArgumentException e) {
- throw e;
- } catch (Throwable t) {
- handleError(t);
- } finally {
- lock.unlock();
- }
+ public CompletableFuture onMessage(UUID player, PlayerMessage message) {
+ return submit(() -> {
+ if (state != null) {
+ state.onMessage(this, player, message);
+ }
+ });
}
@Override
protected void onTransition(GameState from, GameState to) {
- notify(new StateMessage(to != null ? to.getClass() : null));
+ notify(new StateMessage(to != null ? Util.toSnakeCase(to.getClass().getSimpleName()) : "null"));
}
public void notify(ObserverMessage message) {
try {
observer.notify(message);
- } catch (Exception e) {
- e.printStackTrace();
+ } catch (Throwable t) {
+ t.printStackTrace();
}
}
public void notify(UUID player, ObserverMessage message) {
try {
observer.notify(player, message);
- } catch (Exception e) {
- e.printStackTrace();
+ } catch (Throwable t) {
+ t.printStackTrace();
}
}
}
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
index c777951..88e9af8 100644
--- 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
@@ -1,6 +1,6 @@
package eu.jonahbauer.wizard.core.machine.states;
-import eu.jonahbauer.wizard.core.model.Card;
+import eu.jonahbauer.wizard.common.model.card.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@@ -14,10 +14,12 @@ import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
@Unmodifiable
-@EqualsAndHashCode(of = "values")
+@EqualsAndHashCode(of = {"values", "present"})
public final class GameData {
private static final int SIZE = 11;
+ public static final GameData EMPTY = new GameData();
+
public static final Key> PLAYERS = new Key<>("players", 0);
public static final Key