- refactored GameData
- removed reference to Context in State
This commit is contained in:
parent
c301d08094
commit
eba221525f
@ -1,10 +1,13 @@
|
|||||||
package eu.jonahbauer.wizard.core;
|
package eu.jonahbauer.wizard.core;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
|
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import eu.jonahbauer.wizard.core.model.Configuration;
|
||||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -15,7 +18,9 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
public class CLI {
|
public class CLI {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Game game = new Game(Configurations.DEFAULT, (player, msg) -> System.out.println(msg));
|
Configuration config = Configurations.DEFAULT.withTimeout(0);
|
||||||
|
Observer observer = (player, msg) -> System.out.println(msg);
|
||||||
|
Game game = new Game(config, observer);
|
||||||
var players = List.of(
|
var players = List.of(
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(),
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(),
|
||||||
@ -25,13 +30,15 @@ public class CLI {
|
|||||||
|
|
||||||
game.start(players);
|
game.start(players);
|
||||||
|
|
||||||
|
GameState state = null;
|
||||||
|
|
||||||
Scanner scanner = new Scanner(System.in);
|
Scanner scanner = new Scanner(System.in);
|
||||||
Pattern pattern = Pattern.compile("(\\d) (predict|play|trump) (.*)");
|
Pattern pattern = Pattern.compile("(\\d) ([a-z]+) (.*)");
|
||||||
while (scanner.hasNextLine()) {
|
while (scanner.hasNextLine()) {
|
||||||
try {
|
try {
|
||||||
Matcher matcher = pattern.matcher(scanner.nextLine());
|
Matcher matcher = pattern.matcher(scanner.nextLine());
|
||||||
if (!matcher.find()) {
|
if (!matcher.find()) {
|
||||||
System.err.println("Format is \"(\\\\d) (predict|play|trump) (.*)\"");
|
System.err.println("Format is \"(\\\\d) ([a-z]+) (.*)\"");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String player = matcher.group(1);
|
String player = matcher.group(1);
|
||||||
|
@ -8,15 +8,15 @@ import java.util.concurrent.*;
|
|||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public abstract class Context<T extends State> {
|
public abstract class Context<S extends State<S,C>, C extends Context<S,C>> {
|
||||||
protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
protected T state;
|
protected S state;
|
||||||
protected final ReentrantLock lock = new ReentrantLock();
|
protected final ReentrantLock lock = new ReentrantLock();
|
||||||
private final Condition finishCondition = lock.newCondition();
|
private final Condition finishCondition = lock.newCondition();
|
||||||
private boolean finished;
|
private boolean finished;
|
||||||
private Throwable exception;
|
private Throwable exception;
|
||||||
|
|
||||||
protected void start(@NotNull T state) {
|
protected void start(@NotNull S state) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (finished) throw new IllegalStateException("Context has already finished.");
|
if (finished) throw new IllegalStateException("Context has already finished.");
|
||||||
@ -26,14 +26,16 @@ public abstract class Context<T extends State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void transition(T currentState, T newState) {
|
public void transition(S currentState, S newState) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (state == currentState) {
|
if (state == currentState) {
|
||||||
state = newState;
|
state = newState;
|
||||||
if (currentState != null) currentState.onExit();
|
if (currentState != null) //noinspection unchecked
|
||||||
|
currentState.onExit((C) this);
|
||||||
onTransition(currentState, newState);
|
onTransition(currentState, newState);
|
||||||
if (newState != null) newState.onEnter();
|
if (newState != null) //noinspection unchecked
|
||||||
|
newState.onEnter((C) this);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Current state does not match.");
|
throw new IllegalStateException("Current state does not match.");
|
||||||
}
|
}
|
||||||
@ -65,6 +67,11 @@ public abstract class Context<T extends State> {
|
|||||||
finish(new CancellationException());
|
finish(new CancellationException());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
public boolean isDone() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
@Blocking
|
@Blocking
|
||||||
public void await() throws InterruptedException, ExecutionException, CancellationException {
|
public void await() throws InterruptedException, ExecutionException, CancellationException {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
@ -84,12 +91,13 @@ public abstract class Context<T extends State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void timeout(@NotNull T currentState, long delay) {
|
public void timeout(@NotNull S currentState, long delay) {
|
||||||
scheduler.schedule(() -> {
|
scheduler.schedule(() -> {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (state == currentState) {
|
if (state == currentState) {
|
||||||
state.onTimeout();
|
//noinspection unchecked
|
||||||
|
state.onTimeout((C) this);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
handleError(t);
|
handleError(t);
|
||||||
@ -100,8 +108,16 @@ public abstract class Context<T extends State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handleError(Throwable t) {
|
protected void handleError(Throwable t) {
|
||||||
finish(t);
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (!isDone()) {
|
||||||
|
finish(t);
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onTransition(T from, T to) {}
|
protected void onTransition(S from, S to) {}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine;
|
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.GameState;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.game.Starting;
|
import eu.jonahbauer.wizard.core.machine.states.game.Starting;
|
||||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||||
@ -12,7 +13,9 @@ import lombok.Getter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class Game extends Context<GameState> {
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
|
||||||
|
|
||||||
|
public final class Game extends Context<GameState, Game> {
|
||||||
@Getter
|
@Getter
|
||||||
private final Configuration config;
|
private final Configuration config;
|
||||||
private final Observer observer;
|
private final Observer observer;
|
||||||
@ -23,17 +26,26 @@ public final class Game extends Context<GameState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start(List<UUID> players) {
|
public void start(List<UUID> players) {
|
||||||
start(new Starting(this, GameData.builder().players(players).build()));
|
start(new Starting(new GameData().with(PLAYERS, List.copyOf(players))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resume(GameState state) {
|
public void resume(GameState state) {
|
||||||
start(state);
|
start(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameState stop() {
|
||||||
|
GameState state = this.state;
|
||||||
|
if (state != null) {
|
||||||
|
finish();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
public void onMessage(UUID player, PlayerMessage message) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
state.onMessage(player, message);
|
state.onMessage(this, player, message);
|
||||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
@ -1,334 +0,0 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine;
|
|
||||||
|
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
|
||||||
import eu.jonahbauer.wizard.core.util.Pair;
|
|
||||||
import lombok.*;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@With
|
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
@SuppressWarnings({"unused", "UnusedReturnValue", "OptionalUsedAsFieldOrParameterType", "OptionalAssignedToNull"})
|
|
||||||
public final class GameData {
|
|
||||||
private final @Unmodifiable List<UUID> players;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> score;
|
|
||||||
|
|
||||||
private final Integer round;
|
|
||||||
private final @Unmodifiable Map<UUID, @Unmodifiable List<Card>> hands;
|
|
||||||
private final Optional<Card> trumpCard;
|
|
||||||
private final Card.Suit trumpSuit;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> predictions;
|
|
||||||
private final @Unmodifiable Map<UUID, Integer> tricks;
|
|
||||||
|
|
||||||
private final Integer trick;
|
|
||||||
private final @Unmodifiable List<Pair<UUID, Card>> stack;
|
|
||||||
|
|
||||||
private final UUID currentPlayer;
|
|
||||||
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredPlayers;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredRound;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrick;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredHands;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrumpCard;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTrumpSuit;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredStack;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredPredictions;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredTricks;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredScore;
|
|
||||||
private @EqualsAndHashCode.Exclude transient boolean requiredCurrentPlayer;
|
|
||||||
|
|
||||||
@Builder(toBuilder = true)
|
|
||||||
private GameData(@Unmodifiable List<UUID> players,
|
|
||||||
@Unmodifiable Map<UUID, Integer> score,
|
|
||||||
Integer round,
|
|
||||||
@Unmodifiable Map<UUID, @Unmodifiable List<Card>> hands,
|
|
||||||
Optional<Card> trumpCard,
|
|
||||||
Card.Suit trumpSuit,
|
|
||||||
@Unmodifiable Map<UUID, Integer> predictions,
|
|
||||||
@Unmodifiable Map<UUID, Integer> tricks,
|
|
||||||
Integer trick,
|
|
||||||
@Unmodifiable List<Pair<UUID, Card>> stack,
|
|
||||||
UUID currentPlayer)
|
|
||||||
{
|
|
||||||
this.players = players != null ? List.copyOf(players) : null;
|
|
||||||
this.score = score != null ? Map.copyOf(score) : null;
|
|
||||||
|
|
||||||
this.round = round;
|
|
||||||
this.hands = hands != null ? Map.copyOf(hands) : null;
|
|
||||||
this.trumpCard = trumpCard;
|
|
||||||
this.trumpSuit = trumpSuit;
|
|
||||||
this.predictions = predictions != null ? Map.copyOf(predictions) : null;
|
|
||||||
this.tricks = tricks != null ? Map.copyOf(tricks) : null;
|
|
||||||
|
|
||||||
this.trick = trick;
|
|
||||||
this.stack = stack != null ? List.copyOf(stack) : null;
|
|
||||||
|
|
||||||
this.currentPlayer = currentPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
//<editor-fold desc="Getters" defaultstate="collapsed">
|
|
||||||
public List<UUID> getPlayers() {
|
|
||||||
if (players == null) throw new UnsupportedOperationException();
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRound() {
|
|
||||||
if (round == null) throw new UnsupportedOperationException();
|
|
||||||
return round;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTrick() {
|
|
||||||
if (trick == null) throw new UnsupportedOperationException();
|
|
||||||
return trick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, List<Card>> getHands() {
|
|
||||||
if (hands == null) throw new UnsupportedOperationException();
|
|
||||||
return hands;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Card getTrumpCard() {
|
|
||||||
if (trumpCard == null) throw new UnsupportedOperationException();
|
|
||||||
return trumpCard.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Card.Suit getTrumpSuit() {
|
|
||||||
if (trumpSuit == null) throw new UnsupportedOperationException();
|
|
||||||
return trumpSuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Pair<UUID, Card>> getStack() {
|
|
||||||
if (stack == null) throw new UnsupportedOperationException();
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getPredictions() {
|
|
||||||
if (predictions == null) throw new UnsupportedOperationException();
|
|
||||||
return predictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getTricks() {
|
|
||||||
if (tricks == null) throw new UnsupportedOperationException();
|
|
||||||
return tricks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<UUID, Integer> getScore() {
|
|
||||||
if (score == null) throw new UnsupportedOperationException();
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getCurrentPlayer() {
|
|
||||||
if (currentPlayer == null) throw new UnsupportedOperationException();
|
|
||||||
return currentPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getPlayer(int index) {
|
|
||||||
return getPlayers().get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPlayerCount() {
|
|
||||||
return getPlayers().size();
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
//<editor-fold desc="Requirements" defaultstate="collapsed">
|
|
||||||
public GameData requirePlayers() {
|
|
||||||
if (players == null) throw new AssertionError();
|
|
||||||
requiredPlayers = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireRound() {
|
|
||||||
if (round == null) throw new AssertionError();
|
|
||||||
requiredRound = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrick() {
|
|
||||||
if (trick == null) throw new AssertionError();
|
|
||||||
requiredTrick = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireHands() {
|
|
||||||
if (hands == null) throw new AssertionError();
|
|
||||||
requiredHands = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireAllHands() {
|
|
||||||
requirePlayers();
|
|
||||||
requireHands();
|
|
||||||
for (UUID uuid : players) {
|
|
||||||
if (!hands.containsKey(uuid)) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrumpCard() {
|
|
||||||
if (trumpCard == null) throw new AssertionError();
|
|
||||||
requiredTrumpCard = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTrumpSuit() {
|
|
||||||
if (trumpSuit == null) throw new AssertionError();
|
|
||||||
requiredTrumpSuit = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireStack() {
|
|
||||||
if (stack == null) throw new AssertionError();
|
|
||||||
requiredStack = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requirePredictions() {
|
|
||||||
if (predictions == null) throw new AssertionError();
|
|
||||||
requiredPredictions = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireAllPredictions() {
|
|
||||||
requirePlayers();
|
|
||||||
requirePredictions();
|
|
||||||
for (UUID uuid : players) {
|
|
||||||
if (!predictions.containsKey(uuid)) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireTricks() {
|
|
||||||
if (tricks == null) throw new AssertionError();
|
|
||||||
requiredTricks = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireScore() {
|
|
||||||
if (score == null) throw new AssertionError();
|
|
||||||
requiredScore = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData requireCurrentPlayer() {
|
|
||||||
if (currentPlayer == null) throw new AssertionError();
|
|
||||||
requiredCurrentPlayer = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData onlyRequired() {
|
|
||||||
var builder = toBuilder();
|
|
||||||
if (!requiredPlayers) builder.players(null);
|
|
||||||
if (!requiredRound) builder.round(null);
|
|
||||||
if (!requiredTrick) builder.trick(null);
|
|
||||||
if (!requiredHands) builder.hands(null);
|
|
||||||
if (!requiredTrumpCard) builder.trumpCard(null);
|
|
||||||
if (!requiredTrumpSuit) builder.trumpSuit(null);
|
|
||||||
if (!requiredStack) builder.stack(null);
|
|
||||||
if (!requiredPredictions) builder.predictions(null);
|
|
||||||
if (!requiredTricks) builder.tricks(null);
|
|
||||||
if (!requiredScore) builder.score(null);
|
|
||||||
if (!requiredCurrentPlayer) builder.currentPlayer(null);
|
|
||||||
requiredPlayers = false;
|
|
||||||
requiredRound = false;
|
|
||||||
requiredTrick = false;
|
|
||||||
requiredHands = false;
|
|
||||||
requiredTrumpCard = false;
|
|
||||||
requiredTrumpSuit = false;
|
|
||||||
requiredStack = false;
|
|
||||||
requiredPredictions = false;
|
|
||||||
requiredTricks = false;
|
|
||||||
requiredScore = false;
|
|
||||||
requiredCurrentPlayer = false;
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
//<editor-fold desc="Wither" defaultstate="collapsed">
|
|
||||||
public GameData withPrediction(UUID player, int prediction) {
|
|
||||||
Map<UUID, Integer> predictions = new HashMap<>(getPredictions());
|
|
||||||
predictions.put(player, prediction);
|
|
||||||
return withPredictions(predictions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextPlayer() {
|
|
||||||
int index = getPlayers().indexOf(getCurrentPlayer());
|
|
||||||
UUID next;
|
|
||||||
if (index == -1) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
} else if (index == getPlayerCount() - 1) {
|
|
||||||
next = getPlayers().get(0);
|
|
||||||
} else {
|
|
||||||
next = getPlayers().get(index + 1);
|
|
||||||
}
|
|
||||||
return withCurrentPlayer(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextTrick() {
|
|
||||||
int trick = getTrick();
|
|
||||||
return withTrick(trick + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withNextRound() {
|
|
||||||
int round = getRound();
|
|
||||||
return withRound(round + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameData withCardPlayed(UUID player, Card card) {
|
|
||||||
List<Card> hand = new ArrayList<>(getHands().get(player));
|
|
||||||
hand.remove(card);
|
|
||||||
Map<UUID, List<Card>> hands = new HashMap<>(getHands());
|
|
||||||
hands.put(player, hand);
|
|
||||||
List<Pair<UUID, Card>> stack = new ArrayList<>(getStack());
|
|
||||||
stack.add(Pair.of(player, card));
|
|
||||||
return withStack(stack).withHands(hands);
|
|
||||||
}
|
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder("(");
|
|
||||||
if (players != null) builder.append("players=").append(players).append(", ");
|
|
||||||
if (round != null) builder.append("round=").append(round).append(", ");
|
|
||||||
if (trick != null) builder.append("trick=").append(trick).append(", ");
|
|
||||||
if (hands != null) builder.append("hands=").append(hands).append(", ");
|
|
||||||
if (trumpCard != null) builder.append("trumpCard=").append(trumpCard).append(", ");
|
|
||||||
if (trumpSuit != null) builder.append("trumpSuit=").append(trumpSuit).append(", ");
|
|
||||||
if (stack != null) builder.append("stack=").append(stack).append(", ");
|
|
||||||
if (predictions != null) builder.append("predictions=").append(predictions).append(", ");
|
|
||||||
if (tricks != null) builder.append("tricks=").append(tricks).append(", ");
|
|
||||||
if (currentPlayer != null) builder.append("currentPlayer=").append(currentPlayer).append(", ");
|
|
||||||
if (builder.length() > 1) builder.setLength(builder.length() - 2);
|
|
||||||
builder.append(")");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An interface for @lombok.experimental.Delegate
|
|
||||||
public interface Getters {
|
|
||||||
@NotNull List<UUID> getPlayers();
|
|
||||||
@NotNull Map<UUID, Integer> getScore();
|
|
||||||
|
|
||||||
int getRound();
|
|
||||||
@NotNull Map<UUID, List<Card>> getHands();
|
|
||||||
@Nullable Card getTrumpCard();
|
|
||||||
@NotNull Card.Suit getTrumpSuit();
|
|
||||||
@NotNull Map<UUID, Integer> getPredictions();
|
|
||||||
@NotNull Map<UUID, Integer> getTricks();
|
|
||||||
|
|
||||||
int getTrick();
|
|
||||||
@NotNull List<Pair<UUID, Card>> getStack();
|
|
||||||
|
|
||||||
@NotNull UUID getCurrentPlayer();
|
|
||||||
@NotNull UUID getPlayer(int index);
|
|
||||||
int getPlayerCount();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,219 @@
|
|||||||
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Unmodifiable
|
||||||
|
@EqualsAndHashCode(of = "values")
|
||||||
|
public final class GameData {
|
||||||
|
private static final int SIZE = 11;
|
||||||
|
|
||||||
|
public static final Key<List<UUID>> PLAYERS = new Key<>("players", 0);
|
||||||
|
public static final Key<Map<UUID, Integer>> SCORE = new Key<>("score", 1);
|
||||||
|
public static final Key<Integer> ROUND = new Key<>("round", 2);
|
||||||
|
public static final Key<Map<UUID, List<Card>>> HANDS = new Key<>("hands", 3);
|
||||||
|
public static final Key<Card> TRUMP_CARD = new Key<>("trumpCard", 4);
|
||||||
|
public static final Key<Card.Suit> TRUMP_SUIT = new Key<>("trumpSuit", 5);
|
||||||
|
public static final Key<Map<UUID, Integer>> PREDICTIONS = new Key<>("predictions", 6);
|
||||||
|
public static final Key<Map<UUID, Integer>> TRICKS = new Key<>("tricks", 7);
|
||||||
|
public static final Key<Integer> TRICK = new Key<>("trick", 8);
|
||||||
|
public static final Key<List<Pair<UUID, Card>>> STACK = new Key<>("stack", 9);
|
||||||
|
public static final Key<UUID> CURRENT_PLAYER = new Key<>("currentPlayer", 10);
|
||||||
|
|
||||||
|
private final Object[] values;
|
||||||
|
private final boolean[] present;
|
||||||
|
private transient final boolean[] required = new boolean[SIZE];
|
||||||
|
|
||||||
|
public GameData() {
|
||||||
|
this.values = new Object[SIZE];
|
||||||
|
this.present = new boolean[SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameData(Object[] values, boolean[] present) {
|
||||||
|
this.values = values;
|
||||||
|
this.present = present;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value to which the specified key is mapped or {@code null} if this map contains no mapping for the
|
||||||
|
* key.
|
||||||
|
* @param key the key whose associated value is to be returned
|
||||||
|
* @param <T> the value type
|
||||||
|
* @return the value to which the specified key is mapped, or null if this map contains no mapping for the key
|
||||||
|
*/
|
||||||
|
public <T> T get(@NotNull Key<T> key) {
|
||||||
|
int index = key.index();
|
||||||
|
if (!present[index]) throw new NoSuchElementException();
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this map with the value for the specified key changed or {@code this} if the current value
|
||||||
|
* is equal to the new value.
|
||||||
|
* @param key the key whose associated value is to be changed
|
||||||
|
* @param value value to be associated with the specified key
|
||||||
|
* @param <T> the value type
|
||||||
|
* @return the newly generated map or {@code this} is no changes had to be made
|
||||||
|
*/
|
||||||
|
public <T> GameData with(@NotNull Key<T> key, T value) {
|
||||||
|
int index = key.index();
|
||||||
|
|
||||||
|
if (present[index] && Objects.equals(values[index], value)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index] = value;
|
||||||
|
newPresent[index] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T1, T2> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2) {
|
||||||
|
int index1 = key1.index();
|
||||||
|
int index2 = key2.index();
|
||||||
|
|
||||||
|
if (present[index1]
|
||||||
|
&& present[index2]
|
||||||
|
&& Objects.equals(values[index1], value1)
|
||||||
|
&& Objects.equals(values[index2], value2)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index1] = value1;
|
||||||
|
newPresent[index1] = true;
|
||||||
|
newValues[index2] = value2;
|
||||||
|
newPresent[index2] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T1, T2, T3> GameData with(@NotNull Key<T1> key1, T1 value1, Key<T2> key2, T2 value2, Key<T3> key3, T3 value3) {
|
||||||
|
int index1 = key1.index();
|
||||||
|
int index2 = key2.index();
|
||||||
|
int index3 = key3.index();
|
||||||
|
|
||||||
|
if (present[index1]
|
||||||
|
&& present[index2]
|
||||||
|
&& present[index3]
|
||||||
|
&& Objects.equals(values[index1], value1)
|
||||||
|
&& Objects.equals(values[index2], value2)
|
||||||
|
&& Objects.equals(values[index3], value3)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
newValues[index1] = value1;
|
||||||
|
newPresent[index1] = true;
|
||||||
|
newValues[index2] = value2;
|
||||||
|
newPresent[index2] = true;
|
||||||
|
newValues[index3] = value3;
|
||||||
|
newPresent[index3] = true;
|
||||||
|
return new GameData(newValues, newPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for the specified key or throws a
|
||||||
|
* {@link NoSuchElementException} otherwise.
|
||||||
|
* @param key key whose presence in this map is to be tested
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for the specified key
|
||||||
|
* @see Map#containsKey(Object)
|
||||||
|
*/
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public GameData require(@NotNull Key<?> key) {
|
||||||
|
if (!present[key.index()]) {
|
||||||
|
throw new NoSuchElementException("Could not find required value '" + key + "'.");
|
||||||
|
}
|
||||||
|
required[key.index()] = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for each of the specified keys or throws a
|
||||||
|
* {@link NoSuchElementException} otherwise.
|
||||||
|
* @param keys keys whose presence in this map is to be tested
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for at least one of the specified keys
|
||||||
|
* @see #require(Key)
|
||||||
|
*/
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public GameData require(Key<?>...keys) {
|
||||||
|
for (Key<?> key : keys) {
|
||||||
|
require(key);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code this} if this map contains a mapping for both the specified keys and the specified map contains
|
||||||
|
* a mapping for each entry in the specified list.
|
||||||
|
* @param list key to a list whose entries' presence is to be tested
|
||||||
|
* @param map key to a map which must contain the specified keys
|
||||||
|
* @param <K> key type
|
||||||
|
* @param <V> value type
|
||||||
|
* @return {@code this}
|
||||||
|
* @throws NoSuchElementException if this map contains no mapping for at least one of the specified keys or if
|
||||||
|
* the associated map contains no mapping for at least one of the entries of
|
||||||
|
* the associated list
|
||||||
|
*/
|
||||||
|
@Contract("_,_ -> this")
|
||||||
|
public <K, V> GameData requireEach(Key<? extends Collection<K>> list, Key<Map<K,V>> map) {
|
||||||
|
require(list, map);
|
||||||
|
var mapValue = get(map);
|
||||||
|
var listValue = get(list);
|
||||||
|
for (K k : listValue) {
|
||||||
|
if (!mapValue.containsKey(k)) throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retains only the mappings that have been required since object creation.
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public GameData clean() {
|
||||||
|
Object[] newValues = Arrays.copyOf(this.values, SIZE);
|
||||||
|
boolean[] newPresent = Arrays.copyOf(this.present, SIZE);
|
||||||
|
|
||||||
|
boolean modified = false;
|
||||||
|
for (int i = 0; i < SIZE; i++) {
|
||||||
|
if (!required[i]) {
|
||||||
|
newValues[i] = null;
|
||||||
|
newPresent[i] = false;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) return new GameData(newValues, newPresent);
|
||||||
|
else return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Value
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Key<T> {
|
||||||
|
String name;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +1,37 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states;
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.experimental.Delegate;
|
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.UUID;
|
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
|
@Unmodifiable
|
||||||
public abstract class GameState implements State {
|
public abstract class GameState implements State<GameState, Game> {
|
||||||
@Getter
|
@Getter
|
||||||
private final Game game;
|
|
||||||
@Getter
|
|
||||||
@Delegate(types = GameData.Getters.class)
|
|
||||||
private final GameData data;
|
private final GameData data;
|
||||||
|
|
||||||
public GameState(Game game, GameData data) {
|
public GameState(GameData data) {
|
||||||
this.game = game;
|
this.data = data.require(PLAYERS).clean();
|
||||||
this.data = data.requirePlayers().onlyRequired();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void transition(GameState state) {
|
protected final void transition(Game game, GameState state) {
|
||||||
getGame().transition(this, state);
|
game.transition(this, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void timeout() {
|
protected final void timeout(Game game) {
|
||||||
getGame().timeout(this, getTimeout(false));
|
game.timeout(this, getTimeout(game, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final long getTimeout(boolean absolute) {
|
protected final long getTimeout(Game game, boolean absolute) {
|
||||||
return (absolute ? System.currentTimeMillis() : 0) + getGame().getConfig().timeout();
|
return (absolute ? System.currentTimeMillis() : 0) + game.getConfig().timeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
throw new IllegalStateException("You cannot do that right now.");
|
throw new IllegalStateException("You cannot do that right now.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,4 +39,33 @@ public abstract class GameState implements State {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName();
|
return getClass().getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> T get(GameData.Key<T> key) {
|
||||||
|
return getData().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayerCount() {
|
||||||
|
return get(PLAYERS).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getPlayer(int index) {
|
||||||
|
return get(PLAYERS).get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getNextPlayer() {
|
||||||
|
return getNextPlayer(get(CURRENT_PLAYER));
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getNextPlayer(UUID player) {
|
||||||
|
var players = get(PLAYERS);
|
||||||
|
var index = players.indexOf(player);
|
||||||
|
if (index == -1) {
|
||||||
|
throw new AssertionError("Player is not a valid player.");
|
||||||
|
} else if (index == players.size() - 1) {
|
||||||
|
return players.get(0);
|
||||||
|
} else {
|
||||||
|
return players.get(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
|
public class InvalidDataException extends RuntimeException {
|
||||||
|
public InvalidDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states;
|
package eu.jonahbauer.wizard.core.machine.states;
|
||||||
|
|
||||||
public interface State {
|
import eu.jonahbauer.wizard.core.machine.Context;
|
||||||
default void onEnter() {}
|
|
||||||
default void onTimeout() {}
|
public interface State<S extends State<S,C>, C extends Context<S,C>> {
|
||||||
default void onExit() {}
|
default void onEnter(C context) {}
|
||||||
|
default void onTimeout(C context) {}
|
||||||
|
default void onExit(C context) {}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class Finishing extends GameState {
|
public final class Finishing extends GameState {
|
||||||
|
|
||||||
public Finishing(Game game, GameData data) {
|
public Finishing(GameData data) {
|
||||||
super(game, data.requireScore());
|
super(data.require(SCORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
getGame().notify(new ScoreMessage(getScore()));
|
game.notify(new ScoreMessage(get(SCORE)));
|
||||||
getGame().finish();
|
game.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.game;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class Starting extends GameState {
|
public final class Starting extends GameState {
|
||||||
|
|
||||||
public Starting(Game game, GameData data) {
|
public Starting(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new StartingRound(getGame(), getData().withRound(0).withScore(Collections.emptyMap())));
|
transition(game, new StartingRound(getData().with(
|
||||||
|
ROUND, 0,
|
||||||
|
SCORE, Map.of()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,46 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.HandMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.HandMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
import eu.jonahbauer.wizard.core.model.Deck;
|
import eu.jonahbauer.wizard.core.model.Deck;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class Dealing extends RoundState {
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
private transient Map<UUID, List<Card>> hands;
|
|
||||||
|
|
||||||
public Dealing(Game game, GameData data) {
|
public final class Dealing extends RoundState {
|
||||||
super(game, data);
|
|
||||||
|
public Dealing(GameData data) {
|
||||||
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
Deck deck = new Deck(getGame().getConfig().cards());
|
Deck deck = new Deck(game.getConfig().cards());
|
||||||
deck.shuffle();
|
deck.shuffle();
|
||||||
|
|
||||||
hands = new HashMap<>();
|
var hands = new HashMap<UUID, List<Card>>();
|
||||||
|
|
||||||
int dealer = getRound();
|
int round = get(ROUND);
|
||||||
int playerCount = getPlayerCount();
|
int playerCount = getPlayerCount();
|
||||||
int cardCount = getRound() + 1;
|
int cardCount = round + 1;
|
||||||
for (int i = 1; i <= playerCount; i++) {
|
for (int i = 1; i <= playerCount; i++) {
|
||||||
int player = (dealer + i) % playerCount;
|
int player = (round + i) % playerCount;
|
||||||
hands.put(getPlayer(player), deck.draw(cardCount));
|
hands.put(getPlayer(player), deck.draw(cardCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Card> trumpCard = Optional.ofNullable(deck.draw());
|
for (UUID player : get(PLAYERS)) {
|
||||||
|
game.notify(player, new HandMessage(player, hands.get(player)));
|
||||||
|
}
|
||||||
|
|
||||||
transition(new DeterminingTrump(
|
Card trumpCard = deck.draw();
|
||||||
getGame(),
|
transition(game, new DeterminingTrump(
|
||||||
getData().withHands(hands)
|
getData().with(
|
||||||
.withTrumpCard(trumpCard)
|
HANDS, Map.copyOf(hands),
|
||||||
|
TRUMP_CARD, trumpCard
|
||||||
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (hands != null) {
|
|
||||||
for (UUID player : getPlayers()) {
|
|
||||||
getGame().notify(player, new HandMessage(player, hands.get(player)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,71 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.TrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.TrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PICK_TRUMP;
|
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PICK_TRUMP;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class DeterminingTrump extends RoundState {
|
public final class DeterminingTrump extends RoundState {
|
||||||
private transient Card.Suit trumpSuit;
|
public DeterminingTrump(GameData data) {
|
||||||
|
super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS));
|
||||||
public DeterminingTrump(Game game, GameData data) {
|
|
||||||
super(game, data.requireAllHands().requireTrumpCard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
Card trumpCard = getTrumpCard();
|
Card trumpCard = getData().get(TRUMP_CARD);
|
||||||
Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE;
|
Card.Suit trumpSuit = trumpCard != null ? trumpCard.getTrumpSuit() : Card.Suit.NONE;
|
||||||
if (trumpSuit == null) {
|
if (trumpSuit == null) {
|
||||||
getGame().notify(new TrumpMessage(getTrumpCard(), null));
|
game.notify(new TrumpMessage(trumpCard, null));
|
||||||
getGame().notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(true)));
|
game.notify(new UserInputMessage(getDealer(), PICK_TRUMP, getTimeout(game, true)));
|
||||||
timeout();
|
timeout(game);
|
||||||
} else {
|
} else {
|
||||||
this.trumpSuit = trumpSuit;
|
transition(game, trumpSuit);
|
||||||
transition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimeout() {
|
public void onTimeout(Game game) {
|
||||||
Card.Suit[] suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
Card.Suit[] suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
||||||
this.trumpSuit = suits[(int)(Math.random() * suits.length)];
|
transition(game, suits[(int) (Math.random() * suits.length)]);
|
||||||
transition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExit() {
|
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
if (trumpSuit != null) {
|
|
||||||
getGame().notify(new TrumpMessage(getTrumpCard(), trumpSuit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
|
||||||
if (getDealer().equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
if (getDealer().equals(player) && message instanceof PickTrumpMessage trumpMessage) {
|
||||||
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
checkTrumpSuit(trumpMessage.getTrumpSuit());
|
||||||
this.trumpSuit = trumpMessage.getTrumpSuit();
|
transition(game, trumpMessage.getTrumpSuit());
|
||||||
transition();
|
|
||||||
} else {
|
} else {
|
||||||
super.onMessage(player, message);
|
super.onMessage(game, player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void checkTrumpSuit(Card.Suit suit) {
|
void checkTrumpSuit(Card.Suit suit) {
|
||||||
Card.Suit[] suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
Card.Suit[] suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
|
||||||
for (Card.Suit s : suits) {
|
for (Card.Suit s : suits) {
|
||||||
if (s == suit) return;
|
if (s == suit) return;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + ".");
|
throw new IllegalArgumentException("Trump suit must be one of " + Arrays.toString(suits) + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transition() {
|
private void transition(Game game, @NotNull Card.Suit trumpSuit) {
|
||||||
if (trumpSuit == null) throw new AssertionError();
|
game.notify(new TrumpMessage(getData().get(TRUMP_CARD), trumpSuit));
|
||||||
|
transition(game, new Predicting(getData().with(
|
||||||
transition(new Predicting(
|
TRUMP_SUIT, trumpSuit,
|
||||||
getGame(),
|
PREDICTIONS, Map.of(),
|
||||||
getData().withTrumpSuit(trumpSuit)
|
CURRENT_PLAYER, getNextPlayer(getDealer())
|
||||||
.withPredictions(Collections.emptyMap())
|
)));
|
||||||
.withCurrentPlayer(getDealer())
|
|
||||||
.withNextPlayer()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.game.Finishing;
|
import eu.jonahbauer.wizard.core.machine.states.game.Finishing;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.ScoreMessage;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
@ -10,47 +11,38 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class FinishingRound extends RoundState {
|
public final class FinishingRound extends RoundState {
|
||||||
private transient Map<UUID, Integer> points;
|
|
||||||
|
|
||||||
public FinishingRound(Game game, GameData data) {
|
public FinishingRound(GameData data) {
|
||||||
super(game, data.requireAllPredictions().requireTricks().requireScore());
|
super(data.requireEach(PLAYERS, PREDICTIONS).require(TRICKS, SCORE));
|
||||||
|
checkData(data);
|
||||||
int tricks = data.getTricks().values().stream().mapToInt(i -> i).sum();
|
|
||||||
if (tricks != data.getRound() + 1) {
|
|
||||||
throw new AssertionError("Unexpected number of tricks in round " + data.getRound() + ": " + tricks + ".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
points = getPoints();
|
var points = getPoints();
|
||||||
|
game.notify(new ScoreMessage(points));
|
||||||
|
|
||||||
var score = new HashMap<>(getScore());
|
var score = new HashMap<>(get(SCORE));
|
||||||
points.forEach((uuid, p) -> score.compute(uuid, (u, total) -> total == null ? p : total + p));
|
points.forEach((uuid, amount) -> score.merge(uuid, amount, Integer::sum));
|
||||||
|
|
||||||
GameData data = getData().withScore(score);
|
GameData data = getData().with(SCORE, Map.copyOf(score));
|
||||||
|
|
||||||
if (60 / getPlayerCount() == getRound() + 1) {
|
if (60 / getPlayerCount() == get(ROUND) + 1) {
|
||||||
transition(new Finishing(getGame(), data));
|
transition(game, new Finishing(data));
|
||||||
} else {
|
} else {
|
||||||
transition(new StartingRound(getGame(), data.withNextRound()));
|
transition(game, new StartingRound(data.with(ROUND, get(ROUND) + 1)));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (points != null) {
|
|
||||||
getGame().notify(new ScoreMessage(points));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Map<UUID, Integer> getPoints() {
|
Map<UUID, Integer> getPoints() {
|
||||||
var points = new HashMap<UUID, Integer>();
|
var points = new HashMap<UUID, Integer>();
|
||||||
for (UUID player : getPlayers()) {
|
for (UUID player : get(PLAYERS)) {
|
||||||
int prediction = getPredictions().get(player);
|
int prediction = get(PREDICTIONS).get(player);
|
||||||
int tricks = getTricks().getOrDefault(player, 0);
|
int tricks = get(TRICKS).getOrDefault(player, 0);
|
||||||
|
|
||||||
if (tricks == prediction) {
|
if (tricks == prediction) {
|
||||||
points.put(player, 20 + 10 * tricks);
|
points.put(player, 20 + 10 * tricks);
|
||||||
@ -60,4 +52,12 @@ public final class FinishingRound extends RoundState {
|
|||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkData(GameData data) {
|
||||||
|
// the number of tricks played should be equal to the number of tricks in total
|
||||||
|
int tricks = data.get(TRICKS).values().stream().mapToInt(i -> i).sum();
|
||||||
|
if (tricks != data.get(ROUND) + 1) {
|
||||||
|
throw new InvalidDataException("Unexpected number of tricks in round " + data.get(ROUND) + ": " + tricks + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
|
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.PredictionMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.PredictionMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
||||||
@ -10,36 +10,34 @@ import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.MAKE_PREDICTION;
|
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.MAKE_PREDICTION;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public final class Predicting extends RoundState {
|
public final class Predicting extends RoundState {
|
||||||
private transient Integer prediction;
|
public Predicting(GameData data) {
|
||||||
|
super(data.requireEach(PLAYERS, HANDS).require(PREDICTIONS, TRUMP_SUIT, CURRENT_PLAYER));
|
||||||
public Predicting(Game game, GameData data) {
|
|
||||||
super(game, data.requireAllHands().requirePredictions().requireTrumpSuit().requireCurrentPlayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
getGame().notify(new UserInputMessage(getCurrentPlayer(), MAKE_PREDICTION, getTimeout(true)));
|
game.notify(new UserInputMessage(getData().get(CURRENT_PLAYER), MAKE_PREDICTION, getTimeout(game, true)));
|
||||||
timeout();
|
timeout(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimeout() {
|
public void onTimeout(Game game) {
|
||||||
try {
|
try {
|
||||||
checkPrediction(0);
|
checkPrediction(game, 0);
|
||||||
this.prediction = 0;
|
transition(game, 0);
|
||||||
transition();
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
try {
|
try {
|
||||||
checkPrediction(1);
|
checkPrediction(game, 1);
|
||||||
this.prediction = 1;
|
transition(game, 1);
|
||||||
transition();
|
|
||||||
} catch (IllegalArgumentException e2) {
|
} catch (IllegalArgumentException e2) {
|
||||||
throw new AssertionError(e2);
|
throw new AssertionError(e2);
|
||||||
}
|
}
|
||||||
@ -47,57 +45,54 @@ public final class Predicting extends RoundState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onExit() {
|
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
if (prediction != null) {
|
if (getData().get(CURRENT_PLAYER).equals(player) && message instanceof PredictMessage predictMessage) {
|
||||||
getGame().notify(new PredictionMessage(getCurrentPlayer(), prediction));
|
checkPrediction(game, predictMessage.getPrediction());
|
||||||
}
|
transition(game, predictMessage.getPrediction());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
|
||||||
if (getCurrentPlayer().equals(player) && message instanceof PredictMessage predictMessage) {
|
|
||||||
checkPrediction(predictMessage.getPrediction());
|
|
||||||
this.prediction = predictMessage.getPrediction();
|
|
||||||
transition();
|
|
||||||
} else {
|
} else {
|
||||||
super.onMessage(player, message);
|
super.onMessage(game, player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void checkPrediction(int prediction) {
|
void checkPrediction(Game game, int prediction) {
|
||||||
if (prediction < 0 || prediction > getRound() + 1) {
|
int round = get(ROUND);
|
||||||
throw new IllegalArgumentException("Prediction must be between 0 and " + (getRound() + 1) + ".");
|
if (prediction < 0 || prediction > round + 1) {
|
||||||
|
throw new IllegalArgumentException("Prediction must be between 0 and " + (round + 1) + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getGame().getConfig().allowExactPredictions() && isLastPlayer()) {
|
if (!game.getConfig().allowExactPredictions() && isLastPlayer()) {
|
||||||
int sum = getPredictions().values().stream().mapToInt(i -> i).sum();
|
int sum = get(PREDICTIONS).values().stream().mapToInt(i -> i).sum();
|
||||||
if (sum + prediction == getRound() + 1) {
|
if (sum + prediction == round + 1) {
|
||||||
throw new IllegalArgumentException("Predictions must not add up.");
|
throw new IllegalArgumentException("Predictions must not add up.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transition() {
|
private void transition(Game game, int prediction) {
|
||||||
if (prediction == null) throw new AssertionError();
|
game.notify(new PredictionMessage(get(CURRENT_PLAYER), prediction));
|
||||||
|
|
||||||
GameData data = getData().withPrediction(getCurrentPlayer(), prediction)
|
// add prediction
|
||||||
.withNextPlayer();
|
var predictions = new HashMap<>(get(PREDICTIONS));
|
||||||
|
predictions.put(get(CURRENT_PLAYER), prediction);
|
||||||
|
|
||||||
|
GameData data = getData().with(
|
||||||
|
PREDICTIONS, Map.copyOf(predictions),
|
||||||
|
CURRENT_PLAYER, getNextPlayer()
|
||||||
|
);
|
||||||
|
|
||||||
if (isLastPlayer()) {
|
if (isLastPlayer()) {
|
||||||
transition(new StartingTrick(
|
data = data.with(
|
||||||
getGame(),
|
TRICK, 0,
|
||||||
data.withTrick(0).withTricks(Collections.emptyMap())
|
TRICKS, Map.of()
|
||||||
));
|
);
|
||||||
|
transition(game, new StartingTrick(data));
|
||||||
} else {
|
} else {
|
||||||
transition(new Predicting(
|
transition(game, new Predicting(data));
|
||||||
getGame(),
|
|
||||||
data
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLastPlayer() {
|
private boolean isLastPlayer() {
|
||||||
return getDealer().equals(getCurrentPlayer());
|
return getDealer().equals(get(CURRENT_PLAYER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
package eu.jonahbauer.wizard.core.machine.states.round;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
import eu.jonahbauer.wizard.core.machine.states.GameState;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public abstract class RoundState extends GameState {
|
public abstract class RoundState extends GameState {
|
||||||
public RoundState(Game game, GameData data) {
|
public RoundState(GameData data) {
|
||||||
super(game, data.requireRound().requireScore());
|
super(data.require(ROUND, SCORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UUID getDealer() {
|
protected UUID getDealer() {
|
||||||
return getPlayer(getRound() % getPlayerCount());
|
return getPlayer(getData().get(ROUND) % getPlayerCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.round;
|
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.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
|
|
||||||
public final class StartingRound extends RoundState {
|
public final class StartingRound extends RoundState {
|
||||||
public StartingRound(Game game, GameData data) {
|
public StartingRound(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new Dealing(getGame(), getData()));
|
transition(game, new Dealing(getData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,48 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||||
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
|
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.TrickMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.TrickMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
import eu.jonahbauer.wizard.core.util.Pair;
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public final class FinishingTrick extends TrickState {
|
public final class FinishingTrick extends TrickState {
|
||||||
private transient UUID winner;
|
|
||||||
|
|
||||||
public FinishingTrick(Game game, GameData data) {
|
public FinishingTrick(GameData data) {
|
||||||
super(game, data.requireStack());
|
super(data.require(STACK));
|
||||||
assert assertions();
|
checkData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
this.winner = getWinner();
|
var winner = getWinner();
|
||||||
|
game.notify(new TrickMessage(winner, get(STACK).stream().map(Pair::second).toList()));
|
||||||
|
|
||||||
var tricks = new HashMap<>(getTricks());
|
var tricks = new HashMap<>(get(TRICKS));
|
||||||
tricks.compute(winner, (k, t) -> t == null ? 1 : t + 1);
|
tricks.merge(winner, 1, Integer::sum);
|
||||||
GameData data = getData().withTricks(tricks);
|
|
||||||
|
|
||||||
if (getTrick() < getRound()) {
|
GameData data = getData().with(TRICKS, Map.copyOf(tricks));
|
||||||
transition(new StartingTrick(
|
|
||||||
getGame(),
|
if (get(TRICK) < get(ROUND)) {
|
||||||
data.withCurrentPlayer(winner)
|
transition(game, new StartingTrick(data.with(
|
||||||
.withNextTrick()
|
CURRENT_PLAYER, winner,
|
||||||
));
|
TRICK, get(TRICK) + 1
|
||||||
|
)));
|
||||||
} else {
|
} else {
|
||||||
transition(new FinishingRound(getGame(), data));
|
transition(game, new FinishingRound(data));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (winner != null) {
|
|
||||||
getGame().notify(new TrickMessage(winner, getStack().stream().map(Pair::second).toList()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
UUID getWinner() {
|
UUID getWinner() {
|
||||||
var wizard = getStack().stream()
|
var wizard = get(STACK).stream()
|
||||||
.filter(pair -> pair.second().getSuit() == Card.Suit.WIZARD)
|
.filter(pair -> pair.second().getSuit() == Card.Suit.WIZARD)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
@ -57,16 +50,15 @@ public final class FinishingTrick extends TrickState {
|
|||||||
return wizard.first();
|
return wizard.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getStack().stream().allMatch(pair -> pair.second().getSuit() == Card.Suit.JESTER)) {
|
if (get(STACK).stream().allMatch(pair -> pair.second().getSuit() == Card.Suit.JESTER)) {
|
||||||
return getStack().get(0).first();
|
return get(STACK).get(0).first();
|
||||||
}
|
}
|
||||||
|
|
||||||
var trumpSuit = getTrumpSuit();
|
var trumpSuit = get(TRUMP_SUIT);
|
||||||
var suit = getTrickSuit();
|
var suit = getTrickSuit();
|
||||||
return getStack().stream()
|
return get(STACK).stream()
|
||||||
.max(
|
.max(
|
||||||
Comparator.<Pair<UUID, Card>>comparingInt(pair -> pair.second()
|
Comparator.<Pair<UUID, Card>>comparingInt(pair -> pair.second().getSuit() == trumpSuit ? 1 : 0)
|
||||||
.getSuit() == trumpSuit ? 1 : 0)
|
|
||||||
.thenComparing(pair -> pair.second().getSuit() == suit ? 1 : 0)
|
.thenComparing(pair -> pair.second().getSuit() == suit ? 1 : 0)
|
||||||
.thenComparing(pair -> pair.second().getValue())
|
.thenComparing(pair -> pair.second().getValue())
|
||||||
)
|
)
|
||||||
@ -74,16 +66,20 @@ public final class FinishingTrick extends TrickState {
|
|||||||
.first();
|
.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean assertions() {
|
private static void checkData(GameData data) {
|
||||||
if (getStack().size() != getPlayerCount()) {
|
// every player has played exactly one card
|
||||||
throw new AssertionError("Unexpected stack size: " + getStack().size() + " (expected " + getPlayerCount() + ").");
|
int stackSize = data.get(STACK).size();
|
||||||
|
int playerCount = data.get(PLAYERS).size();
|
||||||
|
if (stackSize != playerCount) {
|
||||||
|
throw new InvalidDataException("Unexpected stack size: " + stackSize + " (expected " + playerCount + ").");
|
||||||
}
|
}
|
||||||
int expectedSize = getRound() - getTrick();
|
|
||||||
for (List<Card> hand : getHands().values()) {
|
// every player has round - trick cards left on his hand
|
||||||
|
int expectedSize = data.get(ROUND) - data.get(TRICK);
|
||||||
|
for (List<Card> hand : data.get(HANDS).values()) {
|
||||||
if (hand.size() != expectedSize) {
|
if (hand.size() != expectedSize) {
|
||||||
throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + ").");
|
throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + ").");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,49 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.CardMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.CardMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
import eu.jonahbauer.wizard.core.messages.observer.UserInputMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayerMessage;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PLAY_CARD;
|
import static eu.jonahbauer.wizard.core.messages.observer.UserInputMessage.Action.PLAY_CARD;
|
||||||
|
|
||||||
public final class PlayingCard extends TrickState {
|
public final class PlayingCard extends TrickState {
|
||||||
private transient Card card;
|
|
||||||
|
|
||||||
public PlayingCard(Game game, GameData data) {
|
public PlayingCard(GameData data) {
|
||||||
super(game, data.requireStack());
|
super(data.require(STACK));
|
||||||
assert assertions();
|
checkData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
getGame().notify(new UserInputMessage(getCurrentPlayer(), PLAY_CARD, getTimeout(true)));
|
game.notify(new UserInputMessage(get(CURRENT_PLAYER), PLAY_CARD, getTimeout(game, true)));
|
||||||
timeout();
|
timeout(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(UUID player, PlayerMessage message) {
|
public void onMessage(Game game, UUID player, PlayerMessage message) {
|
||||||
if (getCurrentPlayer().equals(player) && message instanceof PlayCardMessage cardMessage) {
|
if (get(CURRENT_PLAYER).equals(player) && message instanceof PlayCardMessage cardMessage) {
|
||||||
checkCard(cardMessage.getCard());
|
checkCard(cardMessage.getCard());
|
||||||
this.card = cardMessage.getCard();
|
transition(game, cardMessage.getCard());
|
||||||
transition();
|
|
||||||
} else {
|
} else {
|
||||||
super.onMessage(player, message);
|
super.onMessage(game, player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimeout() {
|
public void onTimeout(Game game) {
|
||||||
var hand = getHands().get(getCurrentPlayer());
|
var hand = get(HANDS).get(get(CURRENT_PLAYER));
|
||||||
this.card = hand.stream().filter(c -> {
|
var card = hand.stream().filter(c -> {
|
||||||
try {
|
try {
|
||||||
checkCard(c);
|
checkCard(c);
|
||||||
return true;
|
return true;
|
||||||
@ -51,19 +51,12 @@ public final class PlayingCard extends TrickState {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).findAny().orElseThrow(() -> new AssertionError("Cannot play any card."));
|
}).findAny().orElseThrow(() -> new AssertionError("Cannot play any card."));
|
||||||
transition();
|
transition(game, card);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExit() {
|
|
||||||
if (card != null) {
|
|
||||||
getGame().notify(new CardMessage(getCurrentPlayer(), card));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void checkCard(Card card) {
|
void checkCard(Card card) {
|
||||||
var hand = getHands().get(getCurrentPlayer());
|
var hand = get(HANDS).get(get(CURRENT_PLAYER));
|
||||||
if (!hand.contains(card)) {
|
if (!hand.contains(card)) {
|
||||||
throw new IllegalArgumentException("You do not have this card on your hand.");
|
throw new IllegalArgumentException("You do not have this card on your hand.");
|
||||||
}
|
}
|
||||||
@ -76,34 +69,51 @@ public final class PlayingCard extends TrickState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transition() {
|
private void transition(Game game, @NotNull Card card) {
|
||||||
if (card == null) throw new AssertionError();
|
var currentPlayer = get(CURRENT_PLAYER);
|
||||||
|
game.notify(new CardMessage(currentPlayer, card));
|
||||||
|
|
||||||
GameData data = getData().withCardPlayed(getCurrentPlayer(), card);
|
// add card to stack
|
||||||
|
var stack = get(STACK);
|
||||||
|
var mutableStack = new ArrayList<Pair<UUID, Card>>(stack.size() + 1);
|
||||||
|
mutableStack.addAll(stack);
|
||||||
|
mutableStack.add(Pair.of(currentPlayer, card));
|
||||||
|
|
||||||
var summary = data.getHands().values().stream()
|
// remove card from hand
|
||||||
|
var hands = get(HANDS);
|
||||||
|
var mutableHands = new HashMap<>(hands);
|
||||||
|
var hand = hands.get(currentPlayer);
|
||||||
|
var mutableHand = new ArrayList<>(hand);
|
||||||
|
mutableHand.remove(card);
|
||||||
|
mutableHands.put(currentPlayer, List.copyOf(mutableHand));
|
||||||
|
|
||||||
|
GameData data = getData().with(
|
||||||
|
STACK, List.copyOf(mutableStack),
|
||||||
|
HANDS, Map.copyOf(mutableHands)
|
||||||
|
);
|
||||||
|
|
||||||
|
var summary = data.get(HANDS).values().stream()
|
||||||
.mapToInt(Collection::size)
|
.mapToInt(Collection::size)
|
||||||
.summaryStatistics();
|
.summaryStatistics();
|
||||||
|
|
||||||
if (summary.getMax() == summary.getMin()) {
|
if (summary.getMax() == summary.getMin()) { // everybody has the same amount of cards
|
||||||
transition(new FinishingTrick(getGame(), data));
|
transition(game, new FinishingTrick(data));
|
||||||
} else {
|
} else {
|
||||||
transition(new PlayingCard(getGame(), data.withNextPlayer()));
|
transition(game, new PlayingCard(data.with(CURRENT_PLAYER, getNextPlayer())));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean assertions() {
|
private static void checkData(GameData data) {
|
||||||
int expectedSize = getRound() - getTrick();
|
int expectedSize = data.get(ROUND) - data.get(TRICK);
|
||||||
for (List<Card> hand : getHands().values()) {
|
for (List<Card> hand : data.get(HANDS).values()) {
|
||||||
if (hand.size() != expectedSize && hand.size() != expectedSize + 1) {
|
if (hand.size() != expectedSize && hand.size() != expectedSize + 1) {
|
||||||
throw new AssertionError("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + " or " + (expectedSize + 1) + ").");
|
throw new InvalidDataException("Unexpected hand size: " + hand.size() + " (expected " + expectedSize + " or " + (expectedSize + 1) + ").");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int size = getHands().get(getCurrentPlayer()).size();
|
int size = data.get(HANDS).get(data.get(CURRENT_PLAYER)).size();
|
||||||
if (size != expectedSize + 1) {
|
if (size != expectedSize + 1) {
|
||||||
throw new AssertionError("Unexpected current player's hand size: " + size + " (expected " + (expectedSize + 1) + ")");
|
throw new InvalidDataException("Unexpected current player's hand size: " + size + " (expected " + (expectedSize + 1) + ")");
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.machine.Game;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.machine.GameData;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public final class StartingTrick extends TrickState {
|
public final class StartingTrick extends TrickState {
|
||||||
public StartingTrick(Game game, GameData data) {
|
public StartingTrick(GameData data) {
|
||||||
super(game, data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnter() {
|
public void onEnter(Game game) {
|
||||||
transition(new PlayingCard(
|
transition(game, new PlayingCard(getData().with(STACK, List.of())));
|
||||||
getGame(),
|
|
||||||
getData().withStack(Collections.emptyList())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
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.GameData;
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.round.RoundState;
|
import eu.jonahbauer.wizard.core.machine.states.round.RoundState;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
|
|
||||||
public abstract class TrickState extends RoundState {
|
public abstract class TrickState extends RoundState {
|
||||||
public TrickState(Game game, GameData data) {
|
public TrickState(GameData data) {
|
||||||
super(
|
super(
|
||||||
game,
|
data.requireEach(PLAYERS, HANDS)
|
||||||
data.requireAllHands()
|
.requireEach(PLAYERS, PREDICTIONS)
|
||||||
.requireAllPredictions()
|
.require(TRUMP_SUIT, TRICK, TRICKS, CURRENT_PLAYER)
|
||||||
.requireTrumpSuit()
|
|
||||||
.requireTrick()
|
|
||||||
.requireTricks()
|
|
||||||
.requireCurrentPlayer()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Card.Suit getTrickSuit() {
|
protected Card.Suit getTrickSuit() {
|
||||||
for (var pair : getStack()) {
|
for (var pair : get(STACK)) {
|
||||||
Card.Suit suit = pair.second().getSuit();
|
Card.Suit suit = pair.second().getSuit();
|
||||||
if (suit == Card.Suit.WIZARD) {
|
if (suit == Card.Suit.WIZARD) {
|
||||||
return Card.Suit.NONE;
|
return Card.Suit.NONE;
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
package eu.jonahbauer.wizard.core.util;
|
package eu.jonahbauer.wizard.core.util;
|
||||||
|
|
||||||
public record Pair<F,S>(F first, S second) {
|
import java.util.Map;
|
||||||
|
|
||||||
|
public record Pair<F,S>(F first, S second) implements Map.Entry<F,S> {
|
||||||
public static <F,S> Pair<F,S> of(F first, S second) {
|
public static <F,S> Pair<F,S> of(F first, S second) {
|
||||||
return new Pair<>(first, second);
|
return new Pair<>(first, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public F getKey() {
|
||||||
|
return first();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public S getValue() {
|
||||||
|
return second();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public S setValue(S value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ 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.states.GameData.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@ -37,9 +38,9 @@ public class FinishingRoundTest {
|
|||||||
|
|
||||||
FinishingRound state = mock(FinishingRound.class);
|
FinishingRound state = mock(FinishingRound.class);
|
||||||
when(state.getPoints()).thenCallRealMethod();
|
when(state.getPoints()).thenCallRealMethod();
|
||||||
when(state.getPredictions()).thenReturn(predictions);
|
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||||
when(state.getTricks()).thenReturn(tricks);
|
when(state.get(TRICKS)).thenReturn(tricks);
|
||||||
when(state.getPlayers()).thenReturn(players);
|
when(state.get(PLAYERS)).thenReturn(players);
|
||||||
|
|
||||||
assertEquals(Map.of(
|
assertEquals(Map.of(
|
||||||
player0, 20,
|
player0, 20,
|
||||||
|
@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@ -22,18 +23,17 @@ public class PredictingTest {
|
|||||||
when(game.getConfig()).thenReturn(config);
|
when(game.getConfig()).thenReturn(config);
|
||||||
|
|
||||||
Predicting state = mock(Predicting.class);
|
Predicting state = mock(Predicting.class);
|
||||||
doCallRealMethod().when(state).checkPrediction(anyInt());
|
doCallRealMethod().when(state).checkPrediction(any(), anyInt());
|
||||||
when(state.getGame()).thenReturn(game);
|
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||||
when(state.getPredictions()).thenReturn(predictions);
|
|
||||||
when(state.getDealer()).thenReturn(new UUID(0,0));
|
when(state.getDealer()).thenReturn(new UUID(0,0));
|
||||||
when(state.getCurrentPlayer()).thenReturn(new UUID(0,1));
|
when(state.get(CURRENT_PLAYER)).thenReturn(new UUID(0,1));
|
||||||
when(state.getRound()).thenReturn(10);
|
when(state.get(ROUND)).thenReturn(10);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(-1));
|
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, -1));
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(12));
|
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 12));
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
state.checkPrediction(i);
|
state.checkPrediction(game, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,18 +49,17 @@ public class PredictingTest {
|
|||||||
when(game.getConfig()).thenReturn(config);
|
when(game.getConfig()).thenReturn(config);
|
||||||
|
|
||||||
Predicting state = mock(Predicting.class);
|
Predicting state = mock(Predicting.class);
|
||||||
doCallRealMethod().when(state).checkPrediction(anyInt());
|
doCallRealMethod().when(state).checkPrediction(any(), anyInt());
|
||||||
when(state.getGame()).thenReturn(game);
|
when(state.get(PREDICTIONS)).thenReturn(predictions);
|
||||||
when(state.getPredictions()).thenReturn(predictions);
|
|
||||||
when(state.getDealer()).thenReturn(player);
|
when(state.getDealer()).thenReturn(player);
|
||||||
when(state.getCurrentPlayer()).thenReturn(player);
|
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||||
when(state.getRound()).thenReturn(10);
|
when(state.get(ROUND)).thenReturn(10);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(1));
|
assertThrows(IllegalArgumentException.class, () -> state.checkPrediction(game, 1));
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
if (i == 1) continue;
|
if (i == 1) continue;
|
||||||
state.checkPrediction(i);
|
state.checkPrediction(game, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.trick.FinishingTrick;
|
import eu.jonahbauer.wizard.core.machine.states.GameData;
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
import eu.jonahbauer.wizard.core.util.Pair;
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -8,11 +8,23 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class FinishingTrickTest {
|
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
|
@Test
|
||||||
public void getWinner_ReturnsFirstWizard() {
|
public void getWinner_ReturnsFirstWizard() {
|
||||||
List<Pair<UUID, Card>> stack = List.of(
|
List<Pair<UUID, Card>> stack = List.of(
|
||||||
@ -23,13 +35,7 @@ public class FinishingTrickTest {
|
|||||||
Pair.of(null, Card.GREEN_11)
|
Pair.of(null, Card.GREEN_11)
|
||||||
);
|
);
|
||||||
|
|
||||||
FinishingTrick state = mock(FinishingTrick.class);
|
performTest(stack, Card.Suit.YELLOW, Card.Suit.RED);
|
||||||
when(state.getWinner()).thenCallRealMethod();
|
|
||||||
when(state.getStack()).thenReturn(stack);
|
|
||||||
when(state.getTrickSuit()).thenReturn(Card.Suit.RED);
|
|
||||||
when(state.getTrumpSuit()).thenReturn(Card.Suit.YELLOW);
|
|
||||||
|
|
||||||
assertNotNull(state.getWinner());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -42,13 +48,7 @@ public class FinishingTrickTest {
|
|||||||
Pair.of(null, Card.GREEN_11)
|
Pair.of(null, Card.GREEN_11)
|
||||||
);
|
);
|
||||||
|
|
||||||
FinishingTrick state = mock(FinishingTrick.class);
|
performTest(stack, Card.Suit.YELLOW, Card.Suit.RED);
|
||||||
when(state.getWinner()).thenCallRealMethod();
|
|
||||||
when(state.getStack()).thenReturn(stack);
|
|
||||||
when(state.getTrickSuit()).thenReturn(Card.Suit.RED);
|
|
||||||
when(state.getTrumpSuit()).thenReturn(Card.Suit.YELLOW);
|
|
||||||
|
|
||||||
assertNotNull(state.getWinner());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -61,13 +61,7 @@ public class FinishingTrickTest {
|
|||||||
Pair.of(null, Card.GREEN_11)
|
Pair.of(null, Card.GREEN_11)
|
||||||
);
|
);
|
||||||
|
|
||||||
FinishingTrick state = mock(FinishingTrick.class);
|
performTest(stack, Card.Suit.BLUE, Card.Suit.RED);
|
||||||
when(state.getWinner()).thenCallRealMethod();
|
|
||||||
when(state.getStack()).thenReturn(stack);
|
|
||||||
when(state.getTrickSuit()).thenReturn(Card.Suit.RED);
|
|
||||||
when(state.getTrumpSuit()).thenReturn(Card.Suit.BLUE);
|
|
||||||
|
|
||||||
assertNotNull(state.getWinner());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -79,12 +73,6 @@ public class FinishingTrickTest {
|
|||||||
Pair.of(null, Card.YELLOW_JESTER)
|
Pair.of(null, Card.YELLOW_JESTER)
|
||||||
);
|
);
|
||||||
|
|
||||||
FinishingTrick state = mock(FinishingTrick.class);
|
performTest(stack, Card.Suit.NONE, Card.Suit.NONE);
|
||||||
when(state.getWinner()).thenCallRealMethod();
|
|
||||||
when(state.getStack()).thenReturn(stack);
|
|
||||||
when(state.getTrickSuit()).thenReturn(Card.Suit.NONE);
|
|
||||||
when(state.getTrumpSuit()).thenReturn(Card.Suit.NONE);
|
|
||||||
|
|
||||||
assertNotNull(state.getWinner());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.trick.PlayingCard;
|
import eu.jonahbauer.wizard.core.machine.Game;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PickTrumpMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PlayCardMessage;
|
||||||
import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
import eu.jonahbauer.wizard.core.messages.player.PredictMessage;
|
||||||
@ -11,6 +11,7 @@ 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.states.GameData.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@ -22,9 +23,9 @@ public class PlayingCardTest {
|
|||||||
|
|
||||||
PlayingCard state = mock(PlayingCard.class);
|
PlayingCard state = mock(PlayingCard.class);
|
||||||
doCallRealMethod().when(state).checkCard(any());
|
doCallRealMethod().when(state).checkCard(any());
|
||||||
when(state.getHands()).thenReturn(Map.of(player, hand));
|
when(state.get(HANDS)).thenReturn(Map.of(player, hand));
|
||||||
when(state.getStack()).thenReturn(List.of());
|
when(state.get(STACK)).thenReturn(List.of());
|
||||||
when(state.getCurrentPlayer()).thenReturn(player);
|
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||||
|
|
||||||
for (Card value : Card.values()) {
|
for (Card value : Card.values()) {
|
||||||
if (hand.contains(value)) continue;
|
if (hand.contains(value)) continue;
|
||||||
@ -42,8 +43,8 @@ public class PlayingCardTest {
|
|||||||
PlayingCard state = mock(PlayingCard.class);
|
PlayingCard state = mock(PlayingCard.class);
|
||||||
doCallRealMethod().when(state).checkCard(any());
|
doCallRealMethod().when(state).checkCard(any());
|
||||||
when(state.getTrickSuit()).thenReturn(Card.Suit.YELLOW);
|
when(state.getTrickSuit()).thenReturn(Card.Suit.YELLOW);
|
||||||
when(state.getHands()).thenReturn(Map.of(player, hand));
|
when(state.get(HANDS)).thenReturn(Map.of(player, hand));
|
||||||
when(state.getCurrentPlayer()).thenReturn(player);
|
when(state.get(CURRENT_PLAYER)).thenReturn(player);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.BLUE_1));
|
assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.BLUE_1));
|
||||||
assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.RED_1));
|
assertThrows(IllegalArgumentException.class, () -> state.checkCard(Card.RED_1));
|
||||||
@ -58,24 +59,28 @@ public class PlayingCardTest {
|
|||||||
UUID player = new UUID(0, 0);
|
UUID player = new UUID(0, 0);
|
||||||
UUID player2 = new UUID(0, 1);
|
UUID player2 = new UUID(0, 1);
|
||||||
|
|
||||||
PlayingCard state = mock(PlayingCard.class);
|
Game game = mock(Game.class);
|
||||||
doCallRealMethod().when(state).onMessage(any(), any());
|
|
||||||
when(state.getCurrentPlayer()).thenReturn(player);
|
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PlayCardMessage(Card.BLUE_WIZARD)));
|
PlayingCard state = mock(PlayingCard.class);
|
||||||
assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PredictMessage(1)));
|
doCallRealMethod().when(state).onMessage(any(), any(), any());
|
||||||
assertThrows(IllegalStateException.class, () -> state.onMessage(player2, new PickTrumpMessage(Card.Suit.BLUE)));
|
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
|
@Test
|
||||||
public void onMessage_ThrowsIllegalState_IfNotPlayCard() {
|
public void onMessage_ThrowsIllegalState_IfNotPlayCard() {
|
||||||
UUID player = new UUID(0, 0);
|
UUID player = new UUID(0, 0);
|
||||||
|
|
||||||
PlayingCard state = mock(PlayingCard.class);
|
Game game = mock(Game.class);
|
||||||
doCallRealMethod().when(state).onMessage(any(), any());
|
|
||||||
when(state.getCurrentPlayer()).thenReturn(player);
|
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> state.onMessage(player, new PredictMessage(1)));
|
PlayingCard state = mock(PlayingCard.class);
|
||||||
assertThrows(IllegalStateException.class, () -> state.onMessage(player, new PickTrumpMessage(Card.Suit.BLUE)));
|
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,6 +1,5 @@
|
|||||||
package eu.jonahbauer.wizard.core.machine.states.trick;
|
package eu.jonahbauer.wizard.core.machine.states.trick;
|
||||||
|
|
||||||
import eu.jonahbauer.wizard.core.machine.states.trick.TrickState;
|
|
||||||
import eu.jonahbauer.wizard.core.model.Card;
|
import eu.jonahbauer.wizard.core.model.Card;
|
||||||
import eu.jonahbauer.wizard.core.util.Pair;
|
import eu.jonahbauer.wizard.core.util.Pair;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -8,6 +7,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@ -19,7 +19,7 @@ public class TrickStateTest {
|
|||||||
|
|
||||||
TrickState trickState = mock(TrickState.class);
|
TrickState trickState = mock(TrickState.class);
|
||||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||||
when(trickState.getStack()).thenReturn(stack);
|
when(trickState.get(STACK)).thenReturn(stack);
|
||||||
|
|
||||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ public class TrickStateTest {
|
|||||||
|
|
||||||
TrickState trickState = mock(TrickState.class);
|
TrickState trickState = mock(TrickState.class);
|
||||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||||
when(trickState.getStack()).thenReturn(stack);
|
when(trickState.get(STACK)).thenReturn(stack);
|
||||||
|
|
||||||
assertEquals(trickState.getTrickSuit(), Card.Suit.RED);
|
assertEquals(trickState.getTrickSuit(), Card.Suit.RED);
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ public class TrickStateTest {
|
|||||||
|
|
||||||
TrickState trickState = mock(TrickState.class);
|
TrickState trickState = mock(TrickState.class);
|
||||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||||
when(trickState.getStack()).thenReturn(stack);
|
when(trickState.get(STACK)).thenReturn(stack);
|
||||||
|
|
||||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ public class TrickStateTest {
|
|||||||
|
|
||||||
TrickState trickState = mock(TrickState.class);
|
TrickState trickState = mock(TrickState.class);
|
||||||
when(trickState.getTrickSuit()).thenCallRealMethod();
|
when(trickState.getTrickSuit()).thenCallRealMethod();
|
||||||
when(trickState.getStack()).thenReturn(stack);
|
when(trickState.get(STACK)).thenReturn(stack);
|
||||||
|
|
||||||
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
assertEquals(trickState.getTrickSuit(), Card.Suit.NONE);
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ public class TrickStateTest {
|
|||||||
);
|
);
|
||||||
TrickState trickState2 = mock(TrickState.class);
|
TrickState trickState2 = mock(TrickState.class);
|
||||||
when(trickState2.getTrickSuit()).thenCallRealMethod();
|
when(trickState2.getTrickSuit()).thenCallRealMethod();
|
||||||
when(trickState2.getStack()).thenReturn(stack2);
|
when(trickState2.get(STACK)).thenReturn(stack2);
|
||||||
|
|
||||||
assertEquals(trickState2.getTrickSuit(), Card.Suit.YELLOW);
|
assertEquals(trickState2.getTrickSuit(), Card.Suit.YELLOW);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user