Compare commits

..

No commits in common. "854e2a914bb6cad80c09a59b7a3609df28d151c7" and "82db2bdab133c8f098ee899ffbb7ec478f0f2ef8" have entirely different histories.

26 changed files with 126 additions and 122 deletions

0
graphics/.gitkeep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

0
graphics/menu/.gitkeep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
sfx/card_played.mp3 Normal file

Binary file not shown.

BIN
sfx/card_played_special.mp3 Normal file

Binary file not shown.

BIN
sfx/card_shuffle.mp3 Normal file

Binary file not shown.

BIN
sfx/game_end.mp3 Normal file

Binary file not shown.

BIN
sfx/game_lost.mp3 Normal file

Binary file not shown.

BIN
sfx/game_start.mp3 Normal file

Binary file not shown.

BIN
sfx/game_win.mp3 Normal file

Binary file not shown.

View File

@ -16,7 +16,7 @@ import eu.jonahbauer.wizard.common.model.Card;
public class InstructionScreen extends MenuScreen {
private static final int MAX_PAGE = 3;
private TextButton buttonBack;
private TextButton buttonOK;
private VerticalGroup content;
private ScrollPane scrollPane;
private TextButton nextPageButton;
@ -29,7 +29,7 @@ public class InstructionScreen extends MenuScreen {
private final ChangeListener listener = new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
if (actor == buttonOK) {
game.getClient().execute(Menu.class, Menu::showMenuScreen);
sfxClick();
} else if (actor == nextPageButton) {
@ -70,9 +70,9 @@ public class InstructionScreen extends MenuScreen {
previousPageButton.addListener(listener);
getButtonGroup().addActor(previousPageButton);
buttonBack = new TextButton(messages.get("menu.instruction.back"), skin);
buttonBack.addListener(listener);
getButtonGroup().addActor(buttonBack);
buttonOK = new TextButton(messages.get("menu.instruction.back"), skin);
buttonOK.addListener(listener);
getButtonGroup().addActor(buttonOK);
nextPageButton = new TextButton(messages.get("menu.instruction.nextPageButton"), skin);
nextPageButton.addListener(listener);
@ -83,7 +83,7 @@ public class InstructionScreen extends MenuScreen {
scrollPane.setSize(0.65f * WizardGame.WIDTH, 400 + 0.1f * WizardGame.HEIGHT + 80);
stage.addActor(scrollPane);
stage.addCaptureListener(new KeyboardFocusManager(scrollPane, previousPageButton, buttonBack, nextPageButton));
stage.addCaptureListener(new KeyboardFocusManager(scrollPane, previousPageButton, buttonOK, nextPageButton));
showFirstPage();
}
@ -166,8 +166,9 @@ public class InstructionScreen extends MenuScreen {
reset();
startSection("menu.instruction.variant.title");
addParagraph("menu.instruction.variant.intro");
Label variantsIntro = new Label(messages.get("menu.instruction.variant.intro"), skin);
content.addActor(variantsIntro);
Table table = new Table(skin).padTop(10);
table.defaults().space(10.0f).left().top();
table.columnDefaults(1).grow();

View File

@ -70,6 +70,6 @@ public class CardUtil {
);
public Card.Suit getDefaultTrumpSuit(Card card) {
return card == null ? Card.Suit.NONE : DEFAULT_SUITES.get(card);
return DEFAULT_SUITES.get(card);
}
}

View File

@ -0,0 +1,26 @@
package eu.jonahbauer.wizard.common.messages.server;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.UUID;
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class KickVotedMessage extends ServerMessage {
/**
* UUID of the voting player
*/
private final @NonNull UUID voter;
/**
* UUID of player who is supposed to be kicked
*/
private final @NonNull UUID player;
/**
* Time until the vote ends in {@link System#currentTimeMillis() UNIX time}
*/
private final long timeout;
}

View File

@ -0,0 +1,18 @@
package eu.jonahbauer.wizard.common.messages.server;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.UUID;
@Getter
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public final class KickedMessage extends ServerMessage {
/**
* UUID of player who was kicked
*/
private final @NonNull UUID player;
}

View File

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
@EqualsAndHashCode
public abstract sealed class ServerMessage permits AckMessage, GameMessage, NackMessage, PlayerLeftMessage, PlayerModifiedMessage, SessionJoinedMessage, SessionListMessage, SessionModifiedMessage, SessionRemovedMessage, StartingGameMessage {
public abstract sealed class ServerMessage permits AckMessage, GameMessage, KickVotedMessage, KickedMessage, NackMessage, PlayerLeftMessage, PlayerModifiedMessage, SessionJoinedMessage, SessionListMessage, SessionModifiedMessage, SessionRemovedMessage, StartingGameMessage {
private static final ObjectMapper MAPPER = SerializationUtil.newObjectMapper(ServerMessage.class, ObserverMessage.class);
public static ServerMessage parse(String json) throws ParseException {

View File

@ -1,17 +1,26 @@
package eu.jonahbauer.wizard.core.machine.states.round;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.model.card.GameCards;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PICK_TRUMP;
public final class DeterminingTrump extends RoundState {
private transient UUID player;
private transient boolean werewolf;
public DeterminingTrump(GameData data) {
super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS));
@ -26,31 +35,74 @@ public final class DeterminingTrump extends RoundState {
var player = entry.getKey();
var hand = entry.getValue();
if (hand.contains(Card.WEREWOLF)) {
this.player = player;
this.werewolf = true;
game.notify(new TrumpMessage(trumpCard, null));
game.notify(new TrumpMessage(Card.WEREWOLF, null));
var data = getData().with(CURRENT_PLAYER, player);
return sync(data, DeterminingTrumpUserInput::new);
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
return timeout(game);
}
}
// default trump handling
Card.Suit trumpSuit = trumpCard != null ? GameCards.get(trumpCard).getTrumpSuit() : Card.Suit.NONE;
if (trumpSuit == null) {
var player = getDealer();
this.player = getDealer();
game.notify(new TrumpMessage(trumpCard, null));
var data = getData().with(CURRENT_PLAYER, player);
return sync(data, DeterminingTrumpUserInput::new);
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
return timeout(game);
} else {
var data = getData().with(
return transition(game, trumpSuit);
}
}
@Override
public Optional<GameState> onTimeout(Game game) {
game.notify(new TimeoutMessage());
Card.Suit[] suits;
if (werewolf) {
suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW, Card.Suit.NONE};
} else {
suits = new Card.Suit[]{Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
}
return transition(game, suits[game.getRandom().nextInt(suits.length)]);
}
@Override
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
if (this.player.equals(player) && message instanceof PickTrumpMessage trumpMessage) {
checkTrumpSuit(trumpMessage.getTrumpSuit());
return transition(game, trumpMessage.getTrumpSuit());
} else {
return super.onMessage(game, player, message);
}
}
private void checkTrumpSuit(Card.Suit suit) {
if (!werewolf && suit == Card.Suit.NONE) {
throw new IllegalArgumentException("Trump suit must not be " + Card.Suit.NONE + ".");
} else if (suit == null) {
throw new IllegalArgumentException("Trump suit must not be null.");
}
}
private Optional<GameState> transition(Game game, @NotNull Card.Suit trumpSuit) {
GameData data = getData().with(
TRUMP_SUIT, trumpSuit,
CURRENT_PLAYER, getNextPlayer(getDealer())
);
if (werewolf) {
var mutableHands = new HashMap<>(get(HANDS));
var mutableHand = new ArrayList<>(mutableHands.get(player));
mutableHand.set(mutableHand.indexOf(Card.WEREWOLF), get(TRUMP_CARD));
mutableHands.put(player, List.copyOf(mutableHand));
data = data.with(HANDS, Map.copyOf(mutableHands));
game.notify(new TrumpMessage(Card.WEREWOLF, trumpSuit));
game.notify(player, new HandMessage(player, mutableHands.get(player)));
} else {
game.notify(new TrumpMessage(get(TRUMP_CARD), trumpSuit));
}
return sync(data, Predicting::new);
}
}
}

View File

@ -1,89 +0,0 @@
package eu.jonahbauer.wizard.core.machine.states.round;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.TransientState;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PICK_TRUMP;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TRUMP_CARD;
public final class DeterminingTrumpUserInput extends RoundState implements TransientState {
private final transient UUID player;
private final transient boolean werewolf;
public DeterminingTrumpUserInput(GameData data) {
super(data.require(TRUMP_CARD).requireEach(PLAYERS, HANDS).require(CURRENT_PLAYER));
this.player = get(CURRENT_PLAYER);
this.werewolf = get(HANDS).get(this.player).contains(Card.WEREWOLF);
}
@Override
public Optional<GameState> onEnter(Game game) {
game.notify(new UserInputMessage(this.player, PICK_TRUMP, getTimeout(game, true)));
return timeout(game);
}
@Override
public Optional<GameState> onTimeout(Game game) {
game.notify(new TimeoutMessage());
Card.Suit[] suits;
if (werewolf) {
suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW, Card.Suit.NONE};
} else {
suits = new Card.Suit[] {Card.Suit.BLUE, Card.Suit.GREEN, Card.Suit.RED, Card.Suit.YELLOW};
}
return transition(game, suits[game.getRandom().nextInt(suits.length)]);
}
@Override
public Optional<GameState> onMessage(Game game, UUID player, PlayerMessage message) {
if (this.player.equals(player) && message instanceof PickTrumpMessage trumpMessage) {
checkTrumpSuit(trumpMessage.getTrumpSuit());
return transition(game, trumpMessage.getTrumpSuit());
} else {
return super.onMessage(game, player, message);
}
}
private void checkTrumpSuit(Card.Suit suit) {
if (!werewolf && suit == Card.Suit.NONE) {
throw new IllegalArgumentException("Trump suit must not be " + Card.Suit.NONE + ".");
} else if (suit == null) {
throw new IllegalArgumentException("Trump suit must not be null.");
}
}
private Optional<GameState> transition(Game game, @NotNull Card.Suit trumpSuit) {
GameData data = getData().with(
TRUMP_SUIT, trumpSuit,
CURRENT_PLAYER, getNextPlayer(getDealer())
);
if (werewolf) {
var mutableHands = new HashMap<>(get(HANDS));
var mutableHand = new ArrayList<>(mutableHands.get(player));
mutableHand.set(mutableHand.indexOf(Card.WEREWOLF), get(TRUMP_CARD));
mutableHands.put(player, List.copyOf(mutableHand));
data = data.with(HANDS, Map.copyOf(mutableHands));
game.notify(new TrumpMessage(Card.WEREWOLF, trumpSuit));
game.notify(player, new HandMessage(player, mutableHands.get(player)));
} else {
game.notify(new TrumpMessage(get(TRUMP_CARD), trumpSuit));
}
return sync(data, Predicting::new);
}
}

View File

@ -38,7 +38,7 @@ public class DeterminingTrumpTest {
@SuppressWarnings("SameParameterValue")
private Game performTest(GameConfiguration configuration, int round, Map<UUID, List<Card>> hands, Card trumpCard, MessageQueue queue) {
Game game = spy(new Game(configuration, queue));
doFinish().when(game).transition(argThat(argument -> argument instanceof SyncState && argument.getData().has(TRUMP_SUIT)));
doFinish().when(game).transition(any(SyncState.class));
queue.setGame(game);
var playerList = List.of(players);
@ -92,8 +92,7 @@ public class DeterminingTrumpTest {
);
// play cards in given order
MessageQueue queue = new MessageQueue()
.sync(players);
MessageQueue queue = new MessageQueue();
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_JESTER, queue);
@ -118,7 +117,6 @@ public class DeterminingTrumpTest {
// play cards in given order
MessageQueue queue = new MessageQueue()
.sync(players)
.addPickTrump(players[0], Card.Suit.GREEN);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.BLUE_WIZARD, queue);
@ -146,7 +144,6 @@ public class DeterminingTrumpTest {
// play cards in given order
MessageQueue queue = new MessageQueue()
.sync(players)
.addPickTrump(players[3], Card.Suit.YELLOW);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_1, queue);

View File

@ -148,11 +148,10 @@ public class RoundTest {
@Test
public void run_Anniversary() throws ExecutionException, InterruptedException {
MessageQueue queue = new MessageQueue()
.sync(players) // starting_round
.sync(players) // dealing
.sync(players) // determining_trump
.sync(players)
.sync(players)
.addPickTrump(players[2], Card.Suit.YELLOW)
.sync(players) // determining_trump
.sync(players)
.addPrediction(players[3], 2)
.addPrediction(players[0], 2)
.addPrediction(players[1], 2)