parent
1775604e1d
commit
66f6d10330
1
pom.xml
1
pom.xml
@ -13,6 +13,7 @@
|
||||
<module>wizard-core</module>
|
||||
<module>wizard-client</module>
|
||||
<module>wizard-server</module>
|
||||
<module>wizard-common</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>eu.jonahbauer</groupId>
|
||||
<artifactId>wizard-core</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>wizard-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
19
wizard-common/pom.xml
Normal file
19
wizard-common/pom.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>wizard</artifactId>
|
||||
<groupId>eu.jonahbauer</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>wizard-common</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -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.
|
@ -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.
|
@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.wizard.common.messages.observer;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class JugglingMessage extends ObserverMessage {
|
||||
}
|
@ -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();
|
@ -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.
|
@ -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.
|
@ -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;
|
||||
}
|
@ -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.
|
@ -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;
|
@ -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}.
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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"))
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
@ -16,4 +16,11 @@
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>wizard-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -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);
|
||||
}
|
||||
|
@ -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<S extends State<S,C>, C extends Context<S,C>> {
|
||||
protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
throw new IllegalStateException("Current state does not match.");
|
||||
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;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
);
|
||||
|
||||
protected S state;
|
||||
|
||||
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
private final CompletableFuture<Void> finished = future.whenComplete((v, t) -> {
|
||||
executor.shutdownNow();
|
||||
scheduler.shutdownNow();
|
||||
});
|
||||
|
||||
@NonBlocking
|
||||
protected CompletableFuture<Void> submit(Runnable runnable) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
executor.execute(new PriorityRunnable(100, () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
future.complete(null);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
}));
|
||||
return future;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
protected void start(@NotNull S state) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<RuntimeException> exception = new AtomicReference<>();
|
||||
|
||||
executor.execute(new PriorityRunnable(0, () -> {
|
||||
if (future.isDone()) {
|
||||
exception.set(new IllegalStateException("Context has already finished."));
|
||||
latch.countDown();
|
||||
} else {
|
||||
latch.countDown();
|
||||
doTransition(null, state);
|
||||
}
|
||||
}));
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void timeout(@NotNull S currentState, long delay) {
|
||||
scheduler.schedule(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
if (state == currentState) {
|
||||
//noinspection unchecked
|
||||
state.onTimeout((C) this);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
private void doFinish(Throwable t) {
|
||||
if (future.isDone()) return;
|
||||
|
||||
protected void handleError(Throwable t) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!isDone()) {
|
||||
finish(t);
|
||||
t.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
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 {
|
||||
finished.get();
|
||||
}
|
||||
|
||||
public void timeout(@NotNull S currentState, long delay) {
|
||||
scheduler.schedule(() -> {
|
||||
submit(() -> {
|
||||
if (state == currentState) {
|
||||
//noinspection unchecked
|
||||
state.onTimeout((C) this);
|
||||
}
|
||||
});
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<GameState, Game> {
|
||||
@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<UUID> 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<Void> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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);
|
||||
@ -34,7 +36,7 @@ public final class GameData {
|
||||
private final boolean[] present;
|
||||
private transient final boolean[] required = new boolean[SIZE];
|
||||
|
||||
public GameData() {
|
||||
private GameData() {
|
||||
this.values = new Object[SIZE];
|
||||
this.present = new boolean[SIZE];
|
||||
}
|
||||
@ -81,6 +83,37 @@ public final class GameData {
|
||||
return new GameData(newValues, newPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #with(Key, Object)
|
||||
*/
|
||||
public GameData with(@NotNull TypedValue<?>@NotNull... entries) {
|
||||
boolean equal = true;
|
||||
for (TypedValue<?> entry : entries) {
|
||||
int index = entry.key().index();
|
||||
if (!present[index] || !Objects.equals(values[index], entry.value())) {
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (equal) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||
for (TypedValue<?> entry : entries) {
|
||||
int index = entry.key().index();
|
||||
newValues[index] = entry.value();
|
||||
newPresent[index] = true;
|
||||
}
|
||||
|
||||
return new GameData(newValues, newPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #with(Key, Object)
|
||||
*/
|
||||
public <T1, T2> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2) {
|
||||
int index1 = key1.index();
|
||||
int index2 = key2.index();
|
||||
@ -101,6 +134,9 @@ public final class GameData {
|
||||
return new GameData(newValues, newPresent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #with(Key, Object)
|
||||
*/
|
||||
public <T1, T2, T3> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2, Key<T3> key3, T3 value3) {
|
||||
int index1 = key1.index();
|
||||
int index2 = key2.index();
|
||||
@ -177,7 +213,7 @@ public final class GameData {
|
||||
var mapValue = get(map);
|
||||
var listValue = get(list);
|
||||
for (K k : listValue) {
|
||||
if (!mapValue.containsKey(k)) throw new NoSuchElementException();
|
||||
if (!mapValue.containsKey(k)) throw new NoSuchElementException("Could not find required value: " + map.name() + "[" + k + "].");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -216,4 +252,10 @@ public final class GameData {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
|
||||
public record TypedValue<T>(Key<T> key, T value) {
|
||||
public static <T> TypedValue<T> entry(Key<T> key, T value) {
|
||||
return new TypedValue<>(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
|
@ -3,7 +3,7 @@ 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.GameState;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.core.model.Card;
|
||||
import eu.jonahbauer.wizard.core.model.Deck;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.model.deck.Deck;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
@ -19,7 +22,7 @@ public final class Dealing extends RoundState {
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
Deck deck = new Deck(game.getConfig().cards());
|
||||
deck.shuffle();
|
||||
deck.shuffle(game.getRandom());
|
||||
|
||||
var hands = new HashMap<UUID, List<Card>>();
|
||||
|
||||
@ -32,7 +35,7 @@ public final class Dealing extends RoundState {
|
||||
}
|
||||
|
||||
for (UUID player : get(PLAYERS)) {
|
||||
game.notify(player, new HandMessage(player, hands.get(player)));
|
||||
game.notify(player, new HandMessage(player, hands.get(player).stream().toList()));
|
||||
}
|
||||
|
||||
Card trumpCard = deck.draw();
|
||||
|
@ -1,34 +1,54 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
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.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 eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.model.card.GameCards;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
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.PICK_TRUMP;
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PICK_TRUMP;
|
||||
|
||||
public final class DeterminingTrump extends RoundState {
|
||||
private transient UUID player;
|
||||
private transient boolean werewolf;
|
||||
|
||||
public DeterminingTrump(GameData data) {
|
||||
super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
Card trumpCard = getData().get(TRUMP_CARD);
|
||||
Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE;
|
||||
Card trumpCard = get(TRUMP_CARD);
|
||||
|
||||
// handle werewolf
|
||||
for (Map.Entry<UUID, List<Card>> entry : get(HANDS).entrySet()) {
|
||||
var player = entry.getKey();
|
||||
var hand = entry.getValue();
|
||||
if (hand.contains(Card.WEREWOLF)) {
|
||||
this.player = player;
|
||||
this.werewolf = true;
|
||||
game.notify(new TrumpMessage(trumpCard, null));
|
||||
game.notify(new TrumpMessage(Card.WEREWOLF, null));
|
||||
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// default trump handling
|
||||
Card.Suit trumpSuit = trumpCard != null ? GameCards.get(trumpCard).getTrumpSuit() : Card.Suit.NONE;
|
||||
if (trumpSuit == null) {
|
||||
this.player = getDealer();
|
||||
game.notify(new TrumpMessage(trumpCard, null));
|
||||
game.notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(game, true)));
|
||||
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
} else {
|
||||
transition(game, trumpSuit);
|
||||
@ -38,12 +58,12 @@ public final class DeterminingTrump extends RoundState {
|
||||
@Override
|
||||
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)]);
|
||||
transition(game, suits[game.getRandom().nextInt(suits.length)]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (getDealer().equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
||||
if (this.player.equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
||||
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
||||
transition(game, trumpMessage.getTrumpSuit());
|
||||
} else {
|
||||
@ -51,21 +71,30 @@ public final class DeterminingTrump extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void checkTrumpSuit(Card.Suit suit) {
|
||||
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;
|
||||
private void checkTrumpSuit(Card.Suit suit) {
|
||||
if (suit == Card.Suit.NONE) {
|
||||
throw new IllegalArgumentException("Trump suit must not be " + Card.Suit.NONE + ".");
|
||||
}
|
||||
throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + ".");
|
||||
}
|
||||
|
||||
private void transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||
game.notify(new TrumpMessage(getData().get(TRUMP_CARD), trumpSuit));
|
||||
transition(game, new Predicting(getData().with(
|
||||
GameData data = getData().with(
|
||||
TRUMP_SUIT, trumpSuit,
|
||||
PREDICTIONS, Map.of(),
|
||||
CURRENT_PLAYER, getNextPlayer(getDealer())
|
||||
)));
|
||||
);
|
||||
|
||||
if (werewolf) {
|
||||
var mutableHands = new HashMap<>(get(HANDS));
|
||||
var mutableHand = new ArrayList<>(mutableHands.get(player));
|
||||
mutableHand.set(mutableHand.indexOf(Card.WEREWOLF), get(TRUMP_CARD));
|
||||
mutableHands.put(player, List.copyOf(mutableHand));
|
||||
data = data.with(HANDS, Map.copyOf(mutableHands));
|
||||
game.notify(new TrumpMessage(Card.WEREWOLF, trumpSuit));
|
||||
game.notify(player, new HandMessage(player, mutableHands.get(player)));
|
||||
} else {
|
||||
game.notify(new TrumpMessage(get(TRUMP_CARD), trumpSuit));
|
||||
}
|
||||
transition(game, new Predicting(data));
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,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.states.InvalidDataException;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Finishing;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -17,7 +15,6 @@ public final class FinishingRound extends RoundState {
|
||||
|
||||
public FinishingRound(GameData data) {
|
||||
super(data.requireEach(PLAYERS, PREDICTIONS).require(TRICKS, SCORE));
|
||||
checkData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -37,8 +34,7 @@ public final class FinishingRound extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Map<UUID, Integer> getPoints() {
|
||||
private Map<UUID, Integer> getPoints() {
|
||||
var points = new HashMap<UUID, Integer>();
|
||||
for (UUID player : get(PLAYERS)) {
|
||||
int prediction = get(PREDICTIONS).get(player);
|
||||
@ -52,12 +48,4 @@ 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 + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,18 @@ 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.states.trick.StartingTrick;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.PredictionMessage;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
||||
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 lombok.Getter;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
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;
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.MAKE_PREDICTION;
|
||||
|
||||
@Getter
|
||||
public final class Predicting extends RoundState {
|
||||
@ -25,7 +24,7 @@ public final class Predicting extends RoundState {
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(getData().get(CURRENT_PLAYER), MAKE_PREDICTION, getTimeout(game, true)));
|
||||
game.notify(new UserInputMessage(get(CURRENT_PLAYER), MAKE_PREDICTION, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
}
|
||||
|
||||
@ -46,7 +45,7 @@ public final class Predicting extends RoundState {
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (getData().get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
if (get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
checkPrediction(game, predictMessage.getPrediction());
|
||||
transition(game, predictMessage.getPrediction());
|
||||
} else {
|
||||
@ -54,8 +53,7 @@ public final class Predicting extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void checkPrediction(Game game, int prediction) {
|
||||
private 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) + ".");
|
||||
|
@ -13,6 +13,6 @@ public abstract class RoundState extends GameState {
|
||||
}
|
||||
|
||||
protected UUID getDealer() {
|
||||
return getPlayer(getData().get(ROUND) % getPlayerCount());
|
||||
return getPlayer(get(ROUND) % getPlayerCount());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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 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.common.messages.observer.UserInputMessage.Action.*;
|
||||
|
||||
public final class ChangingPrediction extends TrickState {
|
||||
private transient final int oldPrediction;
|
||||
|
||||
public ChangingPrediction(GameData data) {
|
||||
super(data);
|
||||
oldPrediction = get(PREDICTIONS).get(get(CURRENT_PLAYER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(get(CURRENT_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) {
|
||||
checkPrediction(predictMessage.getPrediction());
|
||||
transition(game, predictMessage.getPrediction());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
transition(game, oldPrediction + 1);
|
||||
}
|
||||
|
||||
private void checkPrediction(int prediction) {
|
||||
if (prediction < 0) {
|
||||
throw new IllegalArgumentException("Prediction must be greater than or equal to 0.");
|
||||
} else if (Math.abs(prediction - oldPrediction) != 1) {
|
||||
throw new IllegalArgumentException("Prediction must differ from your old prediction by exactly one.");
|
||||
}
|
||||
}
|
||||
|
||||
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().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));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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.FinishingRound;
|
||||
import eu.jonahbauer.wizard.core.messages.observer.TrickMessage;
|
||||
import eu.jonahbauer.wizard.core.model.Card;
|
||||
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;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -22,50 +24,54 @@ public final class FinishingTrick extends TrickState {
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
var winner = getWinner();
|
||||
game.notify(new TrickMessage(winner, get(STACK).stream().map(Pair::second).toList()));
|
||||
var stack = get(STACK);
|
||||
var cards = stack.stream().map(Pair::second).toList();
|
||||
|
||||
var tricks = new HashMap<>(get(TRICKS));
|
||||
tricks.merge(winner, 1, Integer::sum);
|
||||
var winner = CardUtils.getWinner(stack, get(TRUMP_SUIT));
|
||||
game.notify(new TrickMessage(winner, cards));
|
||||
|
||||
GameData data = getData().with(TRICKS, Map.copyOf(tricks));
|
||||
GameData data = getData();
|
||||
|
||||
if (get(TRICK) < get(ROUND)) {
|
||||
transition(game, new StartingTrick(data.with(
|
||||
CURRENT_PLAYER, winner,
|
||||
TRICK, get(TRICK) + 1
|
||||
)));
|
||||
boolean bomb = cards.contains(Card.BOMB);
|
||||
boolean juggler = cards.contains(Card.JUGGLER_BLUE)
|
||||
|| cards.contains(Card.JUGGLER_RED)
|
||||
|| cards.contains(Card.JUGGLER_GREEN)
|
||||
|| cards.contains(Card.JUGGLER_YELLOW);
|
||||
boolean cloud = cards.contains(Card.CLOUD_BLUE)
|
||||
|| cards.contains(Card.CLOUD_RED)
|
||||
|| cards.contains(Card.CLOUD_GREEN)
|
||||
|| 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) {
|
||||
var tricks = new HashMap<>(get(TRICKS));
|
||||
tricks.merge(winner, 1, Integer::sum);
|
||||
data = data.with(TRICKS, Map.copyOf(tricks));
|
||||
}
|
||||
|
||||
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)));
|
||||
} else {
|
||||
transition(game, new FinishingRound(data));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
UUID getWinner() {
|
||||
var wizard = get(STACK).stream()
|
||||
.filter(pair -> pair.second().getSuit() == Card.Suit.WIZARD)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (wizard != null) {
|
||||
return wizard.first();
|
||||
}
|
||||
|
||||
if (get(STACK).stream().allMatch(pair -> pair.second().getSuit() == Card.Suit.JESTER)) {
|
||||
return get(STACK).get(0).first();
|
||||
}
|
||||
|
||||
var trumpSuit = get(TRUMP_SUIT);
|
||||
var suit = getTrickSuit();
|
||||
return get(STACK).stream()
|
||||
.max(
|
||||
Comparator.<Pair<UUID, Card>>comparingInt(pair -> pair.second().getSuit() == trumpSuit ? 1 : 0)
|
||||
.thenComparing(pair -> pair.second().getSuit() == suit ? 1 : 0)
|
||||
.thenComparing(pair -> pair.second().getValue())
|
||||
)
|
||||
.orElseThrow(AssertionError::new)
|
||||
.first();
|
||||
}
|
||||
|
||||
private static void checkData(GameData data) {
|
||||
// every player has played exactly one card
|
||||
int stackSize = data.get(STACK).size();
|
||||
|
@ -1,21 +1,21 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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.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.common.messages.observer.CardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.model.card.GameCards;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PLAY_CARD;
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PLAY_CARD;
|
||||
|
||||
public final class PlayingCard extends TrickState {
|
||||
|
||||
@ -33,7 +33,6 @@ public final class PlayingCard extends TrickState {
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(CURRENT_PLAYER).equals(player) && message instanceof PlayCardMessage cardMessage) {
|
||||
checkCard(cardMessage.getCard());
|
||||
transition(game, cardMessage.getCard());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
@ -43,55 +42,43 @@ public final class PlayingCard extends TrickState {
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
var hand = get(HANDS).get(get(CURRENT_PLAYER));
|
||||
var card = hand.stream().filter(c -> {
|
||||
try {
|
||||
checkCard(c);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}).findAny().orElseThrow(() -> new AssertionError("Cannot play any card."));
|
||||
transition(game, card);
|
||||
}
|
||||
var stack = get(STACK);
|
||||
|
||||
@VisibleForTesting
|
||||
void checkCard(Card card) {
|
||||
var hand = get(HANDS).get(get(CURRENT_PLAYER));
|
||||
if (!hand.contains(card)) {
|
||||
throw new IllegalArgumentException("You do not have this card on your hand.");
|
||||
}
|
||||
var card = GameCards.values().stream()
|
||||
.filter(c -> c.isPlayable(hand, stack))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new AssertionError("Cannot play any card."));
|
||||
|
||||
Card.Suit suit = getTrickSuit();
|
||||
if (card.getSuit().isColor() && suit != null && card.getSuit() != suit) {
|
||||
if (hand.stream().anyMatch(c -> c.getSuit() == suit)) {
|
||||
throw new IllegalArgumentException("Must follow suit.");
|
||||
}
|
||||
}
|
||||
transition(game, card.getCard());
|
||||
}
|
||||
|
||||
private void transition(Game game, @NotNull Card card) {
|
||||
var currentPlayer = get(CURRENT_PLAYER);
|
||||
game.notify(new CardMessage(currentPlayer, card));
|
||||
|
||||
// add card to stack
|
||||
// create mutable stack
|
||||
var stack = get(STACK);
|
||||
var mutableStack = new ArrayList<Pair<UUID, Card>>(stack.size() + 1);
|
||||
mutableStack.addAll(stack);
|
||||
mutableStack.add(Pair.of(currentPlayer, card));
|
||||
|
||||
// remove card from hand
|
||||
// create mutable 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));
|
||||
|
||||
GameCards.get(card).play(currentPlayer, mutableHand, mutableStack);
|
||||
|
||||
// when card was played successfully
|
||||
game.notify(new CardMessage(currentPlayer, card));
|
||||
|
||||
// apply modifications
|
||||
mutableHands.put(currentPlayer, List.copyOf(mutableHand));
|
||||
GameData data = getData().with(
|
||||
STACK, List.copyOf(mutableStack),
|
||||
HANDS, Map.copyOf(mutableHands)
|
||||
);
|
||||
|
||||
// check whether the trick is finished
|
||||
var summary = data.get(HANDS).values().stream()
|
||||
.mapToInt(Collection::size)
|
||||
.summaryStatistics();
|
||||
|
@ -2,7 +2,6 @@ package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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.*;
|
||||
|
||||
@ -14,16 +13,4 @@ public abstract class TrickState extends RoundState {
|
||||
.require(TRUMP_SUIT, TRICK, TRICKS, CURRENT_PLAYER)
|
||||
);
|
||||
}
|
||||
|
||||
protected Card.Suit getTrickSuit() {
|
||||
for (var pair : get(STACK)) {
|
||||
Card.Suit suit = pair.second().getSuit();
|
||||
if (suit == Card.Suit.WIZARD) {
|
||||
return Card.Suit.NONE;
|
||||
} else if (suit.isColor()) {
|
||||
return suit;
|
||||
}
|
||||
}
|
||||
return Card.Suit.NONE;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.messages;
|
||||
|
||||
import eu.jonahbauer.wizard.core.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.messages.observer;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A {@link StateMessage} is sent whenever the {@link Game} changes its internal {@link GameState}.
|
||||
*/
|
||||
@Getter
|
||||
public final class StateMessage extends ObserverMessage {
|
||||
/**
|
||||
* The name of the new state in snake_case.
|
||||
*/
|
||||
private final String state;
|
||||
|
||||
public StateMessage(Class<? extends GameState> state) {
|
||||
if (state == null) {
|
||||
this.state = "null";
|
||||
} else {
|
||||
this.state = state.getSimpleName().replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Getter
|
||||
public enum Card {
|
||||
HIDDEN(0, Suit.NONE, Suit.NONE),
|
||||
RED_JESTER(0, Suit.JESTER, Suit.NONE),
|
||||
RED_1(1, Suit.RED, Suit.RED),
|
||||
RED_2(2, Suit.RED, Suit.RED),
|
||||
RED_3(3, Suit.RED, Suit.RED),
|
||||
RED_4(4, Suit.RED, Suit.RED),
|
||||
RED_5(5, Suit.RED, Suit.RED),
|
||||
RED_6(6, Suit.RED, Suit.RED),
|
||||
RED_7(7, Suit.RED, Suit.RED),
|
||||
RED_8(8, Suit.RED, Suit.RED),
|
||||
RED_9(9, Suit.RED, Suit.RED),
|
||||
RED_10(10, Suit.RED, Suit.RED),
|
||||
RED_11(11, Suit.RED, Suit.RED),
|
||||
RED_12(12, Suit.RED, Suit.RED),
|
||||
RED_13(13, Suit.RED, Suit.RED),
|
||||
RED_WIZARD(14, Suit.WIZARD, null),
|
||||
YELLOW_JESTER(0, Suit.JESTER, Suit.NONE),
|
||||
YELLOW_1(1, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_2(2, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_3(3, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_4(4, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_5(5, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_6(6, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_7(7, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_8(8, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_9(9, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_10(10, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_11(11, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_12(12, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_13(13, Suit.YELLOW, Suit.YELLOW),
|
||||
YELLOW_WIZARD(14, Suit.WIZARD, null),
|
||||
GREEN_JESTER(0, Suit.JESTER, Suit.NONE),
|
||||
GREEN_1(1, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_2(2, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_3(3, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_4(4, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_5(5, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_6(6, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_7(7, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_8(8, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_9(9, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_10(10, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_11(11, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_12(12, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_13(13, Suit.GREEN, Suit.GREEN),
|
||||
GREEN_WIZARD(14, Suit.WIZARD, null),
|
||||
BLUE_JESTER(0, Suit.JESTER, Suit.NONE),
|
||||
BLUE_1(1, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_2(2, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_3(3, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_4(4, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_5(5, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_6(6, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_7(7, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_8(8, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_9(9, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_10(10, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_11(11, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_12(12, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_13(13, Suit.BLUE, Suit.BLUE),
|
||||
BLUE_WIZARD(14, Suit.WIZARD, null);
|
||||
|
||||
private final int value;
|
||||
private final @NotNull Suit suit;
|
||||
private final @Nullable Suit trumpSuit;
|
||||
|
||||
Card(int value, @NotNull Suit suit, @Nullable Suit trumpSuit) {
|
||||
if (trumpSuit != null && !trumpSuit.isColor()) throw new IllegalArgumentException();
|
||||
this.value = value;
|
||||
this.suit = suit;
|
||||
this.trumpSuit = trumpSuit;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum Suit {
|
||||
NONE(true),
|
||||
YELLOW(true),
|
||||
RED(true),
|
||||
GREEN(true),
|
||||
BLUE(true),
|
||||
JESTER(false),
|
||||
WIZARD(false);
|
||||
|
||||
private final boolean color;
|
||||
|
||||
Suit(boolean color) {
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.model;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@UtilityClass
|
||||
public class Cards {
|
||||
public static final Set<Card> DEFAULT = Set.of(
|
||||
Card.RED_JESTER, Card.GREEN_JESTER, Card.YELLOW_JESTER, Card.BLUE_JESTER,
|
||||
Card.RED_1, Card.GREEN_1, Card.YELLOW_1, Card.BLUE_1,
|
||||
Card.RED_2, Card.GREEN_2, Card.YELLOW_2, Card.BLUE_2,
|
||||
Card.RED_3, Card.GREEN_3, Card.YELLOW_3, Card.BLUE_3,
|
||||
Card.RED_4, Card.GREEN_4, Card.YELLOW_4, Card.BLUE_4,
|
||||
Card.RED_5, Card.GREEN_5, Card.YELLOW_5, Card.BLUE_5,
|
||||
Card.RED_6, Card.GREEN_6, Card.YELLOW_6, Card.BLUE_6,
|
||||
Card.RED_7, Card.GREEN_7, Card.YELLOW_7, Card.BLUE_7,
|
||||
Card.RED_8, Card.GREEN_8, Card.YELLOW_8, Card.BLUE_8,
|
||||
Card.RED_9, Card.GREEN_9, Card.YELLOW_9, Card.BLUE_9,
|
||||
Card.RED_10, Card.GREEN_10, Card.YELLOW_10, Card.BLUE_10,
|
||||
Card.RED_11, Card.GREEN_11, Card.YELLOW_11, Card.BLUE_11,
|
||||
Card.RED_12, Card.GREEN_12, Card.YELLOW_12, Card.BLUE_12,
|
||||
Card.RED_13, Card.GREEN_13, Card.YELLOW_13, Card.BLUE_13,
|
||||
Card.RED_WIZARD, Card.GREEN_WIZARD, Card.YELLOW_WIZARD, Card.BLUE_WIZARD
|
||||
);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.model;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.model;
|
||||
|
||||
import eu.jonahbauer.wizard.core.model.deck.Decks;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -13,15 +14,45 @@ import java.util.NoSuchElementException;
|
||||
public class Configurations {
|
||||
private static final Map<String, Configuration> CONFIGURATIONS = new HashMap<>();
|
||||
|
||||
public static final Configuration DEFAULT = register("default", new Configuration(
|
||||
Cards.DEFAULT,
|
||||
public static final Configuration DEFAULT = register("DEFAULT", new Configuration(
|
||||
Decks.DEFAULT,
|
||||
true,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
public static final Configuration DEFAULT_PM1 = register("DEFAULT_PM1", new Configuration(
|
||||
Decks.DEFAULT,
|
||||
false,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
public static final Configuration ANNIVERSARY_2016 = register("ANNIVERSARY_2016", new Configuration(
|
||||
Decks.ANNIVERSARY_2016,
|
||||
true,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
public static final Configuration ANNIVERSARY_2016_PM1 = register("ANNIVERSARY_2016_PM1", new Configuration(
|
||||
Decks.ANNIVERSARY_2016,
|
||||
false,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
public static final Configuration ANNIVERSARY_2021 = register("ANNIVERSARY_2021", new Configuration(
|
||||
Decks.ANNIVERSARY_2021,
|
||||
true,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
public static final Configuration ANNIVERSARY_2021_PM1 = register("ANNIVERSARY_2021_PM1", new Configuration(
|
||||
Decks.ANNIVERSARY_2021,
|
||||
false,
|
||||
10 * 60 * 1000
|
||||
));
|
||||
|
||||
@Contract("_,_ -> param2")
|
||||
private static Configuration register(@NotNull String name, @NotNull Configuration configuration) {
|
||||
if (CONFIGURATIONS.putIfAbsent(name.toLowerCase(Locale.ROOT), configuration) != null) {
|
||||
if (CONFIGURATIONS.putIfAbsent(name.toUpperCase(Locale.ROOT), configuration) != null) {
|
||||
throw new IllegalArgumentException("Name already taken.");
|
||||
}
|
||||
return configuration;
|
||||
@ -29,7 +60,7 @@ public class Configurations {
|
||||
|
||||
@NotNull
|
||||
public static Configuration get(@NotNull String name) {
|
||||
var out = CONFIGURATIONS.get(name.toLowerCase(Locale.ROOT));
|
||||
var out = CONFIGURATIONS.get(name.toUpperCase(Locale.ROOT));
|
||||
if (out == null) throw new NoSuchElementException("Configuration with name '" + name + "' does not exist.");
|
||||
return out;
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@UtilityClass
|
||||
public class CardUtils {
|
||||
public Card.Suit getTrickSuit(List<Pair<UUID, Card>> stack) {
|
||||
for (var pair : stack) {
|
||||
Card.Suit suit = GameCards.get(pair.second()).getTrickSuit();
|
||||
|
||||
if (suit != null) {
|
||||
return suit;
|
||||
}
|
||||
}
|
||||
|
||||
return Card.Suit.NONE;
|
||||
}
|
||||
|
||||
public UUID getWinner(List<Pair<UUID, Card>> stack, Card.Suit trumpSuit) {
|
||||
var suit = getTrickSuit(stack);
|
||||
|
||||
Pair<UUID, Card> fairy = null, dragon = null;
|
||||
|
||||
for (Pair<UUID, Card> pair : stack) {
|
||||
Card card = pair.second();
|
||||
if (card == Card.FAIRY) {
|
||||
fairy = pair;
|
||||
} else if (card == Card.DRAGON) {
|
||||
dragon = pair;
|
||||
}
|
||||
}
|
||||
|
||||
if (fairy != null && dragon != null) {
|
||||
return fairy.first();
|
||||
} else if (dragon != null) {
|
||||
return dragon.first();
|
||||
}
|
||||
|
||||
Comparator<Pair<UUID, Card>> comparator = Comparator.comparingDouble(pair -> {
|
||||
GameCard card = GameCards.get(pair.second());
|
||||
if (card instanceof WizardCard) {
|
||||
return 1000;
|
||||
} else if (card instanceof JesterCard) {
|
||||
return 0;
|
||||
} else if (card == GameCards.BOMB) {
|
||||
return -1;
|
||||
} else if (card == GameCards.DRAGON) {
|
||||
return 2000;
|
||||
} else if (card == GameCards.FAIRY) {
|
||||
return -1;
|
||||
} else if (card instanceof ColoredCard coloredCard) {
|
||||
if (coloredCard.getSuit() == trumpSuit) {
|
||||
return 200 + coloredCard.getValue();
|
||||
} else if (coloredCard.getSuit() == suit) {
|
||||
return 100 + coloredCard.getValue();
|
||||
} else {
|
||||
return coloredCard.getValue();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Dont know how to handle " + pair.second() + ".");
|
||||
}
|
||||
});
|
||||
|
||||
return stack.stream()
|
||||
.max(comparator)
|
||||
.orElseThrow(() -> new RuntimeException("Could not determine trick winner."))
|
||||
.first();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChangelingJesterCard extends JesterCard implements Subcard {
|
||||
public ChangelingJesterCard(Card card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
GameCards.CHANGELING.checkHand(hand);
|
||||
hand.remove(Card.CHANGELING);
|
||||
stack.add(Pair.of(player, getCard()));
|
||||
}
|
||||
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
return hand.contains(Card.CHANGELING);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChangelingWizardCard extends WizardCard implements Subcard {
|
||||
public ChangelingWizardCard(Card card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
GameCards.CHANGELING.checkHand(hand);
|
||||
hand.remove(Card.CHANGELING);
|
||||
stack.add(Pair.of(player, getCard()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
return hand.contains(Card.CHANGELING);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class ColoredCard extends GameCard {
|
||||
private final double value;
|
||||
private final Card.Suit suit;
|
||||
|
||||
public ColoredCard(Card card, double value, Card.Suit suit) {
|
||||
super(card);
|
||||
this.value = value;
|
||||
this.suit = suit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
Card.Suit suit = CardUtils.getTrickSuit(stack);
|
||||
if (this.suit != suit && canFollowSuit(hand, suit)) {
|
||||
throw new IllegalArgumentException("Must follow suit.");
|
||||
}
|
||||
|
||||
super.play(player, hand, stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card.Suit getTrumpSuit() {
|
||||
return getSuit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card.Suit getTrickSuit() {
|
||||
return getSuit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
Card.Suit suit = CardUtils.getTrickSuit(stack);
|
||||
if (this.suit != suit && canFollowSuit(hand, suit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isPlayable(hand, stack);
|
||||
}
|
||||
|
||||
private boolean canFollowSuit(List<Card> hand, Card.Suit suit) {
|
||||
return hand.stream()
|
||||
.map(GameCards::get)
|
||||
.anyMatch(card -> card instanceof ColoredCard coloredCard && coloredCard.getSuit() == suit);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class ColoredSubcard extends ColoredCard implements Subcard {
|
||||
private final GameCard parent;
|
||||
|
||||
public ColoredSubcard(Card card, GameCard parent, double value, Card.Suit suit) {
|
||||
super(card, value, suit);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
parent.checkHand(hand);
|
||||
hand.remove(parent.getCard());
|
||||
stack.add(Pair.of(player, getCard()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
return hand.contains(parent.getCard());
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GameCard {
|
||||
private final Card card;
|
||||
|
||||
public abstract Card.Suit getTrumpSuit();
|
||||
public abstract Card.Suit getTrickSuit();
|
||||
|
||||
/**
|
||||
* Standard implementation of playing a card.
|
||||
* <br>
|
||||
* Removes the card from the hand checking that the hand contains it and then adds a new entry to the stack.
|
||||
* @param player the player playing this card
|
||||
* @param hand the player's hand cards
|
||||
* @param stack the stack
|
||||
*/
|
||||
@Contract(mutates = "param2, param3")
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
checkHand(hand);
|
||||
hand.remove(getCard());
|
||||
stack.add(Pair.of(player, getCard()));
|
||||
}
|
||||
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
return hand.contains(getCard());
|
||||
}
|
||||
|
||||
protected void checkHand(List<Card> hand) {
|
||||
if (!hand.contains(getCard())) {
|
||||
throw new IllegalArgumentException("You do not have this card on your hand.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@UtilityClass
|
||||
@SuppressWarnings("unused")
|
||||
public class GameCards {
|
||||
private static final Map<Card, GameCard> CARDS = new HashMap<>();
|
||||
|
||||
public final GameCard HIDDEN = register(new NonPlayableCard(Card.HIDDEN, null));
|
||||
|
||||
public final GameCard BLUE_1 = register(new ColoredCard(Card.BLUE_1, 1, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_2 = register(new ColoredCard(Card.BLUE_2, 2, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_3 = register(new ColoredCard(Card.BLUE_3, 3, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_4 = register(new ColoredCard(Card.BLUE_4, 4, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_5 = register(new ColoredCard(Card.BLUE_5, 5, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_6 = register(new ColoredCard(Card.BLUE_6, 6, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_7 = register(new ColoredCard(Card.BLUE_7, 7, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_8 = register(new ColoredCard(Card.BLUE_8, 8, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_9 = register(new ColoredCard(Card.BLUE_9, 9, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_10 = register(new ColoredCard(Card.BLUE_10, 10, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_11 = register(new ColoredCard(Card.BLUE_11, 11, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_12 = register(new ColoredCard(Card.BLUE_12, 12, Card.Suit.BLUE));
|
||||
public final GameCard BLUE_13 = register(new ColoredCard(Card.BLUE_13, 13, Card.Suit.BLUE));
|
||||
|
||||
public final GameCard RED_1 = register(new ColoredCard(Card.RED_1, 1, Card.Suit.RED));
|
||||
public final GameCard RED_2 = register(new ColoredCard(Card.RED_2, 2, Card.Suit.RED));
|
||||
public final GameCard RED_3 = register(new ColoredCard(Card.RED_3, 3, Card.Suit.RED));
|
||||
public final GameCard RED_4 = register(new ColoredCard(Card.RED_4, 4, Card.Suit.RED));
|
||||
public final GameCard RED_5 = register(new ColoredCard(Card.RED_5, 5, Card.Suit.RED));
|
||||
public final GameCard RED_6 = register(new ColoredCard(Card.RED_6, 6, Card.Suit.RED));
|
||||
public final GameCard RED_7 = register(new ColoredCard(Card.RED_7, 7, Card.Suit.RED));
|
||||
public final GameCard RED_8 = register(new ColoredCard(Card.RED_8, 8, Card.Suit.RED));
|
||||
public final GameCard RED_9 = register(new ColoredCard(Card.RED_9, 9, Card.Suit.RED));
|
||||
public final GameCard RED_10 = register(new ColoredCard(Card.RED_10, 10, Card.Suit.RED));
|
||||
public final GameCard RED_11 = register(new ColoredCard(Card.RED_11, 11, Card.Suit.RED));
|
||||
public final GameCard RED_12 = register(new ColoredCard(Card.RED_12, 12, Card.Suit.RED));
|
||||
public final GameCard RED_13 = register(new ColoredCard(Card.RED_13, 13, Card.Suit.RED));
|
||||
|
||||
public final GameCard GREEN_1 = register(new ColoredCard(Card.GREEN_1, 1, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_2 = register(new ColoredCard(Card.GREEN_2, 2, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_3 = register(new ColoredCard(Card.GREEN_3, 3, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_4 = register(new ColoredCard(Card.GREEN_4, 4, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_5 = register(new ColoredCard(Card.GREEN_5, 5, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_6 = register(new ColoredCard(Card.GREEN_6, 6, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_7 = register(new ColoredCard(Card.GREEN_7, 7, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_8 = register(new ColoredCard(Card.GREEN_8, 8, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_9 = register(new ColoredCard(Card.GREEN_9, 9, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_10 = register(new ColoredCard(Card.GREEN_10, 10, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_11 = register(new ColoredCard(Card.GREEN_11, 11, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_12 = register(new ColoredCard(Card.GREEN_12, 12, Card.Suit.GREEN));
|
||||
public final GameCard GREEN_13 = register(new ColoredCard(Card.GREEN_13, 13, Card.Suit.GREEN));
|
||||
|
||||
public final GameCard YELLOW_1 = register(new ColoredCard(Card.YELLOW_1, 1, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_2 = register(new ColoredCard(Card.YELLOW_2, 2, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_3 = register(new ColoredCard(Card.YELLOW_3, 3, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_4 = register(new ColoredCard(Card.YELLOW_4, 4, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_5 = register(new ColoredCard(Card.YELLOW_5, 5, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_6 = register(new ColoredCard(Card.YELLOW_6, 6, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_7 = register(new ColoredCard(Card.YELLOW_7, 7, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_8 = register(new ColoredCard(Card.YELLOW_8, 8, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_9 = register(new ColoredCard(Card.YELLOW_9, 9, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_10 = register(new ColoredCard(Card.YELLOW_10, 10, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_11 = register(new ColoredCard(Card.YELLOW_11, 11, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_12 = register(new ColoredCard(Card.YELLOW_12, 12, Card.Suit.YELLOW));
|
||||
public final GameCard YELLOW_13 = register(new ColoredCard(Card.YELLOW_13, 13, Card.Suit.YELLOW));
|
||||
|
||||
public final GameCard BLUE_WIZARD = register(new WizardCard(Card.BLUE_WIZARD));
|
||||
public final GameCard RED_WIZARD = register(new WizardCard(Card.RED_WIZARD));
|
||||
public final GameCard GREEN_WIZARD = register(new WizardCard(Card.GREEN_WIZARD));
|
||||
public final GameCard YELLOW_WIZARD = register(new WizardCard(Card.YELLOW_WIZARD));
|
||||
|
||||
public final GameCard BLUE_JESTER = register(new JesterCard(Card.BLUE_JESTER));
|
||||
public final GameCard RED_JESTER = register(new JesterCard(Card.RED_JESTER));
|
||||
public final GameCard GREEN_JESTER = register(new JesterCard(Card.GREEN_JESTER));
|
||||
public final GameCard YELLOW_JESTER = register(new JesterCard(Card.YELLOW_JESTER));
|
||||
|
||||
public final GameCard CHANGELING = register(new NonPlayableCard(Card.CHANGELING, null));
|
||||
public final GameCard CHANGELING_WIZARD = register(new ChangelingWizardCard(Card.CHANGELING_WIZARD));
|
||||
public final GameCard CHANGELING_JESTER = register(new ChangelingJesterCard(Card.CHANGELING_JESTER));
|
||||
|
||||
public final GameCard BOMB = register(new SimpleCard(Card.BOMB, Card.Suit.NONE, null));
|
||||
public final GameCard WEREWOLF = register(new NonPlayableCard(Card.WEREWOLF, null));
|
||||
public final GameCard DRAGON = register(new SimpleCard(Card.DRAGON, null, Card.Suit.NONE));
|
||||
public final GameCard FAIRY = register(new SimpleCard(Card.FAIRY, Card.Suit.NONE, null));
|
||||
|
||||
public final GameCard CLOUD = register(new NonPlayableCard(Card.CLOUD, null));
|
||||
public final GameCard CLOUD_BLUE = register(new ColoredSubcard(Card.CLOUD_BLUE, CLOUD, 9.75, Card.Suit.BLUE));
|
||||
public final GameCard CLOUD_RED = register(new ColoredSubcard(Card.CLOUD_RED, CLOUD, 9.75, Card.Suit.RED));
|
||||
public final GameCard CLOUD_GREEN = register(new ColoredSubcard(Card.CLOUD_GREEN, CLOUD, 9.75, Card.Suit.GREEN));
|
||||
public final GameCard CLOUD_YELLOW = register(new ColoredSubcard(Card.CLOUD_YELLOW, CLOUD, 9.75, Card.Suit.YELLOW));
|
||||
|
||||
public final GameCard JUGGLER = register(new NonPlayableCard(Card.JUGGLER, null));
|
||||
public final GameCard JUGGLER_BLUE = register(new ColoredSubcard(Card.JUGGLER_BLUE, JUGGLER, 7.5, Card.Suit.BLUE));
|
||||
public final GameCard JUGGLER_RED = register(new ColoredSubcard(Card.JUGGLER_RED, JUGGLER, 7.5, Card.Suit.RED));
|
||||
public final GameCard JUGGLER_GREEN = register(new ColoredSubcard(Card.JUGGLER_GREEN, JUGGLER, 7.5, Card.Suit.GREEN));
|
||||
public final GameCard JUGGLER_YELLOW = register(new ColoredSubcard(Card.JUGGLER_YELLOW, JUGGLER, 7.5, Card.Suit.YELLOW));
|
||||
|
||||
@Contract("_ -> param1")
|
||||
private static GameCard register(@NotNull GameCard card) {
|
||||
if (CARDS.containsKey(card.getCard())) {
|
||||
throw new IllegalArgumentException("Name already taken.");
|
||||
}
|
||||
|
||||
CARDS.put(card.getCard(), card);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static GameCard get(@NotNull Card name) {
|
||||
return CARDS.get(name);
|
||||
}
|
||||
|
||||
public static Collection<GameCard> values() {
|
||||
return Collections.unmodifiableCollection(CARDS.values());
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
|
||||
public class JesterCard extends SimpleCard {
|
||||
public JesterCard(Card card) {
|
||||
super(card, Card.Suit.NONE, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public final class NonPlayableCard extends GameCard {
|
||||
private final Card.Suit suit;
|
||||
|
||||
public NonPlayableCard(Card card, Card.Suit suit) {
|
||||
super(card);
|
||||
this.suit = suit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
throw new IllegalArgumentException("This card cannot be played.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card.Suit getTrumpSuit() {
|
||||
return getSuit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card.Suit getTrickSuit() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayable(List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class SimpleCard extends GameCard {
|
||||
private final Card.Suit trumpSuit;
|
||||
private final Card.Suit trickSuit;
|
||||
|
||||
public SimpleCard(Card card, Card.Suit trumpSuit, Card.Suit trickSuit) {
|
||||
super(card);
|
||||
this.trumpSuit = trumpSuit;
|
||||
this.trickSuit = trickSuit;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
/**
|
||||
* Marker interface for subcards. A subcard is an internal variant of another card. It contains additional information
|
||||
* that would otherwise need to be provided in a seperate message.
|
||||
* <br>
|
||||
* A subcard may not be on any player's hand.
|
||||
* <br><br>
|
||||
* For example: when a player has the {@link GameCards#CLOUD} on his hand, he may not play the card directly but can only
|
||||
* play one of its subcards {@link GameCards#CLOUD_BLUE}, {@link GameCards#CLOUD_GREEN}, {@link GameCards#CLOUD_RED} or
|
||||
* {@link GameCards#CLOUD_YELLOW}.
|
||||
*/
|
||||
public interface Subcard {
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
|
||||
public class WizardCard extends SimpleCard {
|
||||
public WizardCard(Card card) {
|
||||
super(card, null, Card.Suit.NONE);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.model;
|
||||
package eu.jonahbauer.wizard.core.model.deck;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
@ -13,8 +14,8 @@ public final class Deck {
|
||||
this.cards.addAll(cards);
|
||||
}
|
||||
|
||||
public void shuffle() {
|
||||
Collections.shuffle(cards);
|
||||
public void shuffle(Random random) {
|
||||
Collections.shuffle(cards, random);
|
||||
next = 0;
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package eu.jonahbauer.wizard.core.model.deck;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@UtilityClass
|
||||
public class Decks {
|
||||
public static final Set<Card> DEFAULT = Collections.unmodifiableSet(new LinkedHashSet<>(List.of(
|
||||
Card.BLUE_1, Card.RED_1, Card.GREEN_1, Card.YELLOW_1,
|
||||
Card.BLUE_2, Card.RED_2, Card.GREEN_2, Card.YELLOW_2,
|
||||
Card.BLUE_3, Card.RED_3, Card.GREEN_3, Card.YELLOW_3,
|
||||
Card.BLUE_4, Card.RED_4, Card.GREEN_4, Card.YELLOW_4,
|
||||
Card.BLUE_5, Card.RED_5, Card.GREEN_5, Card.YELLOW_5,
|
||||
Card.BLUE_6, Card.RED_6, Card.GREEN_6, Card.YELLOW_6,
|
||||
Card.BLUE_7, Card.RED_7, Card.GREEN_7, Card.YELLOW_7,
|
||||
Card.BLUE_8, Card.RED_8, Card.GREEN_8, Card.YELLOW_8,
|
||||
Card.BLUE_9, Card.RED_9, Card.GREEN_9, Card.YELLOW_9,
|
||||
Card.BLUE_10, Card.RED_10, Card.GREEN_10, Card.YELLOW_10,
|
||||
Card.BLUE_11, Card.RED_11, Card.GREEN_11, Card.YELLOW_11,
|
||||
Card.BLUE_12, Card.RED_12, Card.GREEN_12, Card.YELLOW_12,
|
||||
Card.BLUE_13, Card.RED_13, Card.GREEN_13, Card.YELLOW_13,
|
||||
Card.BLUE_JESTER, Card.RED_JESTER, Card.GREEN_JESTER, Card.YELLOW_JESTER,
|
||||
Card.BLUE_WIZARD, Card.RED_WIZARD, Card.GREEN_WIZARD, Card.YELLOW_WIZARD
|
||||
)));
|
||||
|
||||
public static final Set<Card> ANNIVERSARY_2016 = Collections.unmodifiableSet(new LinkedHashSet<>(List.of(
|
||||
Card.BLUE_1, Card.RED_1, Card.GREEN_1, Card.YELLOW_1,
|
||||
Card.BLUE_2, Card.RED_2, Card.GREEN_2, Card.YELLOW_2,
|
||||
Card.BLUE_3, Card.RED_3, Card.GREEN_3, Card.YELLOW_3,
|
||||
Card.BLUE_4, Card.RED_4, Card.GREEN_4, Card.YELLOW_4,
|
||||
Card.BLUE_5, Card.RED_5, Card.GREEN_5, Card.YELLOW_5,
|
||||
Card.BLUE_6, Card.RED_6, Card.GREEN_6, Card.YELLOW_6,
|
||||
Card.BLUE_7, Card.RED_7, Card.GREEN_7, Card.YELLOW_7,
|
||||
Card.BLUE_8, Card.RED_8, Card.GREEN_8, Card.YELLOW_8,
|
||||
Card.BLUE_9, Card.RED_9, Card.GREEN_9, Card.YELLOW_9,
|
||||
Card.BLUE_10, Card.RED_10, Card.GREEN_10, Card.YELLOW_10,
|
||||
Card.BLUE_11, Card.RED_11, Card.GREEN_11, Card.YELLOW_11,
|
||||
Card.BLUE_12, Card.RED_12, Card.GREEN_12, Card.YELLOW_12,
|
||||
Card.BLUE_13, Card.RED_13, Card.GREEN_13, Card.YELLOW_13,
|
||||
Card.BLUE_JESTER, Card.RED_JESTER, Card.GREEN_JESTER, Card.YELLOW_JESTER,
|
||||
Card.BLUE_WIZARD, Card.RED_WIZARD, Card.GREEN_WIZARD, Card.YELLOW_WIZARD,
|
||||
Card.WEREWOLF, Card.BOMB, Card.DRAGON, Card.FAIRY, Card.JUGGLER, Card.CLOUD
|
||||
)));
|
||||
|
||||
public static final Set<Card> ANNIVERSARY_2021 = Collections.unmodifiableSet(new LinkedHashSet<>(List.of(
|
||||
Card.BLUE_1, Card.RED_1, Card.GREEN_1, Card.YELLOW_1,
|
||||
Card.BLUE_2, Card.RED_2, Card.GREEN_2, Card.YELLOW_2,
|
||||
Card.BLUE_3, Card.RED_3, Card.GREEN_3, Card.YELLOW_3,
|
||||
Card.BLUE_4, Card.RED_4, Card.GREEN_4, Card.YELLOW_4,
|
||||
Card.BLUE_5, Card.RED_5, Card.GREEN_5, Card.YELLOW_5,
|
||||
Card.BLUE_6, Card.RED_6, Card.GREEN_6, Card.YELLOW_6,
|
||||
Card.BLUE_7, Card.RED_7, Card.GREEN_7, Card.YELLOW_7,
|
||||
Card.BLUE_8, Card.RED_8, Card.GREEN_8, Card.YELLOW_8,
|
||||
Card.BLUE_9, Card.RED_9, Card.GREEN_9, Card.YELLOW_9,
|
||||
Card.BLUE_10, Card.RED_10, Card.GREEN_10, Card.YELLOW_10,
|
||||
Card.BLUE_11, Card.RED_11, Card.GREEN_11, Card.YELLOW_11,
|
||||
Card.BLUE_12, Card.RED_12, Card.GREEN_12, Card.YELLOW_12,
|
||||
Card.BLUE_13, Card.RED_13, Card.GREEN_13, Card.YELLOW_13,
|
||||
Card.BLUE_JESTER, Card.RED_JESTER, Card.GREEN_JESTER, Card.YELLOW_JESTER,
|
||||
Card.BLUE_WIZARD, Card.RED_WIZARD, Card.GREEN_WIZARD, Card.YELLOW_WIZARD,
|
||||
Card.WEREWOLF, Card.BOMB, Card.DRAGON, Card.FAIRY, Card.JUGGLER, Card.CLOUD, Card.CHANGELING
|
||||
)));
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package eu.jonahbauer.wizard.core.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@UtilityClass
|
||||
public class Util {
|
||||
public static String toSnakeCase(String str) {
|
||||
return str.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
@ -1,16 +1,57 @@
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.api.RepetitionInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class GameTest {
|
||||
@Test
|
||||
public void run() throws InterruptedException, ExecutionException {
|
||||
Game game = new Game(Configurations.DEFAULT.withTimeout(0), (player, msg) -> System.out.println(msg));
|
||||
@RepeatedTest(3)
|
||||
public void runDefault(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
|
||||
Game game = new Game(
|
||||
repetitionInfo.getCurrentRepetition(),
|
||||
Configurations.DEFAULT.withTimeout(0),
|
||||
(player, msg) -> System.out.println(msg)
|
||||
);
|
||||
var players = List.of(
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID()
|
||||
);
|
||||
|
||||
game.start(players);
|
||||
game.await();
|
||||
}
|
||||
|
||||
@RepeatedTest(3)
|
||||
public void runAnniversary2016(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
|
||||
Game game = new Game(
|
||||
repetitionInfo.getCurrentRepetition(),
|
||||
Configurations.ANNIVERSARY_2016.withTimeout(0),
|
||||
(player, msg) -> System.out.println(msg)
|
||||
);
|
||||
var players = List.of(
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID()
|
||||
);
|
||||
|
||||
game.start(players);
|
||||
game.await();
|
||||
}
|
||||
|
||||
@RepeatedTest(3)
|
||||
public void runAnniversary2021(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
|
||||
Game game = new Game(
|
||||
repetitionInfo.getCurrentRepetition(),
|
||||
Configurations.ANNIVERSARY_2021.withTimeout(0),
|
||||
(player, msg) -> System.out.println(msg)
|
||||
);
|
||||
var players = List.of(
|
||||
UUID.randomUUID(),
|
||||
UUID.randomUUID(),
|
||||
|
@ -0,0 +1,30 @@
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.mockito.stubbing.Stubber;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UtilityClass
|
||||
public class GameTestUtils {
|
||||
public static Map<UUID, Integer> emptyToIntMap(List<UUID> players) {
|
||||
return players.stream().collect(Collectors.toUnmodifiableMap(player -> player, player -> 0));
|
||||
}
|
||||
|
||||
public static Stubber doFinish() {
|
||||
return Mockito.doAnswer(finish());
|
||||
}
|
||||
|
||||
public static <T> Answer<T> finish() {
|
||||
return invocation -> {
|
||||
Game game = (Game) invocation.getMock();
|
||||
game.finish();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
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.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* A queue of {@link PlayerMessage}s that will automatically be sent to a {@link Game} when
|
||||
* {@linkplain UserInputMessage input is required}. The {@code PlayerMessage}s are ordered and it is asserted that
|
||||
* the incoming {@code UserInputMessage}s match the given {@code PlayerMessage}s.
|
||||
*/
|
||||
public class MessageQueue implements Observer {
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final LinkedList<QueuedMessage> messages = new LinkedList<>();
|
||||
@Setter
|
||||
private Game game;
|
||||
|
||||
public MessageQueue add(UUID player, UserInputMessage.Action action, PlayerMessage message) {
|
||||
messages.add(new QueuedMessage(player, action, message));
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageQueue addCard(UUID player, Card card) {
|
||||
return add(player, UserInputMessage.Action.PLAY_CARD, new PlayCardMessage(card));
|
||||
}
|
||||
|
||||
public MessageQueue addPrediction(UUID player, int prediction) {
|
||||
return add(player, UserInputMessage.Action.MAKE_PREDICTION, new PredictMessage(prediction));
|
||||
}
|
||||
|
||||
public MessageQueue addChangePrediction(UUID player, int prediction) {
|
||||
return add(player, UserInputMessage.Action.CHANGE_PREDICTION, new PredictMessage(prediction));
|
||||
}
|
||||
|
||||
public MessageQueue addPickTrump(UUID player, Card.Suit trumpSuit) {
|
||||
return add(player, UserInputMessage.Action.PICK_TRUMP, new PickTrumpMessage(trumpSuit));
|
||||
}
|
||||
|
||||
public MessageQueue addCards(List<UUID> players, Map<UUID, List<Card>> cards, int startPlayer) {
|
||||
Map<UUID, Integer> nextCards = new HashMap<>();
|
||||
int size = players.size();
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (int i = 0; i < size; i++) {
|
||||
var player = players.get((i + startPlayer) % size);
|
||||
var hand = cards.get(player);
|
||||
|
||||
int next = nextCards.getOrDefault(player, 0);
|
||||
|
||||
if (next < hand.size()) {
|
||||
nextCards.merge(player, 1, Integer::sum);
|
||||
var card = hand.get(next);
|
||||
addCard(player, card);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageQueue assertThrows(Class<? extends Exception> exception) {
|
||||
messages.getLast().setException(exception);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void doNotify(ObserverMessage om) {
|
||||
try {
|
||||
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.");
|
||||
|
||||
var queued = messages.poll();
|
||||
|
||||
var queuedPlayer = queued.getPlayer();
|
||||
var queuedAction = queued.getAction();
|
||||
var queuedMessage = queued.getMessage();
|
||||
var exception = queued.getException();
|
||||
|
||||
if (exception == null) {
|
||||
assertEquals(queuedPlayer, player);
|
||||
assertEquals(queuedAction, message.getAction());
|
||||
}
|
||||
|
||||
System.out.println(queuedPlayer + ": " + queuedMessage);
|
||||
if (exception != null) {
|
||||
var executionException = Assertions.assertThrows(
|
||||
ExecutionException.class,
|
||||
() -> game.onMessage(queuedPlayer, queuedMessage).get(),
|
||||
"Excepted exception for message " + queuedMessage + " from player " + queuedPlayer + "."
|
||||
);
|
||||
assertInstanceOf(
|
||||
exception,
|
||||
executionException.getCause(),
|
||||
"Excepted exception for message " + queuedMessage + " from player " + queuedPlayer + "."
|
||||
);
|
||||
} else {
|
||||
Assertions.assertDoesNotThrow(() -> game.onMessage(queuedPlayer, queuedMessage).get());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
game.finish(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(ObserverMessage message) {
|
||||
executor.execute(() -> doNotify(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(UUID player, ObserverMessage message) {
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@RequiredArgsConstructor
|
||||
private static class QueuedMessage {
|
||||
private final UUID player;
|
||||
private final UserInputMessage.Action action;
|
||||
private final PlayerMessage message;
|
||||
private Class<? extends Exception> exception;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class GameStateTest {
|
||||
|
||||
@Test
|
||||
public void getNextPlayer_IsPermutation() {
|
||||
List<UUID> players = List.of(
|
||||
new UUID(0, 1),
|
||||
new UUID(0, 2),
|
||||
new UUID(0, 3),
|
||||
new UUID(0, 4),
|
||||
new UUID(0, 5),
|
||||
new UUID(0, 6)
|
||||
);
|
||||
|
||||
GameState gameState = mock(GameState.class);
|
||||
when(gameState.getNextPlayer(any())).thenCallRealMethod();
|
||||
when(gameState.get(GameData.PLAYERS)).thenReturn(players);
|
||||
|
||||
Set<UUID> a = new HashSet<>(players);
|
||||
Set<UUID> b = new HashSet<>();
|
||||
|
||||
a.forEach(player -> b.add(gameState.getNextPlayer(player)));
|
||||
|
||||
assertEquals(a, b);
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.MessageQueue;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.model.Configuration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class DeterminingTrumpTest {
|
||||
private final UUID[] players = new UUID[] {
|
||||
new UUID(0, 0),
|
||||
new UUID(0, 1),
|
||||
new UUID(0, 2),
|
||||
new UUID(0, 3)
|
||||
};
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private Game performTest(Configuration configuration, int round, Map<UUID, List<Card>> hands, Card trumpCard, MessageQueue queue) {
|
||||
Game game = spy(new Game(configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(Predicting.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
|
||||
GameData data = GameData.EMPTY.with(
|
||||
entry(PLAYERS, playerList),
|
||||
entry(ROUND, round),
|
||||
entry(SCORE, Map.of()),
|
||||
entry(TRUMP_CARD, trumpCard),
|
||||
entry(HANDS, hands)
|
||||
);
|
||||
|
||||
game.resume(new DeterminingTrump(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
return game;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_Simple() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.CLOUD),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue();
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.YELLOW_1, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining trump
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.YELLOW_1 && trump.getSuit() == Card.Suit.YELLOW));
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_Simple2() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.CLOUD),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue();
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_JESTER, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining trump
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.GREEN_JESTER && trump.getSuit() == Card.Suit.NONE));
|
||||
order.verify(game).transition(any(), any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithUserInput() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.CLOUD),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPickTrump(players[0], Card.Suit.GREEN);
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.BLUE_WIZARD, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining trump
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.BLUE_WIZARD && trump.getSuit() == null));
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.BLUE_WIZARD && trump.getSuit() == Card.Suit.GREEN));
|
||||
order.verify(game).transition(any(), any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithWerewolf() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.CLOUD),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.WEREWOLF)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPickTrump(players[3], Card.Suit.YELLOW);
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_1, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining trump
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.GREEN_1 && trump.getSuit() == null));
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.WEREWOLF && trump.getSuit() == null));
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrumpMessage trump && trump.getCard() == Card.WEREWOLF && trump.getSuit() == Card.Suit.YELLOW));
|
||||
order.verify(game).notify(eq(players[3]), any(HandMessage.class)); // swap trump card and werewolf
|
||||
order.verify(game).transition(any(), any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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;
|
||||
|
||||
public class FinishingRoundTest {
|
||||
@Test
|
||||
public void getPoints() {
|
||||
UUID player0 = new UUID(0, 0);
|
||||
UUID player1 = new UUID(0, 1);
|
||||
UUID player2 = new UUID(0, 2);
|
||||
|
||||
List<UUID> players = List.of(
|
||||
player0,
|
||||
player1,
|
||||
player2
|
||||
);
|
||||
|
||||
Map<UUID, Integer> predictions = Map.of(
|
||||
player0, 0,
|
||||
player1, 3,
|
||||
player2, 3
|
||||
);
|
||||
|
||||
Map<UUID, Integer> tricks = Map.of(
|
||||
player0, 0,
|
||||
player1, 3,
|
||||
player2, 5
|
||||
);
|
||||
|
||||
FinishingRound state = mock(FinishingRound.class);
|
||||
when(state.getPoints()).thenCallRealMethod();
|
||||
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||
when(state.get(TRICKS)).thenReturn(tricks);
|
||||
when(state.get(PLAYERS)).thenReturn(players);
|
||||
|
||||
assertEquals(Map.of(
|
||||
player0, 20,
|
||||
player1, 50,
|
||||
player2, -20
|
||||
), state.getPoints());
|
||||
}
|
||||
}
|
@ -1,65 +1,170 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameTestUtils;
|
||||
import eu.jonahbauer.wizard.core.machine.MessageQueue;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
|
||||
import eu.jonahbauer.wizard.core.model.Configuration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class PredictingTest {
|
||||
@Test
|
||||
public void checkPrediction_ThrowsIllegalArgument_IfOutOfBounds() {
|
||||
Map<UUID, Integer> predictions = Map.of();
|
||||
private final UUID[] players = new UUID[] {
|
||||
new UUID(0, 0),
|
||||
new UUID(0, 1),
|
||||
new UUID(0, 2),
|
||||
new UUID(0, 3)
|
||||
};
|
||||
|
||||
Configuration config = mock(Configuration.class);
|
||||
when(config.allowExactPredictions()).thenReturn(true);
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private Game performTest(Configuration configuration, int round, MessageQueue queue) {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], Collections.nCopies(round + 1, Card.HIDDEN),
|
||||
players[1], Collections.nCopies(round + 1, Card.HIDDEN),
|
||||
players[2], Collections.nCopies(round + 1, Card.HIDDEN),
|
||||
players[3], Collections.nCopies(round + 1, Card.HIDDEN)
|
||||
);
|
||||
|
||||
Game game = mock(Game.class);
|
||||
when(game.getConfig()).thenReturn(config);
|
||||
Game game = spy(new Game(configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(StartingTrick.class));
|
||||
queue.setGame(game);
|
||||
|
||||
Predicting state = mock(Predicting.class);
|
||||
doCallRealMethod().when(state).checkPrediction(any(), anyInt());
|
||||
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||
when(state.getDealer()).thenReturn(new UUID(0,0));
|
||||
when(state.get(CURRENT_PLAYER)).thenReturn(new UUID(0,1));
|
||||
when(state.get(ROUND)).thenReturn(10);
|
||||
var playerList = List.of(players);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, -1));
|
||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 12));
|
||||
GameData data = GameData.EMPTY.with(
|
||||
entry(PLAYERS, playerList),
|
||||
entry(ROUND, round),
|
||||
entry(SCORE, Map.of()),
|
||||
entry(TRUMP_SUIT, Card.Suit.NONE),
|
||||
entry(PREDICTIONS, GameTestUtils.emptyToIntMap(playerList)),
|
||||
entry(HANDS, hands),
|
||||
entry(CURRENT_PLAYER, players[(round + 1) % players.length])
|
||||
);
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
state.checkPrediction(game, i);
|
||||
}
|
||||
game.resume(new Predicting(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
return game;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkPrediction_ThrowsIllegalArgument_IfAddsUp() {
|
||||
UUID player = new UUID(0,0);
|
||||
Map<UUID, Integer> predictions = Map.of(new UUID(0,1), 10);
|
||||
public void predicting_Simple() {
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPrediction(players[0], 4)
|
||||
.addPrediction(players[1], 3)
|
||||
.addPrediction(players[2], 3)
|
||||
.addPrediction(players[3], 0);
|
||||
|
||||
Configuration config = mock(Configuration.class);
|
||||
when(config.allowExactPredictions()).thenReturn(false);
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
|
||||
|
||||
Game game = mock(Game.class);
|
||||
when(game.getConfig()).thenReturn(config);
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // predicting
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(StartingTrick.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
Predicting state = mock(Predicting.class);
|
||||
doCallRealMethod().when(state).checkPrediction(any(), anyInt());
|
||||
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||
when(state.getDealer()).thenReturn(player);
|
||||
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||
when(state.get(ROUND)).thenReturn(10);
|
||||
@Test
|
||||
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);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 1));
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (i == 1) continue;
|
||||
state.checkPrediction(game, i);
|
||||
}
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // predicting
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(StartingTrick.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void predicting_AddUp() {
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPrediction(players[0], 1)
|
||||
.addPrediction(players[1], 1)
|
||||
.addPrediction(players[2], 1)
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[3], 0);
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021_PM1, 3, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // predicting
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(Predicting.class)); // next player
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(StartingTrick.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,241 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.MessageQueue;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.model.Configuration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class RoundTest {
|
||||
private final UUID[] players = new UUID[] {
|
||||
new UUID(0, 0),
|
||||
new UUID(0, 1),
|
||||
new UUID(0, 2),
|
||||
new UUID(0, 3)
|
||||
};
|
||||
|
||||
@SneakyThrows
|
||||
private Game performTest(long seed, Configuration configuration, int round, MessageQueue queue) {
|
||||
Game game = spy(new Game(seed, configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(StartingRound.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
|
||||
GameData data = GameData.EMPTY.with(
|
||||
entry(PLAYERS, playerList),
|
||||
entry(ROUND, round),
|
||||
entry(SCORE, Map.of())
|
||||
);
|
||||
|
||||
game.resume(new StartingRound(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
return game;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_Simple() throws ExecutionException, InterruptedException {
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPrediction(players[3], 3)
|
||||
.addPrediction(players[0], 0)
|
||||
.addPrediction(players[1], 3)
|
||||
.addPrediction(players[2], 1)
|
||||
// trick 0
|
||||
.addCard(players[3], Card.RED_7)
|
||||
.addCard(players[0], Card.GREEN_WIZARD)
|
||||
.addCard(players[1], Card.GREEN_10)
|
||||
.addCard(players[2], Card.RED_1)
|
||||
// trick 1
|
||||
.addCard(players[0], Card.RED_9)
|
||||
.addCard(players[1], Card.YELLOW_WIZARD)
|
||||
.addCard(players[2], Card.RED_3)
|
||||
.addCard(players[3], Card.RED_10)
|
||||
// trick 2
|
||||
.addCard(players[1], Card.YELLOW_JESTER)
|
||||
.addCard(players[2], Card.GREEN_12)
|
||||
.addCard(players[3], Card.GREEN_5)
|
||||
.addCard(players[0], Card.BLUE_7)
|
||||
// trick 3
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addCard(players[3], Card.YELLOW_10)
|
||||
.addCard(players[0], Card.BLUE_5)
|
||||
.addCard(players[1], Card.GREEN_2)
|
||||
// trick 4
|
||||
.addCard(players[1], Card.GREEN_7)
|
||||
.addCard(players[2], Card.GREEN_3)
|
||||
.addCard(players[3], Card.YELLOW_5)
|
||||
.addCard(players[0], Card.RED_13)
|
||||
// trick 5
|
||||
.addCard(players[1], Card.YELLOW_8)
|
||||
.addCard(players[2], Card.BLUE_9)
|
||||
.addCard(players[3], Card.YELLOW_9)
|
||||
.addCard(players[0], Card.GREEN_JESTER)
|
||||
// trick 5
|
||||
.addCard(players[3], Card.YELLOW_2)
|
||||
.addCard(players[0], Card.BLUE_2)
|
||||
.addCard(players[1], Card.BLUE_10)
|
||||
.addCard(players[2], Card.BLUE_6);
|
||||
|
||||
int round = 6;
|
||||
Game game = performTest(0L, Configurations.DEFAULT, round, queue);
|
||||
game.await();
|
||||
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting_round
|
||||
order.verify(game).notify(any(StateMessage.class)); // dealing
|
||||
order.verify(game, atLeast(4)).notify(any(), any(HandMessage.class)); // hands
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining_trump
|
||||
order.verify(game).notify(any(TrumpMessage.class)); // trump
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // predicting
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user input
|
||||
}
|
||||
|
||||
for (int i = 0; i < round + 1; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
for (int j = 0; j < players.length; j++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing_card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input
|
||||
order.verify(game).notify(any(CardMessage.class)); // user input
|
||||
}
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing_trick
|
||||
order.verify(game).notify(any(TrickMessage.class)); // trick
|
||||
}
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing_round
|
||||
order.verify(game).notify(argThat(message ->
|
||||
message instanceof ScoreMessage score
|
||||
&& score.getPoints().get(players[0]) == -10
|
||||
&& score.getPoints().get(players[1]) == 50
|
||||
&& score.getPoints().get(players[2]) == 30
|
||||
&& score.getPoints().get(players[3]) == -10
|
||||
)); // score
|
||||
order.verify(game).transition(any(), any(StartingRound.class)); // next round
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_Anniversary() throws ExecutionException, InterruptedException {
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addPickTrump(players[2], Card.Suit.YELLOW)
|
||||
.addPrediction(players[3], 2)
|
||||
.addPrediction(players[0], 2)
|
||||
.addPrediction(players[1], 2)
|
||||
.addPrediction(players[2], 1).assertThrows(IllegalArgumentException.class)
|
||||
.addPrediction(players[2], 3)
|
||||
// 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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
.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)
|
||||
// trick 5
|
||||
.addCard(players[0], Card.GREEN_7)
|
||||
.addCard(players[1], Card.FAIRY)
|
||||
.addCard(players[2], Card.YELLOW_7)
|
||||
.addCard(players[3], Card.BLUE_6)
|
||||
// trick 6
|
||||
.addCard(players[2], Card.BLUE_4)
|
||||
.addCard(players[3], Card.BLUE_11)
|
||||
.addCard(players[0], Card.GREEN_1)
|
||||
.addCard(players[1], Card.GREEN_11);
|
||||
|
||||
int round = 6;
|
||||
Game game = performTest(227L, Configurations.ANNIVERSARY_2021_PM1, round, queue);
|
||||
game.await();
|
||||
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting round
|
||||
order.verify(game).notify(any(StateMessage.class)); // dealing
|
||||
order.verify(game, times(4)).notify(any(), any(HandMessage.class)); // hands
|
||||
order.verify(game).notify(any(StateMessage.class)); // determining trump
|
||||
order.verify(game, times(2)).notify(any(TrumpMessage.class)); // werewolf
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input
|
||||
order.verify(game).notify(any(TrumpMessage.class)); // user input
|
||||
order.verify(game).notify(any(), any(HandMessage.class)); // update hand
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class));
|
||||
order.verify(game).notify(any(UserInputMessage.class));
|
||||
order.verify(game).notify(any(PredictionMessage.class));
|
||||
}
|
||||
for (int i = 0; i < round + 1; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
for (int j = 0; j < players.length; j++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing_card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input
|
||||
order.verify(game).notify(any(CardMessage.class)); // user input
|
||||
}
|
||||
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)); // finishing_round
|
||||
order.verify(game).notify(argThat(message ->
|
||||
message instanceof ScoreMessage score
|
||||
&& score.getPoints().get(players[0]) == -10
|
||||
&& score.getPoints().get(players[1]) == 30
|
||||
&& score.getPoints().get(players[2]) == -10
|
||||
&& score.getPoints().get(players[3]) == 40
|
||||
)); // score
|
||||
order.verify(game).transition(any(), any(StartingRound.class)); // next round
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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;
|
||||
|
||||
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<Pair<UUID, Card>> 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<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(UUID.randomUUID(), Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
performTest(stack, Card.Suit.YELLOW, Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestTrump_IfNoWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
performTest(stack, Card.Suit.YELLOW, Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestTrickSuit_IfNeitherWizardNorTrump() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
performTest(stack, Card.Suit.BLUE, Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsFirstJester_IfOnlyJester() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(UUID.randomUUID(), Card.GREEN_JESTER),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.RED_JESTER),
|
||||
Pair.of(null, Card.YELLOW_JESTER)
|
||||
);
|
||||
|
||||
performTest(stack, Card.Suit.NONE, Card.Suit.NONE);
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
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;
|
||||
import eu.jonahbauer.wizard.core.model.Card;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.*;
|
||||
|
||||
public class PlayingCardTest {
|
||||
@Test
|
||||
public void checkCard_ThrowsIllegalArgument_IfCardNotInHand() {
|
||||
List<Card> hand = List.of(Card.BLUE_1, Card.RED_1, Card.YELLOW_1, Card.GREEN_JESTER, Card.BLUE_WIZARD);
|
||||
UUID player = new UUID(0, 0);
|
||||
|
||||
PlayingCard state = mock(PlayingCard.class);
|
||||
doCallRealMethod().when(state).checkCard(any());
|
||||
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;
|
||||
assertThrows(IllegalArgumentException.class, () -> state.checkCard(value));
|
||||
}
|
||||
|
||||
verify(state, times(0)).getData();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkCard_ThrowsIllegalArgument_IfNotFollowingSuit() {
|
||||
List<Card> hand = List.of(Card.BLUE_1, Card.RED_1, Card.YELLOW_1, Card.GREEN_JESTER, Card.BLUE_WIZARD);
|
||||
UUID player = new UUID(0, 0);
|
||||
|
||||
PlayingCard state = mock(PlayingCard.class);
|
||||
doCallRealMethod().when(state).checkCard(any());
|
||||
when(state.getTrickSuit()).thenReturn(Card.Suit.YELLOW);
|
||||
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));
|
||||
|
||||
state.checkCard(Card.YELLOW_1);
|
||||
state.checkCard(Card.GREEN_JESTER);
|
||||
state.checkCard(Card.BLUE_WIZARD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMessage_ThrowsIllegalState_IfNotCurrentPlayer() {
|
||||
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(), any());
|
||||
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||
|
||||
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(), any());
|
||||
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> state.onMessage(game, player, new PredictMessage(1)));
|
||||
assertThrows(IllegalStateException.class, () -> state.onMessage(game, player, new PickTrumpMessage(Card.Suit.BLUE)));
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
import eu.jonahbauer.wizard.core.model.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
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;
|
||||
|
||||
public class TrickStateTest {
|
||||
@Test
|
||||
public void getTrickSuit_ReturnsNone_IfEmpty() {
|
||||
List<Pair<UUID, Card>> stack = List.of();
|
||||
|
||||
TrickState trickState = mock(TrickState.class);
|
||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||
when(trickState.get(STACK)).thenReturn(stack);
|
||||
|
||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrickSuit_ReturnsColor() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
TrickState trickState = mock(TrickState.class);
|
||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||
when(trickState.get(STACK)).thenReturn(stack);
|
||||
|
||||
assertEquals(trickState.getTrickSuit(), Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTickSuit_ReturnsNone_IfFirstCardIsWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
TrickState trickState = mock(TrickState.class);
|
||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||
when(trickState.get(STACK)).thenReturn(stack);
|
||||
|
||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrickSuit_IgnoresJesters() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
TrickState trickState = mock(TrickState.class);
|
||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||
when(trickState.get(STACK)).thenReturn(stack);
|
||||
|
||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||
|
||||
List<Pair<UUID, Card>> stack2 = List.of(
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
TrickState trickState2 = mock(TrickState.class);
|
||||
when(trickState2.getTrickSuit()).thenCallRealMethod();
|
||||
when(trickState2.get(STACK)).thenReturn(stack2);
|
||||
|
||||
assertEquals(trickState2.getTrickSuit(), Card.Suit.YELLOW);
|
||||
}
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameTestUtils;
|
||||
import eu.jonahbauer.wizard.core.machine.MessageQueue;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
|
||||
import eu.jonahbauer.wizard.core.model.Configuration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class TrickTest {
|
||||
|
||||
private final UUID[] players = new UUID[] {
|
||||
new UUID(0, 0),
|
||||
new UUID(0, 1),
|
||||
new UUID(0, 2),
|
||||
new UUID(0, 3)
|
||||
};
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private Game performTest(Configuration configuration, int round, int trick, Map<UUID, List<Card>> hands, Card.Suit trump, MessageQueue queue) {
|
||||
Game game = spy(new Game(configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(StartingTrick.class));
|
||||
doFinish().when(game).transition(any(), any(FinishingRound.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
|
||||
GameData data = GameData.EMPTY.with(
|
||||
entry(PLAYERS, playerList),
|
||||
entry(ROUND, round),
|
||||
entry(SCORE, Map.of()),
|
||||
entry(HANDS, hands),
|
||||
entry(PREDICTIONS, GameTestUtils.emptyToIntMap(playerList)),
|
||||
entry(TRICKS, GameTestUtils.emptyToIntMap(playerList)),
|
||||
entry(TRICK, trick),
|
||||
entry(TRUMP_SUIT, trump),
|
||||
entry(CURRENT_PLAYER, players[0])
|
||||
);
|
||||
|
||||
game.resume(new StartingTrick(data));
|
||||
game.await();
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_Simple() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.YELLOW_1),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCards(List.of(players), hands, 0);
|
||||
|
||||
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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[3])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
@Test
|
||||
public void run_WithWrongInput() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.YELLOW_1),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// 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)
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addPrediction(players[3], 1).assertThrows(IllegalStateException.class)
|
||||
.addCard(players[3], Card.BLUE_1);
|
||||
|
||||
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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[3])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithCloud() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.RED_1),
|
||||
players[1], List.of(Card.CLOUD),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCard(players[0], Card.RED_1)
|
||||
.addCard(players[1], Card.CLOUD_BLUE)
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addCard(players[3], Card.BLUE_1)
|
||||
.addChangePrediction(players[2], 1);
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, 0, hands, Card.Suit.GREEN, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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[2])); // trick with correct winner
|
||||
order.verify(game).notify(any(StateMessage.class)); // changing prediction
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(PredictionMessage.class)); // user response
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithJuggler() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
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)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCard(players[0], Card.RED_1)
|
||||
.addCard(players[1], Card.JUGGLER_RED)
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addCard(players[3], Card.RED_5);
|
||||
|
||||
Game game = performTest(Configurations.ANNIVERSARY_2021, 1, 0, hands, Card.Suit.YELLOW, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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, 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
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithChangeling() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.CHANGELING),
|
||||
players[1], List.of(Card.RED_1),
|
||||
players[2], List.of(Card.GREEN_1),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCard(players[0], Card.CHANGELING_JESTER)
|
||||
.addCard(players[1], Card.RED_1)
|
||||
.addCard(players[2], Card.GREEN_1)
|
||||
.addCard(players[3], Card.BLUE_1);
|
||||
|
||||
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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[3])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void run_WithChangeling2() {
|
||||
Map<UUID, List<Card>> hands = Map.of(
|
||||
players[0], List.of(Card.CHANGELING),
|
||||
players[1], List.of(Card.RED_1),
|
||||
players[2], List.of(Card.GREEN_WIZARD),
|
||||
players[3], List.of(Card.BLUE_1)
|
||||
);
|
||||
|
||||
// play cards in given order
|
||||
MessageQueue queue = new MessageQueue()
|
||||
.addCard(players[0], Card.CHANGELING_WIZARD)
|
||||
.addCard(players[1], Card.RED_1)
|
||||
.addCard(players[2], Card.GREEN_WIZARD)
|
||||
.addCard(players[3], Card.BLUE_1);
|
||||
|
||||
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
|
||||
|
||||
// validate messages
|
||||
InOrder order = inOrder(game);
|
||||
order.verify(game).notify(any(StateMessage.class)); // starting trick
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // playing card
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
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[0])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
package eu.jonahbauer.wizard.core.model.card;
|
||||
|
||||
import eu.jonahbauer.wizard.common.model.card.Card;
|
||||
import eu.jonahbauer.wizard.core.util.Pair;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class CardUtilsTest {
|
||||
private void performWinnerTest(List<Pair<UUID, Card>> stack, Card.Suit trumpSuit) {
|
||||
assertNotNull(CardUtils.getWinner(stack, trumpSuit));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsFirstWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(UUID.randomUUID(), Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsDragon() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(UUID.randomUUID(), Card.DRAGON),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsFairy_IfDragon() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(UUID.randomUUID(), Card.FAIRY),
|
||||
Pair.of(null, Card.DRAGON),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestTrump_IfNoWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestSpecialTrump_IfNoWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.JUGGLER_YELLOW),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestSpecialTrump_IfNoWizard2() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.CLOUD_YELLOW),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.YELLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestTrickSuit_IfNeitherWizardNorTrump() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_5),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.RED_13),
|
||||
Pair.of(null, Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.RED_7),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.BLUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestSpecialTrickSuit_IfNeitherWizardNorTrump() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_5),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.CLOUD_RED),
|
||||
Pair.of(null, Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.RED_7),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.BLUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsHighestSpecialTrickSuit_IfNeitherWizardNorTrump2() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_5),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(UUID.randomUUID(), Card.JUGGLER_RED),
|
||||
Pair.of(null, Card.YELLOW_13),
|
||||
Pair.of(null, Card.GREEN_11),
|
||||
Pair.of(null, Card.RED_7),
|
||||
Pair.of(null, Card.FAIRY)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.BLUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsFirstJester_IfOnlyJester() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(UUID.randomUUID(), Card.GREEN_JESTER),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.RED_JESTER),
|
||||
Pair.of(null, Card.FAIRY),
|
||||
Pair.of(null, Card.YELLOW_JESTER)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWinner_ReturnsJester_IfOnlyFairyAndBomb() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.FAIRY),
|
||||
Pair.of(null, Card.BOMB),
|
||||
Pair.of(UUID.randomUUID(), Card.RED_JESTER)
|
||||
);
|
||||
|
||||
performWinnerTest(stack, Card.Suit.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrickSuit_ReturnsNone_IfEmpty() {
|
||||
List<Pair<UUID, Card>> stack = List.of();
|
||||
assertEquals(Card.Suit.NONE, CardUtils.getTrickSuit(stack));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrickSuit_ReturnsColor() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
assertEquals(Card.Suit.RED, CardUtils.getTrickSuit(stack));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTickSuit_ReturnsNone_IfFirstCardIsWizard() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
assertEquals(Card.Suit.NONE, CardUtils.getTrickSuit(stack));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrickSuit_IgnoresJesters() {
|
||||
List<Pair<UUID, Card>> stack = List.of(
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
assertEquals(Card.Suit.NONE, CardUtils.getTrickSuit(stack));
|
||||
|
||||
List<Pair<UUID, Card>> stack2 = List.of(
|
||||
Pair.of(null, Card.BLUE_JESTER),
|
||||
Pair.of(null, Card.YELLOW_1),
|
||||
Pair.of(null, Card.BLUE_WIZARD),
|
||||
Pair.of(null, Card.RED_1),
|
||||
Pair.of(null, Card.GREEN_11)
|
||||
);
|
||||
assertEquals(Card.Suit.YELLOW, CardUtils.getTrickSuit(stack2));
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>eu.jonahbauer</groupId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>wizard-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
Loading…
x
Reference in New Issue
Block a user