parent
9f067fae1a
commit
b98d25599b
@ -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>
|
@ -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.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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()
|
public static final Gson GSON = new GsonBuilder()
|
||||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
|
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
|
||||||
.create();
|
.create();
|
@ -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,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.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public final class PickTrumpMessage extends PlayerMessage {
|
public final class PickTrumpMessage extends PlayerMessage {
|
||||||
private final Card.Suit trumpSuit;
|
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.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public final class PlayCardMessage extends PlayerMessage {
|
public final class PlayCardMessage extends PlayerMessage {
|
||||||
private final Card card;
|
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.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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 abstract sealed class PlayerMessage permits PickTrumpMessage, PlayCardMessage, PredictMessage {
|
||||||
public static final Gson GSON = new GsonBuilder()
|
public static final Gson GSON = new GsonBuilder()
|
||||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(PlayerMessage.class, "Message"))
|
.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.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public final class PredictMessage extends PlayerMessage {
|
public final class PredictMessage extends PlayerMessage {
|
||||||
private final int prediction;
|
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.Gson;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
@ -1,123 +1,155 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine;
|
package eu.jonahbauer.wizard.core.machine;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.State;
|
import eu.jonahbauer.wizard.core.machine.states.State;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jetbrains.annotations.Blocking;
|
import org.jetbrains.annotations.Blocking;
|
||||||
|
import org.jetbrains.annotations.NonBlocking;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
public abstract class Context<S extends State<S,C>, C extends Context<S,C>> {
|
public abstract class Context<S extends State<S,C>, C extends Context<S,C>> {
|
||||||
protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||||
|
1, 1,
|
||||||
|
0, TimeUnit.SECONDS,
|
||||||
|
new PriorityBlockingQueue<>(
|
||||||
|
11,
|
||||||
|
Comparator.comparingInt(r -> r instanceof PriorityRunnable prio ? prio.getPriority() : Integer.MAX_VALUE)
|
||||||
|
.thenComparingLong(r -> r instanceof PriorityRunnable prio ? prio.getTimestamp() : Long.MAX_VALUE)
|
||||||
|
),
|
||||||
|
r -> {
|
||||||
|
var t = new Thread(r);
|
||||||
|
t.setUncaughtExceptionHandler((t1, e) -> finish(e));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected S state;
|
protected 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) {
|
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
lock.lock();
|
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 {
|
try {
|
||||||
if (finished) throw new IllegalStateException("Context has already finished.");
|
runnable.run();
|
||||||
transition(null, state);
|
future.complete(null);
|
||||||
} finally {
|
} catch (Throwable t) {
|
||||||
lock.unlock();
|
future.completeExceptionally(t);
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void transition(S currentState, S newState) {
|
@Blocking
|
||||||
lock.lock();
|
protected void start(@NotNull S state) {
|
||||||
try {
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
if (state == currentState) {
|
AtomicReference<RuntimeException> exception = new AtomicReference<>();
|
||||||
state = newState;
|
|
||||||
if (currentState != null) //noinspection unchecked
|
executor.execute(new PriorityRunnable(0, () -> {
|
||||||
currentState.onExit((C) this);
|
if (future.isDone()) {
|
||||||
onTransition(currentState, newState);
|
exception.set(new IllegalStateException("Context has already finished."));
|
||||||
if (newState != null) //noinspection unchecked
|
latch.countDown();
|
||||||
newState.onEnter((C) this);
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Current state does not match.");
|
latch.countDown();
|
||||||
|
doTransition(null, state);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
}));
|
||||||
handleError(t);
|
|
||||||
} finally {
|
while (true) {
|
||||||
lock.unlock();
|
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() {
|
public void finish() {
|
||||||
finish(null);
|
finish(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonBlocking
|
||||||
public void finish(Throwable exception) {
|
public void finish(Throwable exception) {
|
||||||
lock.lock();
|
executor.execute(new PriorityRunnable(0, () -> doFinish(exception)));
|
||||||
try {
|
|
||||||
finished = true;
|
|
||||||
this.exception = exception;
|
|
||||||
finishCondition.signalAll();
|
|
||||||
transition(state, null);
|
|
||||||
scheduler.shutdown();
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonBlocking
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
finish(new CancellationException());
|
finish(new CancellationException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
/*
|
||||||
public boolean isDone() {
|
* internal methods that are called on the executor
|
||||||
return finished;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
@Blocking
|
private void doTransition(S currentState, S newState) {
|
||||||
public void await() throws InterruptedException, ExecutionException, CancellationException {
|
if (state == currentState) {
|
||||||
lock.lock();
|
state = newState;
|
||||||
try {
|
if (currentState != null) //noinspection unchecked
|
||||||
while (!finished) {
|
currentState.onExit((C) this);
|
||||||
finishCondition.await();
|
onTransition(currentState, newState);
|
||||||
|
if (newState != null) //noinspection unchecked
|
||||||
|
newState.onEnter((C) this);
|
||||||
}
|
}
|
||||||
if (exception != null) {
|
|
||||||
if (exception instanceof CancellationException cancelled) {
|
|
||||||
throw cancelled;
|
|
||||||
} else {
|
|
||||||
throw new ExecutionException(exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doFinish(Throwable t) {
|
||||||
|
if (future.isDone()) return;
|
||||||
|
|
||||||
|
doTransition(state, null);
|
||||||
|
if (t != null) {
|
||||||
|
future.completeExceptionally(t);
|
||||||
|
} else {
|
||||||
|
future.complete(null);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
public void timeout(@NotNull S currentState, long delay) {
|
||||||
scheduler.schedule(() -> {
|
scheduler.schedule(() -> {
|
||||||
lock.lock();
|
submit(() -> {
|
||||||
try {
|
|
||||||
if (state == currentState) {
|
if (state == currentState) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
state.onTimeout((C) this);
|
state.onTimeout((C) this);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
});
|
||||||
handleError(t);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}, delay, TimeUnit.MILLISECONDS);
|
}, delay, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleError(Throwable t) {
|
@Getter
|
||||||
lock.lock();
|
@RequiredArgsConstructor
|
||||||
try {
|
private static class PriorityRunnable implements Runnable {
|
||||||
if (!isDone()) {
|
private final int priority;
|
||||||
finish(t);
|
private final long timestamp = System.nanoTime();
|
||||||
t.printStackTrace();
|
private final Runnable runnable;
|
||||||
}
|
|
||||||
} finally {
|
@Override
|
||||||
lock.unlock();
|
public void run() {
|
||||||
|
runnable.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onTransition(S from, S to) {}
|
|
||||||
}
|
}
|
||||||
|
@ -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,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
|
|
||||||
);
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
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.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.Configuration;
|
||||||
|
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.junit.jupiter.api.Test;
|
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.Map;
|
||||||
import java.util.UUID;
|
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.*;
|
||||||
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.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
public class PredictingTest {
|
public class PredictingTest {
|
||||||
@Test
|
private final UUID[] players = new UUID[] {
|
||||||
public void checkPrediction_ThrowsIllegalArgument_IfOutOfBounds() {
|
new UUID(0, 0),
|
||||||
Map<UUID, Integer> predictions = Map.of();
|
new UUID(0, 1),
|
||||||
|
new UUID(0, 2),
|
||||||
|
new UUID(0, 3)
|
||||||
|
};
|
||||||
|
|
||||||
Configuration config = mock(Configuration.class);
|
@SneakyThrows
|
||||||
when(config.allowExactPredictions()).thenReturn(true);
|
@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);
|
Game game = spy(new Game(configuration, queue));
|
||||||
when(game.getConfig()).thenReturn(config);
|
doFinish().when(game).transition(any(), any(StartingTrick.class));
|
||||||
|
queue.setGame(game);
|
||||||
|
|
||||||
Predicting state = mock(Predicting.class);
|
var playerList = List.of(players);
|
||||||
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);
|
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, -1));
|
GameData data = GameData.EMPTY.with(
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 12));
|
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++) {
|
game.resume(new Predicting(data));
|
||||||
state.checkPrediction(game, i);
|
game.await();
|
||||||
}
|
|
||||||
|
verify(game, never()).transition(any(), isNull());
|
||||||
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkPrediction_ThrowsIllegalArgument_IfAddsUp() {
|
public void predicting_Simple() {
|
||||||
UUID player = new UUID(0,0);
|
// play cards in given order
|
||||||
Map<UUID, Integer> predictions = Map.of(new UUID(0,1), 10);
|
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);
|
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
|
||||||
when(config.allowExactPredictions()).thenReturn(false);
|
|
||||||
|
|
||||||
Game game = mock(Game.class);
|
// validate messages
|
||||||
when(game.getConfig()).thenReturn(config);
|
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);
|
@Test
|
||||||
doCallRealMethod().when(state).checkPrediction(any(), anyInt());
|
public void predicting_WithWrongInput() {
|
||||||
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
// play cards in given order
|
||||||
when(state.getDealer()).thenReturn(player);
|
MessageQueue queue = new MessageQueue()
|
||||||
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
.addPrediction(players[0], -1).assertThrows(IllegalArgumentException.class)
|
||||||
when(state.get(ROUND)).thenReturn(10);
|
.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++) {
|
// validate messages
|
||||||
if (i == 1) continue;
|
InOrder order = inOrder(game);
|
||||||
state.checkPrediction(game, i);
|
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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue