Reworked state machine
This commit is contained in:
parent
a9e2b1ddd9
commit
d037ada8c5
@ -0,0 +1,123 @@
|
||||
package eu.jonahbauer.wizard.common.machine;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class Context<S extends State<S,C>, C extends Context<S,C>> {
|
||||
@Getter
|
||||
private @NotNull S state;
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
public Context(@NotNull S initialState) {
|
||||
this.state = initialState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically executes the given supplier and transitions to the returned state if present.
|
||||
* @see #transition(State)
|
||||
*/
|
||||
public void execute(Supplier<Optional<@NotNull S>> transition) {
|
||||
lock.lock();
|
||||
try {
|
||||
Optional<S> next = transition.get();
|
||||
next.ifPresent(this::transition);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically applies the given function to the current state and transitions to the returned state if present.
|
||||
* @see #transition(State)
|
||||
*/
|
||||
public void execute(Function<@NotNull S, Optional<@NotNull S>> transition) {
|
||||
lock.lock();
|
||||
try {
|
||||
Optional<S> next = transition.apply(state);
|
||||
next.ifPresent(this::transition);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically transitions to the specified state calling the state lifecycle methods.
|
||||
* When an error occurs during execution of the lifecycle methods {@link #handleError(Throwable)} is called.
|
||||
* @param state the next state
|
||||
* @see State#onExit(Context)
|
||||
* @see State#onEnter(Context)
|
||||
* @see #handleError(Throwable)
|
||||
*/
|
||||
public void transition(@NotNull S state) {
|
||||
lock.lock();
|
||||
try {
|
||||
//noinspection unchecked
|
||||
this.state.onExit((C) this);
|
||||
onTransition(this.state, state);
|
||||
this.state = state;
|
||||
//noinspection unchecked
|
||||
state.onEnter((C) this).ifPresent(this::transition);
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically checks that the current state is {@linkplain Object#equals(Object) equal to} the specified expected
|
||||
* state and transitions to the specified next state.
|
||||
* @param expected the expected current state
|
||||
* @param next the next state
|
||||
* @see #transition(State)
|
||||
* @throws IllegalStateException if the current state is not equal to the expected state
|
||||
*/
|
||||
public void transition(@NotNull S expected, @NotNull S next) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (Objects.equals(expected, this.state)) {
|
||||
transition(next);
|
||||
} else {
|
||||
throw new IllegalStateException("Current state is not " + expected + ".");
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically transitions to the specified state without calling the state lifecycle methods. This method can
|
||||
* be used to recover from errors occurring during {@link State#onExit(Context)} which would otherwise prevent
|
||||
* the context from transitioning to another state.
|
||||
* @param state the next state
|
||||
*/
|
||||
protected void forceTransition(@NotNull S state) {
|
||||
lock.lock();
|
||||
try {
|
||||
onTransition(this.state, state);
|
||||
this.state = state;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method that will synchronously be called on transitioning between two states.
|
||||
* @param from the previous state
|
||||
* @param to the next state
|
||||
*/
|
||||
protected void onTransition(S from, S to) {}
|
||||
|
||||
/**
|
||||
* Callback method that will synchronously be called when an error occurs during execution of state lifecycle
|
||||
* methods.
|
||||
* @param t the cause
|
||||
*/
|
||||
protected abstract void handleError(Throwable t);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package eu.jonahbauer.wizard.common.machine;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface State<S extends State<S,C>, C extends Context<S,C>> {
|
||||
default Optional<S> onEnter(C context) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default void onExit(C context) {}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package eu.jonahbauer.wizard.common.machine;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class TimeoutContext<S extends TimeoutState<S,C>, C extends TimeoutContext<S,C>> extends Context<S,C> {
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public TimeoutContext(@NotNull S initialState) {
|
||||
super(initialState);
|
||||
}
|
||||
|
||||
public void timeout(@NotNull S currentState, long delay) {
|
||||
scheduler.schedule(() -> {
|
||||
if (Objects.equals(getState(), currentState)) {
|
||||
execute(() -> {
|
||||
if (Objects.equals(getState(), currentState)) {
|
||||
//noinspection unchecked
|
||||
return currentState.onTimeout((C) this);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void shutdownNow() {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.jonahbauer.wizard.common.machine;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TimeoutState<S extends TimeoutState<S,C>, C extends TimeoutContext<S,C>> extends State<S,C> {
|
||||
default Optional<S> onTimeout(C context) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class CLI {
|
||||
public static void main(String[] args) {
|
||||
GameConfiguration config = Configurations.DEFAULT.withTimeout(0);
|
||||
GameConfiguration config = Configurations.DEFAULT;
|
||||
Observer observer = (player, msg) -> System.out.println(msg);
|
||||
Game game = new Game(config, observer);
|
||||
var players = List.of(
|
||||
@ -52,24 +52,15 @@ public class CLI {
|
||||
switch (command) {
|
||||
case "predict" -> {
|
||||
int prediction = Integer.parseInt(param);
|
||||
game.onMessage(players.get(id), new PredictMessage(prediction))
|
||||
.whenComplete((v, err) -> {
|
||||
if (err != null) err.printStackTrace();
|
||||
});
|
||||
game.onMessage(players.get(id), new PredictMessage(prediction));
|
||||
}
|
||||
case "play" -> {
|
||||
Card card = Card.valueOf(param);
|
||||
game.onMessage(players.get(id), new PlayCardMessage(card))
|
||||
.whenComplete((v, err) -> {
|
||||
if (err != null) err.printStackTrace();
|
||||
});
|
||||
game.onMessage(players.get(id), new PlayCardMessage(card));
|
||||
}
|
||||
case "trump" -> {
|
||||
Card.Suit suit = Card.Suit.valueOf(param);
|
||||
game.onMessage(players.get(id), new PickTrumpMessage(suit))
|
||||
.whenComplete((v, err) -> {
|
||||
if (err != null) err.printStackTrace();
|
||||
});
|
||||
game.onMessage(players.get(id), new PickTrumpMessage(suit));
|
||||
}
|
||||
default -> System.err.println("Unknown command: " + command);
|
||||
}
|
||||
|
@ -1,155 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.State;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NonBlocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public abstract class Context<S extends State<S,C>, C extends Context<S,C>> {
|
||||
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;
|
||||
|
||||
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
private final CompletableFuture<Void> finished = future.whenComplete((v, t) -> {
|
||||
executor.shutdownNow();
|
||||
scheduler.shutdownNow();
|
||||
});
|
||||
|
||||
@NonBlocking
|
||||
protected CompletableFuture<Void> submit(Runnable runnable) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
executor.execute(new PriorityRunnable(100, () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
future.complete(null);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
}));
|
||||
return future;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
protected void start(@NotNull S state) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<RuntimeException> exception = new AtomicReference<>();
|
||||
|
||||
executor.execute(new PriorityRunnable(0, () -> {
|
||||
if (future.isDone()) {
|
||||
exception.set(new IllegalStateException("Context has already finished."));
|
||||
latch.countDown();
|
||||
} else {
|
||||
latch.countDown();
|
||||
doTransition(null, state);
|
||||
}
|
||||
}));
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
latch.await();
|
||||
if (exception.get() != null) {
|
||||
throw exception.get();
|
||||
}
|
||||
break;
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@NonBlocking
|
||||
public void transition(S currentState, S newState) {
|
||||
executor.execute(new PriorityRunnable(0, () -> doTransition(currentState, newState)));
|
||||
}
|
||||
|
||||
@NonBlocking
|
||||
public void finish() {
|
||||
finish(null);
|
||||
}
|
||||
|
||||
@NonBlocking
|
||||
public void finish(Throwable exception) {
|
||||
executor.execute(new PriorityRunnable(0, () -> doFinish(exception)));
|
||||
}
|
||||
|
||||
@NonBlocking
|
||||
public void cancel() {
|
||||
finish(new CancellationException());
|
||||
}
|
||||
|
||||
/*
|
||||
* internal methods that are called on the executor
|
||||
*/
|
||||
|
||||
private void doTransition(S currentState, S newState) {
|
||||
if (state == currentState) {
|
||||
state = newState;
|
||||
if (currentState != null) //noinspection unchecked
|
||||
currentState.onExit((C) this);
|
||||
onTransition(currentState, newState);
|
||||
if (newState != null) //noinspection unchecked
|
||||
newState.onEnter((C) this);
|
||||
}
|
||||
}
|
||||
|
||||
private void doFinish(Throwable t) {
|
||||
if (future.isDone()) return;
|
||||
|
||||
doTransition(state, null);
|
||||
if (t != null) {
|
||||
future.completeExceptionally(t);
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onTransition(S from, S to) {}
|
||||
|
||||
@Blocking
|
||||
public void await() throws InterruptedException, ExecutionException, CancellationException {
|
||||
finished.get();
|
||||
}
|
||||
|
||||
public void timeout(@NotNull S currentState, long delay) {
|
||||
scheduler.schedule(() -> {
|
||||
submit(() -> {
|
||||
if (state == currentState) {
|
||||
//noinspection unchecked
|
||||
state.onTimeout((C) this);
|
||||
}
|
||||
});
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
private static class PriorityRunnable implements Runnable {
|
||||
private final int priority;
|
||||
private final long timestamp = System.nanoTime();
|
||||
private final Runnable runnable;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +1,58 @@
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Starting;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutContext;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Created;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Error;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import eu.jonahbauer.wizard.core.model.GameConfiguration;
|
||||
import eu.jonahbauer.wizard.core.util.Util;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
|
||||
|
||||
public final class Game extends Context<GameState, Game> {
|
||||
public final class Game extends TimeoutContext<GameState, Game> {
|
||||
@Getter
|
||||
private final Random random;
|
||||
@Getter
|
||||
private final GameConfiguration config;
|
||||
private final Observer observer;
|
||||
|
||||
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
public Game(GameConfiguration config, Observer observer) {
|
||||
super(new Created());
|
||||
this.random = new Random();
|
||||
this.config = config;
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public Game(long seed, GameConfiguration config, Observer observer) {
|
||||
super(new Created());
|
||||
this.random = new Random(seed);
|
||||
this.config = config;
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
public void start(List<UUID> players) {
|
||||
start(new Starting(GameData.EMPTY.with(PLAYERS, List.copyOf(players))));
|
||||
}
|
||||
|
||||
public void resume(GameState state) {
|
||||
start(state);
|
||||
transition(new Created(), state);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> onMessage(UUID player, PlayerMessage message) {
|
||||
return submit(() -> {
|
||||
if (state != null) {
|
||||
state.onMessage(this, player, message);
|
||||
public void start(List<UUID> players) {
|
||||
execute(s -> s.start(this, players));
|
||||
}
|
||||
});
|
||||
|
||||
public void onMessage(UUID player, PlayerMessage message) {
|
||||
execute(s -> s.onMessage(this, player, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,6 +60,13 @@ public final class Game extends Context<GameState, Game> {
|
||||
notify(new StateMessage(to != null ? Util.toSnakeCase(to.getClass().getSimpleName()) : "null"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleError(Throwable t) {
|
||||
// don't use usual transition procedure to prevent errors during onExit from causing stack overflow
|
||||
forceTransition(new Error(t));
|
||||
this.complete(t);
|
||||
}
|
||||
|
||||
public void notify(ObserverMessage message) {
|
||||
try {
|
||||
observer.notify(message);
|
||||
@ -73,4 +82,18 @@ public final class Game extends Context<GameState, Game> {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void complete(@Nullable Throwable t) {
|
||||
shutdownNow();
|
||||
if (t != null) {
|
||||
future.completeExceptionally(t);
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void await() throws InterruptedException, ExecutionException, CancellationException {
|
||||
future.get();
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,57 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states;
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.common.machine.TimeoutState;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.CURRENT_PLAYER;
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
|
||||
|
||||
@Unmodifiable
|
||||
public abstract class GameState implements State<GameState, Game> {
|
||||
public abstract class GameState implements TimeoutState<GameState, Game> {
|
||||
@Getter
|
||||
private final GameData data;
|
||||
|
||||
public GameState(GameData data) {
|
||||
this.data = data.require(PLAYERS).clean();
|
||||
this.data = data.clean();
|
||||
}
|
||||
|
||||
protected final void transition(Game game, GameState state) {
|
||||
game.transition(this, state);
|
||||
}
|
||||
|
||||
protected final void timeout(Game game) {
|
||||
//<editor-fold desc="Utility Methods" defaultstate="collapsed">
|
||||
protected final Optional<GameState> timeout(Game game) {
|
||||
game.timeout(this, getTimeout(game, false));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected final Optional<GameState> transition(GameState state) {
|
||||
return Optional.of(state);
|
||||
}
|
||||
|
||||
protected final long getTimeout(Game game, boolean absolute) {
|
||||
return (absolute ? System.currentTimeMillis() : 0) + game.getConfig().timeout();
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
throw new IllegalStateException("You cannot do that right now.");
|
||||
}
|
||||
|
||||
public Optional<GameState> start(Game game, List<UUID> players) {
|
||||
throw new IllegalStateException("Game has already started.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
//<editor-fold desc="GameData accessors" defaultstate="collapsed">
|
||||
public <T> T get(GameData.Key<T> key) {
|
||||
return getData().get(key);
|
||||
}
|
||||
@ -68,4 +80,5 @@ public abstract class GameState implements State<GameState, Game> {
|
||||
}
|
||||
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Context;
|
||||
|
||||
public interface State<S extends State<S,C>, C extends Context<S,C>> {
|
||||
default void onEnter(C context) {}
|
||||
default void onTimeout(C context) {}
|
||||
default void onExit(C context) {}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class Created extends GameState {
|
||||
public Created() {
|
||||
super(GameData.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<GameState> start(Game game, List<UUID> players) {
|
||||
return transition(new Starting(GameData.EMPTY.with(GameData.PLAYERS, players)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Created;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public final class Error extends GameState {
|
||||
private final Throwable cause;
|
||||
|
||||
public Error(Throwable cause) {
|
||||
super(GameData.EMPTY);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class Finished extends GameState {
|
||||
public Finished() {
|
||||
super(GameData.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<GameState> onEnter(Game context) {
|
||||
context.complete(null);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -2,9 +2,11 @@ package eu.jonahbauer.wizard.core.machine.states.game;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
public final class Finishing extends GameState {
|
||||
@ -14,8 +16,8 @@ public final class Finishing extends GameState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
game.notify(new ScoreMessage(get(SCORE)));
|
||||
game.finish();
|
||||
return transition(new Finished());
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,21 @@ package eu.jonahbauer.wizard.core.machine.states.game;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
|
||||
|
||||
public final class Starting extends GameState {
|
||||
|
||||
public Starting(GameData data) {
|
||||
super(data);
|
||||
super(data.require(PLAYERS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
transition(game, new StartingRound(getData()));
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
return transition(new StartingRound(getData()));
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,12 @@ import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.CHANGE_PREDICTION;
|
||||
@ -25,24 +27,24 @@ public final class ChangingPrediction extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(get(CLOUDED_PLAYER), CHANGE_PREDICTION, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(CLOUDED_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
checkPrediction(predictMessage.getPrediction());
|
||||
transition(game, predictMessage.getPrediction());
|
||||
return transition(game, predictMessage.getPrediction());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
return super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
transition(game, oldPrediction + 1);
|
||||
public Optional<GameState> onTimeout(Game game) {
|
||||
return transition(game, oldPrediction + 1);
|
||||
}
|
||||
|
||||
private void checkPrediction(int prediction) {
|
||||
@ -53,7 +55,7 @@ public final class ChangingPrediction extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
private void transition(Game game, int prediction) {
|
||||
private Optional<GameState> transition(Game game, int prediction) {
|
||||
game.notify(new PredictionMessage(get(CLOUDED_PLAYER), prediction));
|
||||
|
||||
// add prediction
|
||||
@ -64,7 +66,7 @@ public final class ChangingPrediction extends RoundState {
|
||||
PREDICTIONS, Map.copyOf(predictions)
|
||||
);
|
||||
|
||||
transition(game, new FinishingRound(data));
|
||||
return transition(new FinishingRound(data));
|
||||
}
|
||||
|
||||
private void checkData(GameData data) {
|
||||
|
@ -4,12 +4,10 @@ import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.model.deck.Deck;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
@ -20,7 +18,7 @@ public final class Dealing extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
Deck deck = new Deck(game.getConfig().cards());
|
||||
deck.shuffle(game.getRandom());
|
||||
|
||||
@ -39,7 +37,7 @@ public final class Dealing extends RoundState {
|
||||
}
|
||||
|
||||
Card trumpCard = deck.draw();
|
||||
transition(game, new DeterminingTrump(
|
||||
return transition(new DeterminingTrump(
|
||||
getData().with(
|
||||
HANDS, Map.copyOf(hands),
|
||||
TRUMP_CARD, trumpCard
|
||||
|
@ -8,6 +8,7 @@ import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.model.card.GameCards;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -25,7 +26,7 @@ public final class DeterminingTrump extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
Card trumpCard = get(TRUMP_CARD);
|
||||
|
||||
// handle werewolf
|
||||
@ -38,8 +39,7 @@ public final class DeterminingTrump extends RoundState {
|
||||
game.notify(new TrumpMessage(trumpCard, null));
|
||||
game.notify(new TrumpMessage(Card.WEREWOLF, null));
|
||||
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return;
|
||||
return timeout(game);
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,30 +49,30 @@ public final class DeterminingTrump extends RoundState {
|
||||
this.player = getDealer();
|
||||
game.notify(new TrumpMessage(trumpCard, null));
|
||||
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return timeout(game);
|
||||
} else {
|
||||
transition(game, trumpSuit);
|
||||
return transition(game, trumpSuit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
public Optional<GameState> onTimeout(Game game) {
|
||||
Card.Suit[] suits;
|
||||
if (werewolf) {
|
||||
suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW, Card.Suit.NONE};
|
||||
} else {
|
||||
suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
||||
}
|
||||
transition(game, suits[game.getRandom().nextInt(suits.length)]);
|
||||
return transition(game, suits[game.getRandom().nextInt(suits.length)]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (this.player.equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
||||
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
||||
transition(game, trumpMessage.getTrumpSuit());
|
||||
return transition(game, trumpMessage.getTrumpSuit());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
return super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ public final class DeterminingTrump extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
private void transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||
private Optional<GameState> transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||
GameData data = getData().with(
|
||||
TRUMP_SUIT, trumpSuit,
|
||||
CURRENT_PLAYER, getNextPlayer(getDealer())
|
||||
@ -101,6 +101,6 @@ public final class DeterminingTrump extends RoundState {
|
||||
} else {
|
||||
game.notify(new TrumpMessage(get(TRUMP_CARD), trumpSuit));
|
||||
}
|
||||
transition(game, new Predicting(data));
|
||||
return transition(new Predicting(data));
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Finishing;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
@ -18,7 +20,7 @@ public final class FinishingRound extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
var points = getPoints();
|
||||
game.notify(new ScoreMessage(points));
|
||||
|
||||
@ -28,9 +30,9 @@ public final class FinishingRound extends RoundState {
|
||||
GameData data = getData().with(SCORE, Map.copyOf(score));
|
||||
|
||||
if (60 / getPlayerCount() == get(ROUND) + 1) {
|
||||
transition(game, new Finishing(data));
|
||||
return transition(new Finishing(data));
|
||||
} else {
|
||||
transition(game, new StartingRound(data.with(ROUND, get(ROUND) + 1)));
|
||||
return transition(new StartingRound(data.with(ROUND, get(ROUND) + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
@ -11,6 +12,7 @@ import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
@ -23,20 +25,20 @@ public final class Predicting extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(get(CURRENT_PLAYER), MAKE_PREDICTION, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
public Optional<GameState> onTimeout(Game game) {
|
||||
try {
|
||||
checkPrediction(game, 0);
|
||||
transition(game, 0);
|
||||
return transition(game, 0);
|
||||
} catch (IllegalArgumentException e) {
|
||||
try {
|
||||
checkPrediction(game, 1);
|
||||
transition(game, 1);
|
||||
return transition(game, 1);
|
||||
} catch (IllegalArgumentException e2) {
|
||||
throw new AssertionError(e2);
|
||||
}
|
||||
@ -44,12 +46,12 @@ public final class Predicting extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||
checkPrediction(game, predictMessage.getPrediction());
|
||||
transition(game, predictMessage.getPrediction());
|
||||
return transition(game, predictMessage.getPrediction());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
return super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +69,7 @@ public final class Predicting extends RoundState {
|
||||
}
|
||||
}
|
||||
|
||||
private void transition(Game game, int prediction) {
|
||||
private Optional<GameState> transition(Game game, int prediction) {
|
||||
game.notify(new PredictionMessage(get(CURRENT_PLAYER), prediction));
|
||||
|
||||
// add prediction
|
||||
@ -80,9 +82,9 @@ public final class Predicting extends RoundState {
|
||||
);
|
||||
|
||||
if (isLastPlayer()) {
|
||||
transition(game, new StartingTrick(data));
|
||||
return transition(new StartingTrick(data));
|
||||
} else {
|
||||
transition(game, new Predicting(data));
|
||||
return transition(new Predicting(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -9,7 +9,7 @@ import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||
|
||||
public abstract class RoundState extends GameState {
|
||||
public RoundState(GameData data) {
|
||||
super(data.require(ROUND, SCORE));
|
||||
super(data.require(PLAYERS, ROUND, SCORE));
|
||||
}
|
||||
|
||||
protected UUID getDealer() {
|
||||
|
@ -2,6 +2,9 @@ package eu.jonahbauer.wizard.core.machine.states.round;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class StartingRound extends RoundState {
|
||||
public StartingRound(GameData data) {
|
||||
@ -9,7 +12,7 @@ public final class StartingRound extends RoundState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
transition(game, new Dealing(getData()));
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
return transition(new Dealing(getData()));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.ChangingPrediction;
|
||||
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
|
||||
@ -22,7 +23,7 @@ public final class FinishingTrick extends TrickState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
var stack = get(STACK);
|
||||
var cards = stack.stream().map(Pair::second).toList();
|
||||
|
||||
@ -58,14 +59,14 @@ public final class FinishingTrick extends TrickState {
|
||||
|
||||
if (!hasNextTrick) {
|
||||
if (data.has(CLOUDED_PLAYER)) {
|
||||
transition(game, new ChangingPrediction(data));
|
||||
return transition(new ChangingPrediction(data));
|
||||
} else {
|
||||
transition(game, new FinishingRound(data));
|
||||
return transition(new FinishingRound(data));
|
||||
}
|
||||
} else if (juggler) {
|
||||
transition(game, new Juggling(data));
|
||||
return transition(new Juggling(data));
|
||||
} else {
|
||||
transition(game, new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
|
||||
return transition(new StartingTrick(data.with(TRICK, get(TRICK) + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -22,24 +23,24 @@ public final class Juggling extends TrickState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(null, JUGGLE_CARD, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
public Optional<GameState> onTimeout(Game game) {
|
||||
for (UUID player : get(PLAYERS)) {
|
||||
juggledCards.computeIfAbsent(player, p -> {
|
||||
var hand = get(HANDS).get(p);
|
||||
return hand.get(game.getRandom().nextInt(hand.size()));
|
||||
});
|
||||
}
|
||||
juggle(game);
|
||||
return juggle(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(PLAYERS).contains(player) && message instanceof JuggleMessage juggleMessage) {
|
||||
Card card = juggleMessage.getCard();
|
||||
|
||||
@ -49,14 +50,16 @@ public final class Juggling extends TrickState {
|
||||
|
||||
juggledCards.put(player, card);
|
||||
if (juggledCards.size() == get(PLAYERS).size()) {
|
||||
juggle(game);
|
||||
return juggle(game);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
return super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void juggle(Game game) {
|
||||
private Optional<GameState> juggle(Game game) {
|
||||
Map<UUID, Card> newCards = new HashMap<>();
|
||||
juggledCards.forEach((player, card) -> newCards.put(getNextPlayer(player), card));
|
||||
|
||||
@ -77,6 +80,6 @@ public final class Juggling extends TrickState {
|
||||
TRICK, get(TRICK) + 1
|
||||
);
|
||||
|
||||
transition(game, new StartingTrick(data));
|
||||
return transition(new StartingTrick(data));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.CardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
@ -25,25 +26,25 @@ public final class PlayingCard extends TrickState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
game.notify(new UserInputMessage(get(CURRENT_PLAYER), PLAY_CARD, getTimeout(game, true)));
|
||||
timeout(game);
|
||||
return timeout(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
|
||||
if (get(CURRENT_PLAYER).equals(player) && message instanceof PlayCardMessage cardMessage) {
|
||||
if (cardMessage.getCard() == null) {
|
||||
throw new IllegalArgumentException("Card must not be null.");
|
||||
}
|
||||
transition(game, cardMessage.getCard());
|
||||
return transition(game, cardMessage.getCard());
|
||||
} else {
|
||||
super.onMessage(game, player, message);
|
||||
return super.onMessage(game, player, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(Game game) {
|
||||
public Optional<GameState> onTimeout(Game game) {
|
||||
var hand = get(HANDS).get(get(CURRENT_PLAYER));
|
||||
var stack = get(STACK);
|
||||
|
||||
@ -52,10 +53,10 @@ public final class PlayingCard extends TrickState {
|
||||
.findAny()
|
||||
.orElseThrow(() -> new AssertionError("Cannot play any card."));
|
||||
|
||||
transition(game, card.getCard());
|
||||
return transition(game, card.getCard());
|
||||
}
|
||||
|
||||
private void transition(Game game, @NotNull Card card) {
|
||||
private Optional<GameState> transition(Game game, @NotNull Card card) {
|
||||
var currentPlayer = get(CURRENT_PLAYER);
|
||||
|
||||
// create mutable stack
|
||||
@ -87,11 +88,10 @@ public final class PlayingCard extends TrickState {
|
||||
.summaryStatistics();
|
||||
|
||||
if (summary.getMax() == summary.getMin()) { // everybody has the same amount of cards
|
||||
transition(game, new FinishingTrick(data));
|
||||
return transition(new FinishingTrick(data));
|
||||
} else {
|
||||
transition(game, new PlayingCard(data.with(CURRENT_PLAYER, getNextPlayer())));
|
||||
return transition(new PlayingCard(data.with(CURRENT_PLAYER, getNextPlayer())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void checkData(GameData data) {
|
||||
|
@ -2,6 +2,9 @@ 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.GameState;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class StartingTrick extends TrickState {
|
||||
public StartingTrick(GameData data) {
|
||||
@ -9,7 +12,7 @@ public final class StartingTrick extends TrickState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnter(Game game) {
|
||||
transition(game, new PlayingCard(getData()));
|
||||
public Optional<GameState> onEnter(Game game) {
|
||||
return transition(new PlayingCard(getData()));
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ public class ColoredCard extends GameCard {
|
||||
|
||||
@Override
|
||||
public void play(UUID player, List<Card> hand, List<Pair<UUID, Card>> stack) {
|
||||
// check hand first
|
||||
checkHand(hand);
|
||||
|
||||
Card.Suit suit = CardUtils.getTrickSuit(stack);
|
||||
if (this.suit != suit && canFollowSuit(hand, suit)) {
|
||||
throw new IllegalArgumentException("Must follow suit.");
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
@ -7,6 +8,7 @@ import org.mockito.stubbing.Stubber;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -23,7 +25,7 @@ public class GameTestUtils {
|
||||
public static <T> Answer<T> finish() {
|
||||
return invocation -> {
|
||||
Game game = (Game) invocation.getMock();
|
||||
game.finish();
|
||||
game.execute(() -> Optional.of(new Finished()));
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.*;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -11,7 +12,6 @@ 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;
|
||||
|
||||
@ -138,23 +138,23 @@ public class MessageQueue implements Observer {
|
||||
|
||||
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(
|
||||
Assertions.assertThrows(
|
||||
exception,
|
||||
executionException.getCause(),
|
||||
() -> game.onMessage(queuedPlayer, queuedMessage),
|
||||
"Excepted exception for message " + queuedMessage + " from player " + queuedPlayer + "."
|
||||
);
|
||||
} else {
|
||||
Assertions.assertDoesNotThrow(() -> game.onMessage(queuedPlayer, queuedMessage).get());
|
||||
Assertions.assertDoesNotThrow(() -> game.onMessage(queuedPlayer, queuedMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
game.finish(t);
|
||||
game.transition(new GameState(GameData.EMPTY) {
|
||||
@Override
|
||||
public Optional<GameState> onEnter(Game context) {
|
||||
throw t;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.wizard.core.machine.states;
|
||||
|
||||
import eu.jonahbauer.wizard.core.machine.GameState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
@ -8,6 +8,7 @@ import eu.jonahbauer.wizard.common.model.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.machine.states.game.Finished;
|
||||
import eu.jonahbauer.wizard.core.model.GameConfiguration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import lombok.SneakyThrows;
|
||||
@ -37,7 +38,7 @@ public class DeterminingTrumpTest {
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private Game performTest(GameConfiguration 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));
|
||||
doFinish().when(game).transition(any(Predicting.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
@ -53,7 +54,7 @@ public class DeterminingTrumpTest {
|
||||
game.resume(new DeterminingTrump(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
verify(game).transition(any(Finished.class));
|
||||
return game;
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ public class DeterminingTrumpTest {
|
||||
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).transition(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());
|
||||
@ -99,7 +100,7 @@ public class DeterminingTrumpTest {
|
||||
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).transition(any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
@ -126,7 +127,7 @@ public class DeterminingTrumpTest {
|
||||
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).transition(any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
@ -155,7 +156,7 @@ public class DeterminingTrumpTest {
|
||||
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).transition(any(Predicting.class));
|
||||
order.verify(game).notify(any(StateMessage.class)); // finish
|
||||
order.verify(game, never()).notify(any());
|
||||
order.verify(game, never()).notify(any(), any());
|
||||
|
@ -8,6 +8,7 @@ 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.game.Finished;
|
||||
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
|
||||
import eu.jonahbauer.wizard.core.model.GameConfiguration;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
@ -24,7 +25,6 @@ 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 PredictingTest {
|
||||
@ -46,7 +46,7 @@ public class PredictingTest {
|
||||
);
|
||||
|
||||
Game game = spy(new Game(configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(StartingTrick.class));
|
||||
doFinish().when(game).transition(any(StartingTrick.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
@ -64,7 +64,7 @@ public class PredictingTest {
|
||||
game.resume(new Predicting(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
verify(game).transition(any(Finished.class));
|
||||
return game;
|
||||
}
|
||||
|
||||
@ -84,16 +84,16 @@ public class PredictingTest {
|
||||
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).transition(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).transition(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).transition(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).transition(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());
|
||||
@ -126,16 +126,16 @@ public class PredictingTest {
|
||||
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).transition(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).transition(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).transition(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).transition(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());
|
||||
@ -160,16 +160,16 @@ public class PredictingTest {
|
||||
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).transition(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).transition(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).transition(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).transition(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());
|
||||
|
@ -5,8 +5,9 @@ import eu.jonahbauer.wizard.common.model.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.GameConfiguration;
|
||||
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import eu.jonahbauer.wizard.core.model.GameConfiguration;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
@ -16,11 +17,10 @@ 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.GameTestUtils.finish;
|
||||
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 {
|
||||
@ -34,7 +34,7 @@ public class RoundTest {
|
||||
@SneakyThrows
|
||||
private Game performTest(long seed, GameConfiguration configuration, int round, MessageQueue queue) {
|
||||
Game game = spy(new Game(seed, configuration, queue));
|
||||
doFinish().when(game).transition(any(), any(StartingRound.class));
|
||||
doCallRealMethod().doAnswer(finish()).when(game).transition(any(StartingRound.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
@ -48,7 +48,7 @@ public class RoundTest {
|
||||
game.resume(new StartingRound(data));
|
||||
game.await();
|
||||
|
||||
verify(game, never()).transition(any(), isNull());
|
||||
verify(game).transition(any(Finished.class));
|
||||
return game;
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ public class RoundTest {
|
||||
&& 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).transition(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());
|
||||
@ -249,7 +249,7 @@ public class RoundTest {
|
||||
&& 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).transition(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());
|
||||
|
@ -18,6 +18,7 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
|
||||
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.finish;
|
||||
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;
|
||||
@ -36,8 +37,8 @@ public class TrickTest {
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private Game performTest(GameConfiguration 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));
|
||||
doCallRealMethod().doAnswer(finish()).when(game).transition(any(StartingTrick.class));
|
||||
doFinish().when(game).transition(any(FinishingRound.class));
|
||||
queue.setGame(game);
|
||||
|
||||
var playerList = List.of(players);
|
||||
@ -85,7 +86,7 @@ public class TrickTest {
|
||||
}
|
||||
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).transition(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());
|
||||
@ -132,7 +133,7 @@ public class TrickTest {
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing trick
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrickMessage trick && trick.getPlayer() == players[3])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).transition(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());
|
||||
@ -170,7 +171,7 @@ public class TrickTest {
|
||||
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).transition(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());
|
||||
@ -220,7 +221,7 @@ public class TrickTest {
|
||||
order.verify(game).notify(any(StateMessage.class)); // juggling
|
||||
order.verify(game).notify(any(UserInputMessage.class)); // user input request
|
||||
order.verify(game, times(4)).notify(any(), any(HandMessage.class));
|
||||
order.verify(game).transition(any(), any(StartingTrick.class)); // there is another trick
|
||||
order.verify(game).transition(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());
|
||||
@ -261,7 +262,7 @@ public class TrickTest {
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing trick
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrickMessage trick && trick.getPlayer() == players[3])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).transition(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());
|
||||
@ -302,7 +303,7 @@ public class TrickTest {
|
||||
order.verify(game).notify(any(CardMessage.class)); // user response
|
||||
order.verify(game).notify(any(StateMessage.class)); // finishing trick
|
||||
order.verify(game).notify(argThat(msg -> msg instanceof TrickMessage trick && trick.getPlayer() == players[0])); // trick with correct winner
|
||||
order.verify(game).transition(any(), any(FinishingRound.class)); // round is finished
|
||||
order.verify(game).transition(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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user