Compare commits

..

5 Commits
main ... dev

@ -2,8 +2,8 @@ package eu.jonahbauer.wizard.client.cli.commands;
import eu.jonahbauer.wizard.client.cli.Client;
import eu.jonahbauer.wizard.client.cli.state.Game;
import eu.jonahbauer.wizard.client.cli.util.Pair;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.common.util.Pair;
import java.util.List;
import java.util.Map;

@ -2,7 +2,6 @@ package eu.jonahbauer.wizard.client.cli.state;
import eu.jonahbauer.wizard.client.cli.Client;
import eu.jonahbauer.wizard.client.cli.commands.GameCommand;
import eu.jonahbauer.wizard.client.cli.util.Pair;
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.observer.*;
@ -12,6 +11,7 @@ import eu.jonahbauer.wizard.common.messages.server.GameMessage;
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.Getter;
import java.time.Instant;

@ -1,24 +0,0 @@
package eu.jonahbauer.wizard.client.cli.util;
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) {
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();
}
}

@ -1,6 +1,5 @@
package eu.jonahbauer.wizard.client.cli.util;
import eu.jonahbauer.wizard.client.cli.Client;
import eu.jonahbauer.wizard.client.cli.state.ClientState;
import picocli.CommandLine;

@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import eu.jonahbauer.wizard.client.libgdx.screens.MainMenuScreen;
import eu.jonahbauer.wizard.client.libgdx.util.SavedData;
import eu.jonahbauer.wizard.client.libgdx.util.SoundManager;
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
import lombok.Getter;
@ -19,6 +20,8 @@ public class WizardGame extends Game {
public SpriteBatch batch;
public WizardAssetManager assets;
public SoundManager sounds;
public final SavedData storage = new SavedData();
private boolean fullscreenToggle;
@ -35,6 +38,8 @@ public class WizardGame extends Game {
assets.loadShared();
assets.finishLoading();
sounds = new SoundManager(assets);
// background music
Music backgroundMusic = assets.get(WizardAssetManager.MUSIC_BACKGROUND, Music.class);
backgroundMusic.setLooping(true);
@ -77,6 +82,7 @@ public class WizardGame extends Game {
@Override
public void dispose () {
batch.dispose();
sounds.dispose();
assets.dispose();
client.shutdownNow();
var socket = client.getSocket();

@ -0,0 +1,53 @@
package eu.jonahbauer.wizard.client.libgdx.actors;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.scenes.scene2d.ui.List;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Align;
import lombok.Getter;
import lombok.Setter;
public abstract class IconList<T> extends List<T> {
@Getter
@Setter
private float iconWidth = -1;
@Getter
@Setter
private float iconPadding = 8;
@SuppressWarnings("unused")
public IconList(Skin skin) {
super(skin);
}
@SuppressWarnings("unused")
public IconList(Skin skin, String styleName) {
super(skin, styleName);
}
@SuppressWarnings("unused")
public IconList(ListStyle style) {
super(style);
}
public abstract Drawable getIcon(T item);
@Override
@Deprecated
public void setAlignment(int alignment) {}
@Override
protected GlyphLayout drawItem(Batch batch, BitmapFont font, int index, T item, float x, float y, float width) {
var text = toString(item);
var icon = getIcon(item);
var height = font.getCapHeight();
var iconWidth = this.iconWidth < 0 ? height : this.iconWidth;
icon.draw(batch, x, y - height, iconWidth, height);
return font.draw(batch, text, x + iconWidth + iconPadding, y, 0, text.length(), width - iconWidth - iconPadding, Align.left, false, "...");
}
}

@ -0,0 +1,21 @@
package eu.jonahbauer.wizard.client.libgdx.listeners;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import eu.jonahbauer.wizard.client.libgdx.util.SoundManager;
public class ButtonClickListener extends ChangeListener {
private final SoundManager sounds;
public ButtonClickListener(SoundManager sounds) {
this.sounds = sounds;
}
@Override
public void changed(ChangeEvent event, Actor actor) {
if (actor instanceof Button) {
sounds.sfxClick();
}
}
}

@ -29,7 +29,6 @@ public class ConnectScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(Menu.class, Menu::showMenuScreen);
sfxClick();
} else if (actor == buttonConnect) {
try {
var uriString = ConnectScreen.this.uriField.getText();
@ -39,8 +38,6 @@ public class ConnectScreen extends MenuScreen {
} catch (URISyntaxException e) {
uriField.setStyle(getTextFieldErrorStyle());
}
sfxClick();
}
}
};

@ -31,10 +31,8 @@ public class CreateGameScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(Lobby.class, Lobby::showListScreen);
sfxClick();
} else if (actor == buttonContinue) {
create();
sfxClick();
}
}
};

@ -18,7 +18,6 @@ public class ErrorScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(BaseState.class, BaseState::dismissErrorScreen);
sfxClick();
}
}
};

@ -22,10 +22,7 @@ import eu.jonahbauer.wizard.client.libgdx.actors.CardsGroup;
import eu.jonahbauer.wizard.client.libgdx.actors.PadOfTruth;
import eu.jonahbauer.wizard.client.libgdx.state.Game;
import eu.jonahbauer.wizard.client.libgdx.state.Session;
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
import eu.jonahbauer.wizard.client.libgdx.util.CardUtil;
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
import eu.jonahbauer.wizard.client.libgdx.util.*;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.model.Card;
import lombok.Getter;
@ -421,6 +418,13 @@ public class GameScreen extends WizardScreen {
execute(new StartRoundOverlay(this, round));
}
public void deal() {
execute(parallel(
run(() -> game.sounds.sfxShuffle()),
delay(SoundManager.CARD_SHUFFLE_DURATION)
));
}
public void startTrick() {
clearActivePlayer();
execute(() -> cardStack.clearChildren());
@ -531,6 +535,8 @@ public class GameScreen extends WizardScreen {
cardStack.add(seat, actor);
sequence.addAction(delay(actor));
sequence.addAction(delay(AnimationTimings.STACK_HOLD));
game.sounds.sfxPlayCard();
}));
execute(sequence);
}

@ -31,15 +31,12 @@ public class InstructionScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(Menu.class, Menu::showMenuScreen);
sfxClick();
} else if (actor == nextPageButton) {
currentPage = MathUtils.clamp(currentPage + 1, 0, MAX_PAGE);
showPage(currentPage);
sfxClick();
} else if (actor == previousPageButton) {
currentPage = MathUtils.clamp(currentPage - 1, 0, MAX_PAGE);
showPage(currentPage);
sfxClick();
}
}
};

@ -1,6 +1,5 @@
package eu.jonahbauer.wizard.client.libgdx.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;

@ -3,7 +3,10 @@ package eu.jonahbauer.wizard.client.libgdx.screens;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.actors.IconList;
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
import eu.jonahbauer.wizard.client.libgdx.listeners.ResetErrorListener;
import eu.jonahbauer.wizard.client.libgdx.state.Lobby;
@ -17,7 +20,6 @@ public class LobbyScreen extends MenuScreen {
private TextButton buttonBack;
private TextButton buttonJoin;
private TextButton buttonRejoin;
private TextButton buttonCreate;
private TextField playerName;
@ -26,6 +28,7 @@ public class LobbyScreen extends MenuScreen {
private Label labelSessionConfiguration;
private UUID selectedSession;
private boolean rejoin = false;
private List<SessionData> sessions;
private ScrollPane sessionListContainer;
@ -34,16 +37,14 @@ public class LobbyScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(Lobby.class, Lobby::disconnect);
sfxClick();
} else if (actor == buttonJoin) {
if (rejoin) {
game.getClient().execute(Lobby.class, Lobby::showRejoinScreen);
} else {
join();
sfxClick();
}
} else if (actor == buttonCreate) {
game.getClient().execute(Lobby.class, Lobby::showCreateScreen);
sfxClick();
} else if (actor == buttonRejoin) {
game.getClient().execute(Lobby.class, Lobby::showRejoinScreen);
sfxClick();
}
}
};
@ -64,21 +65,26 @@ public class LobbyScreen extends MenuScreen {
buttonCreate.addListener(listener);
getButtonGroup().addActor(buttonCreate);
buttonRejoin = new TextButton(messages.get("menu.lobby.rejoin"), skin);
buttonRejoin.addListener(listener);
getButtonGroup().addActor(buttonRejoin);
buttonJoin = new TextButton(messages.get("menu.lobby.join"), skin);
buttonJoin.addListener(listener);
getButtonGroup().addActor(buttonJoin);
getButtonGroup().setWidth(0.55f * WizardGame.WIDTH);
sessions = new List<>(skin) {
sessions = new IconList<>(skin) {
// TODO better icons
private final Drawable running = skin.getDrawable(UiskinAtlas.NOT_READY);
private final Drawable notRunning = skin.getDrawable(UiskinAtlas.READY);
@Override
public String toString(SessionData session) {
return session.getName();
}
@Override
public Drawable getIcon(SessionData item) {
return item.isRunning() ? running : notRunning;
}
};
sessions.addListener(new ChangeListener() {
@Override
@ -102,12 +108,11 @@ public class LobbyScreen extends MenuScreen {
stage.addActor(content);
stage.addCaptureListener(new KeyboardFocusManager(
sessions, playerName, buttonBack, buttonCreate, buttonRejoin, buttonJoin
sessions, playerName, buttonBack, buttonCreate, buttonJoin
));
buttonBack.setName("button_back");
buttonJoin.setName("button_join");
buttonJoin.setName("button_rejoin");
buttonCreate.setName("button_create");
sessions.setName("session_list");
playerName.setName("player_name");
@ -194,14 +199,21 @@ public class LobbyScreen extends MenuScreen {
labelSessionPlayerCount.setText(Integer.toString(data.getPlayerCount()));
labelSessionConfiguration.setText(data.getConfiguration().toString());
selectedSession = data.getUuid();
updateRejoin(data.isRunning());
} else {
labelSessionName.setText("");
labelSessionPlayerCount.setText("");
labelSessionConfiguration.setText("");
selectedSession = null;
updateRejoin(false);
}
}
private void updateRejoin(boolean rejoin) {
this.rejoin = rejoin;
buttonJoin.setText(messages.get("menu.lobby." + (rejoin ? "rejoin" : "join")));
}
private void join() {
boolean error = false;

@ -23,13 +23,10 @@ public class MainMenuScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonPlay) {
game.getClient().execute(Menu.class, Menu::showConnectScreen);
sfxClick();
} else if (actor == buttonQuit) {
sfxClick();
Gdx.app.exit();
} else if (actor == buttonInstruction) {
game.getClient().execute(Menu.class, Menu::showInstructionScreen);
sfxClick();
}
}
};

@ -26,10 +26,8 @@ public class RejoinScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonBack) {
game.getClient().execute(Lobby.class, Lobby::showListScreen);
sfxClick();
} else if (actor == buttonContinue) {
rejoin();
sfxClick();
}
}
};

@ -1,15 +1,12 @@
package eu.jonahbauer.wizard.client.libgdx.screens;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.actors.IconList;
import eu.jonahbauer.wizard.client.libgdx.listeners.KeyboardFocusManager;
import eu.jonahbauer.wizard.client.libgdx.state.Session;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
@ -17,7 +14,7 @@ import eu.jonahbauer.wizard.common.model.Configuration;
import java.util.UUID;
public class WaitingScreen extends MenuScreen {
public class SessionScreen extends MenuScreen {
private TextButton buttonLeave;
private TextButton buttonReady;
@ -34,15 +31,13 @@ public class WaitingScreen extends MenuScreen {
public void changed(ChangeEvent event, Actor actor) {
if (actor == buttonLeave) {
game.getClient().execute(Session.class, Session::leave);
sfxClick();
} else if (actor == buttonReady) {
game.getClient().execute(Session.class, Session::toggleReady);
sfxClick();
}
}
};
public WaitingScreen(WizardGame game) {
public SessionScreen(WizardGame game) {
super(game);
}
@ -50,19 +45,19 @@ public class WaitingScreen extends MenuScreen {
public void show() {
super.show();
buttonLeave = new TextButton(messages.get("menu.waiting.leave"), skin);
buttonLeave = new TextButton(messages.get("menu.session.leave"), skin);
buttonLeave.addListener(listener);
getButtonGroup().addActor(buttonLeave);
buttonReady = new TextButton(messages.get("menu.waiting.ready"), skin);
buttonReady = new TextButton(messages.get("menu.session.ready"), skin);
buttonReady.addListener(listener);
getButtonGroup().addActor(buttonReady);
getButtonGroup().setWidth(0.55f * WizardGame.WIDTH);
players = new List<>(skin) {
private final TextureRegion ready = skin.getRegion(UiskinAtlas.READY);
private final TextureRegion notReady = skin.getRegion(UiskinAtlas.NOT_READY);
players = new IconList<>(skin) {
private final Drawable ready = skin.getDrawable(UiskinAtlas.READY);
private final Drawable notReady = skin.getDrawable(UiskinAtlas.NOT_READY);
@Override
public String toString(PlayerData player) {
@ -70,16 +65,8 @@ public class WaitingScreen extends MenuScreen {
}
@Override
@SuppressWarnings("SuspiciousNameCombination")
protected GlyphLayout drawItem(Batch batch, BitmapFont font, int index, PlayerData item, float x, float y, float width) {
String string = toString(item);
var height = font.getCapHeight();
if (item.isReady()) {
batch.draw(ready, x, y - height, height, height);
} else {
batch.draw(notReady, x, y - height, height, height);
}
return font.draw(batch, string, x + height + 8, y, 0, string.length(), width - height - 8, Align.left, false, "...");
public Drawable getIcon(PlayerData item) {
return item.isReady() ? ready : notReady;
}
};
@ -121,13 +108,13 @@ public class WaitingScreen extends MenuScreen {
infoTable.columnDefaults(0).growX().width(infoTableWidth);
infoTable.setSize(infoTableWidth, 400);
infoTable.add(messages.get("menu.waiting.session_name.label")).row();
infoTable.add(messages.get("menu.session.session_name.label")).row();
infoTable.add(labelSessionName).row();
infoTable.add(messages.get("menu.waiting.session_uuid.label")).row();
infoTable.add(messages.get("menu.session.session_uuid.label")).row();
infoTable.add(labelSessionUUID).row();
infoTable.add(messages.get("menu.waiting.session_configuration.label")).row();
infoTable.add(messages.get("menu.session.session_configuration.label")).row();
infoTable.add(labelSessionConfiguration).row();
infoTable.add(messages.get("menu.waiting.player_name.label")).row();
infoTable.add(messages.get("menu.session.player_name.label")).row();
infoTable.add(labelPlayerName).row();
return infoTable;
@ -138,7 +125,7 @@ public class WaitingScreen extends MenuScreen {
}
public void setReady(boolean ready) {
buttonReady.setText(messages.get(ready ? "menu.waiting.not_ready" : "menu.waiting.ready"));
buttonReady.setText(messages.get(ready ? "menu.session.not_ready" : "menu.session.ready"));
}
public void addPlayer(PlayerData player) {

@ -2,7 +2,6 @@ package eu.jonahbauer.wizard.client.libgdx.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
@ -16,6 +15,7 @@ import com.badlogic.gdx.utils.viewport.Viewport;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.listeners.AutoFocusListener;
import eu.jonahbauer.wizard.client.libgdx.listeners.ButtonClickListener;
import eu.jonahbauer.wizard.client.libgdx.listeners.ButtonKeyListener;
import eu.jonahbauer.wizard.client.libgdx.util.WizardAssetManager;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
@ -31,7 +31,6 @@ public abstract class WizardScreen implements Screen {
protected Viewport viewport;
private Image background;
private Sound sfxClick;
protected float offsetX;
protected float offsetY;
@ -56,11 +55,10 @@ public abstract class WizardScreen implements Screen {
stage = new Stage(viewport);
stage.addListener(new ButtonKeyListener());
stage.addListener(new AutoFocusListener());
stage.addListener(new ButtonClickListener(game.sounds));
stage.setDebugAll(WizardGame.DEBUG);
Gdx.input.setInputProcessor(stage);
sfxClick = assets.get(WizardAssetManager.SFX_CLICK);
}
@Override
@ -110,8 +108,4 @@ public abstract class WizardScreen implements Screen {
public void dispose() {
stage.dispose();
}
protected void sfxClick() {
sfxClick.play(0.6f);
}
}

@ -68,7 +68,7 @@ public final class AwaitingJoinSession extends Awaiting {
));
} else {
return Optional.of(new Session(
new SessionData(session, sessionName, -1, configuration),
session, sessionName, configuration,
players,
player
));

@ -6,7 +6,6 @@ import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import eu.jonahbauer.wizard.common.messages.observer.*;
import eu.jonahbauer.wizard.common.messages.player.*;
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
@ -118,6 +117,7 @@ public final class Game extends BaseState {
case "starting_round" -> {
return onStartRound(client);
}
case "dealing" -> onDealing();
case "starting_trick" -> onStartTrick();
case "juggling" -> onJuggle();
case "finishing_round" -> onFinishingRound();
@ -190,6 +190,10 @@ public final class Game extends BaseState {
return Optional.empty();
}
private void onDealing() {
if (gameScreen != null) gameScreen.deal();
}
private void onStartTrick() {
log.info("Trick {} is starting...", trick + 1);
trick ++;
@ -433,7 +437,7 @@ public final class Game extends BaseState {
private Optional<ClientState> returnToSession() {
return Optional.of(new Session(
new SessionData(session, sessionName, -1, configuration),
session, sessionName, configuration,
players.entrySet().stream()
.map(entry -> new PlayerData(entry.getKey(), entry.getValue(), false))
.toList(),

@ -1,11 +1,10 @@
package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.screens.WaitingScreen;
import eu.jonahbauer.wizard.client.libgdx.screens.SessionScreen;
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
import eu.jonahbauer.wizard.common.messages.client.ReadyMessage;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import eu.jonahbauer.wizard.common.messages.server.*;
import eu.jonahbauer.wizard.common.model.Configuration;
import lombok.Getter;
@ -16,7 +15,7 @@ import java.util.*;
@Log4j2
@Getter
public final class Session extends BaseState {
private WaitingScreen sessionScreen;
private SessionScreen sessionScreen;
private final UUID self;
@ -28,14 +27,14 @@ public final class Session extends BaseState {
private boolean sending;
public Session(SessionData session, Collection<PlayerData> players, UUID self) {
this(session, players, self, false);
public Session(UUID session, String name, Configuration configuration, Collection<PlayerData> players, UUID self) {
this(session, name, configuration, players, self, false);
}
public Session(SessionData session, Collection<PlayerData> players, UUID self, boolean dontSwitchScreen) {
this.session = session.getUuid();
this.sessionName = session.getName();
this.configuration = session.getConfiguration();
public Session(UUID session, String name, Configuration configuration, Collection<PlayerData> players, UUID self, boolean dontSwitchScreen) {
this.session = session;
this.sessionName = name;
this.configuration = configuration;
players.forEach(p -> this.players.put(p.getUuid(), p));
this.dontSwitchScreen = dontSwitchScreen;
@ -122,7 +121,7 @@ public final class Session extends BaseState {
}
public Optional<ClientState> showInfoScreen(Client client) {
sessionScreen = new WaitingScreen(client.getGame());
sessionScreen = new SessionScreen(client.getGame());
client.getGame().setScreen(sessionScreen);
sessionScreen.setPlayers(players.values().toArray(new PlayerData[0]));
sessionScreen.setReady(players.get(self).isReady());

@ -0,0 +1,47 @@
package eu.jonahbauer.wizard.client.libgdx.util;
import com.badlogic.gdx.audio.Sound;
import lombok.Setter;
public class SoundManager {
public static final float CARD_SHUFFLE_DURATION = 4.2f;
private final WizardAssetManager assets;
@Setter
private float sfxVolume = 1;
private final Sound click;
private final Sound cardPlayed;
private final Sound cardShuffle;
public SoundManager(WizardAssetManager assets) {
this.assets = assets;
assets.load(WizardAssetManager.SFX_CLICK, Sound.class);
assets.load(WizardAssetManager.SFX_CARD_PLAYED, Sound.class);
assets.load(WizardAssetManager.SFX_CARD_SHUFFLE, Sound.class);
assets.finishLoading();
this.click = assets.get(WizardAssetManager.SFX_CLICK);
this.cardPlayed = assets.get(WizardAssetManager.SFX_CARD_PLAYED);
this.cardShuffle = assets.get(WizardAssetManager.SFX_CARD_SHUFFLE);
}
public void sfxClick() {
this.click.play(sfxVolume);
}
public void sfxShuffle() {
this.cardShuffle.play(sfxVolume);
}
public void sfxPlayCard() {
this.cardPlayed.play(sfxVolume);
}
public void dispose() {
assets.unload(WizardAssetManager.SFX_CLICK);
assets.unload(WizardAssetManager.SFX_CARD_PLAYED);
assets.unload(WizardAssetManager.SFX_CARD_SHUFFLE);
}
}

@ -4,7 +4,6 @@ import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.I18NBundleLoader;
import com.badlogic.gdx.assets.loaders.SkinLoader;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
@ -20,7 +19,9 @@ public class WizardAssetManager {
public static final String ATLAS_SKIN = UiskinAtlas.$PATH;
public static final String ATLAS_GAME = GameAtlas.$PATH;
public static final String SFX_CLICK = "button_click_s.mp3";
public static final String SFX_CLICK = "sound/button_click.mp3";
public static final String SFX_CARD_PLAYED = "sound/card_played.mp3";
public static final String SFX_CARD_SHUFFLE = "sound/card_shuffle.mp3";
public static final String MUSIC_BACKGROUND = "background.mp3";
public static final String CURSOR = "cursor.png";
@ -35,7 +36,6 @@ public class WizardAssetManager {
manager.load(SKIN, Skin.class, new SkinLoader.SkinParameter(ATLAS_SKIN));
manager.load(MUSIC_BACKGROUND, Music.class);
manager.load(SFX_CLICK, Sound.class);
}
public void loadGame() {

@ -150,14 +150,14 @@ menu.loading.joining_session=Joining session...
menu.loading.joining_lobby=Joining lobby...
menu.loading.back=Return To Main Menu
menu.waiting.ready=Ready
menu.waiting.not_ready=Not Ready
menu.waiting.leave=Leave
menu.waiting.player_name.label=Own Name
menu.waiting.session_name.label=Session Name
menu.waiting.session_uuid.label=Session UUID
menu.waiting.session_configuration.label=Configuration
menu.session.ready=Ready
menu.session.not_ready=Not Ready
menu.session.leave=Leave
menu.session.player_name.label=Own Name
menu.session.session_name.label=Session Name
menu.session.session_uuid.label=Session UUID
menu.session.session_configuration.label=Configuration
menu.error.malformed_message=Error: Malformed Message
menu.error.unexpected_message=Error: Unexpected Message

@ -143,14 +143,14 @@ menu.loading.joining_session=Trete Sitzung bei...
menu.loading.joining_lobby=Trete Warteraum bei...
menu.loading.back=Zurück zum Hauptmenü
menu.waiting.ready=Bereit
menu.waiting.not_ready=Nicht Bereit
menu.waiting.leave=Verlassen
menu.waiting.player_name.label=Eigener Name
menu.waiting.session_name.label=Sitzungsname
menu.waiting.session_uuid.label=Sitzungs-ID
menu.waiting.session_configuration.label=Spielvariante
menu.session.ready=Bereit
menu.session.not_ready=Nicht Bereit
menu.session.leave=Verlassen
menu.session.player_name.label=Eigener Name
menu.session.session_name.label=Sitzungsname
menu.session.session_uuid.label=Sitzungs-ID
menu.session.session_configuration.label=Spielvariante
menu.error.malformed_message=Fehler: Missgebildete Nachricht
menu.error.unexpected_message=Fehler: Unerwartete Nachricht

@ -3,7 +3,7 @@ package eu.jonahbauer.wizard.common.messages.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.jonahbauer.wizard.common.messages.ParseException;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.util.SerializationUtil;
import eu.jonahbauer.wizard.common.messages.util.SerializationUtil;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

@ -26,4 +26,8 @@ public class SessionData {
* Configuration of the session
*/
private final @NonNull Configuration configuration;
/**
* Whether the session is running.
*/
private final boolean running;
}

@ -2,7 +2,7 @@ package eu.jonahbauer.wizard.common.messages.observer;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.jonahbauer.wizard.common.messages.ParseException;
import eu.jonahbauer.wizard.common.util.SerializationUtil;
import eu.jonahbauer.wizard.common.messages.util.SerializationUtil;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

@ -2,7 +2,7 @@ package eu.jonahbauer.wizard.common.messages.player;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.jonahbauer.wizard.common.messages.ParseException;
import eu.jonahbauer.wizard.common.util.SerializationUtil;
import eu.jonahbauer.wizard.common.messages.util.SerializationUtil;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

@ -3,7 +3,7 @@ package eu.jonahbauer.wizard.common.messages.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.jonahbauer.wizard.common.messages.ParseException;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
import eu.jonahbauer.wizard.common.util.SerializationUtil;
import eu.jonahbauer.wizard.common.messages.util.SerializationUtil;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.common.util;
package eu.jonahbauer.wizard.common.messages.util;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@ -10,10 +10,10 @@ import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import eu.jonahbauer.wizard.common.messages.ParseException;
import eu.jonahbauer.wizard.common.util.StringUtil;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Modifier;
import java.util.Locale;
@UtilityClass
public class SerializationUtil {
@ -61,7 +61,7 @@ public class SerializationUtil {
if (Modifier.isFinal(modifiers) || subclass.isSealed() && !Modifier.isAbstract(modifiers)) {
var name = subclass.getSimpleName();
if (name.endsWith(suffix)) name = name.substring(0, name.length() - suffix.length());
name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
name = StringUtil.toSnakeCase(name);
objectMapper.registerSubtypes(new NamedType(subclass, name));
}

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.core.util;
package eu.jonahbauer.wizard.common.util;
import java.util.Map;

@ -1,11 +1,11 @@
package eu.jonahbauer.wizard.core.util;
package eu.jonahbauer.wizard.common.util;
import lombok.experimental.UtilityClass;
import java.util.Locale;
@UtilityClass
public class Util {
public class StringUtil {
public static String toSnakeCase(String str) {
return str.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
}

@ -0,0 +1,23 @@
module eu.jonahbauer.wizard.common {
exports eu.jonahbauer.wizard.common.machine;
exports eu.jonahbauer.wizard.common.messages;
exports eu.jonahbauer.wizard.common.messages.client;
exports eu.jonahbauer.wizard.common.messages.data;
exports eu.jonahbauer.wizard.common.messages.observer;
exports eu.jonahbauer.wizard.common.messages.player;
exports eu.jonahbauer.wizard.common.messages.server;
exports eu.jonahbauer.wizard.common.model;
exports eu.jonahbauer.wizard.common.util;
opens eu.jonahbauer.wizard.common.messages.client to com.fasterxml.jackson.databind;
opens eu.jonahbauer.wizard.common.messages.data to com.fasterxml.jackson.databind;
opens eu.jonahbauer.wizard.common.messages.observer to com.fasterxml.jackson.databind;
opens eu.jonahbauer.wizard.common.messages.player to com.fasterxml.jackson.databind;
opens eu.jonahbauer.wizard.common.messages.server to com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.module.paramnames;
requires static lombok;
requires static org.jetbrains.annotations;
}

@ -1,72 +0,0 @@
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.model.Configurations;
import java.util.List;
import java.util.Scanner;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CLI {
public static void main(String[] args) {
GameConfiguration config = Configurations.DEFAULT;
Observer observer = (player, msg) -> System.out.println(msg);
Game game = new Game(config, observer);
var players = List.of(
UUID.randomUUID(),
UUID.randomUUID(),
UUID.randomUUID(),
UUID.randomUUID()
);
game.start(players);
Scanner scanner = new Scanner(System.in);
Pattern pattern = Pattern.compile("(\\d) ([a-z]+) (.*)");
while (scanner.hasNextLine()) {
try {
Matcher matcher = pattern.matcher(scanner.nextLine());
if (!matcher.find()) {
System.err.println("Format is \"(\\d) ([a-z]+) (.*)\"");
continue;
}
String player = matcher.group(1);
String command = matcher.group(2);
String param = matcher.group(3);
int id = Integer.parseInt(player);
if (id > players.size()) {
System.err.println("ID must be between 0 and " + (players.size() - 1));
continue;
}
switch (command) {
case "predict" -> {
int prediction = Integer.parseInt(param);
game.onMessage(players.get(id), new PredictMessage(prediction));
}
case "play" -> {
Card card = Card.valueOf(param);
game.onMessage(players.get(id), new PlayCardMessage(card));
}
case "trump" -> {
Card.Suit suit = Card.Suit.valueOf(param);
game.onMessage(players.get(id), new PickTrumpMessage(suit));
}
default -> System.err.println("Unknown command: " + command);
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
}

@ -1,18 +1,22 @@
package eu.jonahbauer.wizard.core.machine;
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.common.machine.TimeoutContext;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.states.TransientState;
import eu.jonahbauer.wizard.core.machine.states.game.Created;
import eu.jonahbauer.wizard.core.machine.states.game.Error;
import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.TransientState;
import eu.jonahbauer.wizard.core.states.game.Created;
import eu.jonahbauer.wizard.core.states.game.Error;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.util.Util;
import eu.jonahbauer.wizard.common.util.StringUtil;
import lombok.Getter;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.List;
import java.util.Random;
@ -30,20 +34,25 @@ public final class Game extends TimeoutContext<GameState, Game> {
private final CompletableFuture<Void> future = new CompletableFuture<>();
public Game(GameConfiguration config, Observer observer) {
private Game(Random random, Configuration config, long timeout, long syncTimeout, Observer observer) {
super(new Created());
this.random = new Random();
this.config = config;
this.random = random;
this.config = Configurations.get(config).withTimeout(timeout).withSyncTimeout(syncTimeout);
this.observer = observer;
}
public Game(long seed, GameConfiguration config, Observer observer) {
super(new Created());
this.random = new Random(seed);
this.config = config;
this.observer = observer;
public Game(Configuration config, long timeout, long syncTimeout, Observer observer) {
this(new Random(), config, timeout, syncTimeout, observer);
}
@TestOnly
public Game(long seed, Configuration config, long timeout, long syncTimeout, Observer observer) {
this(new Random(seed), config, timeout, syncTimeout, observer);
}
@TestOnly
@VisibleForTesting
@SuppressWarnings("ClassEscapesDefinedScope")
public void resume(GameState state) {
transition(new Created(), state);
}
@ -56,10 +65,17 @@ public final class Game extends TimeoutContext<GameState, Game> {
execute(s -> s.onMessage(this, player, message));
}
public void onMessage(UUID player, PlayerMessage message, Runnable preHandleMessage) {
execute(s -> {
preHandleMessage.run();
return s.onMessage(this, player, message);
});
}
@Override
protected void onTransition(GameState from, GameState to) {
if (!(to instanceof TransientState)) {
notify(new StateMessage(to != null ? Util.toSnakeCase(to.getClass().getSimpleName()) : "null"));
notify(new StateMessage(to != null ? StringUtil.toSnakeCase(to.getClass().getSimpleName()) : "null"));
}
}

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.core.messages;
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;

@ -1,4 +0,0 @@
package eu.jonahbauer.wizard.core.machine.states;
public interface TransientState {
}

@ -1,22 +0,0 @@
package eu.jonahbauer.wizard.core.machine.states.game;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.round.StartingRound;
import java.util.Optional;
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
public final class Starting extends GameState {
public Starting(GameData data) {
super(data.require(PLAYERS));
}
@Override
public Optional<GameState> onEnter(Game game) {
return transition(new StartingRound(getData()));
}
}

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.experimental.UtilityClass;
import java.util.Comparator;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import java.util.List;
import java.util.UUID;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import java.util.List;
import java.util.UUID;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.Getter;
import java.util.List;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.Getter;
import java.util.List;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Contract;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.Getter;
import java.util.List;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.machine.states;
package eu.jonahbauer.wizard.core.states;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;

@ -1,10 +1,9 @@
package eu.jonahbauer.wizard.core.machine;
package eu.jonahbauer.wizard.core.states;
import eu.jonahbauer.wizard.common.machine.TimeoutState;
import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.SyncState;
import eu.jonahbauer.wizard.core.Game;
import lombok.Getter;
import org.jetbrains.annotations.Unmodifiable;
@ -13,8 +12,8 @@ import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import static eu.jonahbauer.wizard.core.machine.states.GameData.CURRENT_PLAYER;
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
import static eu.jonahbauer.wizard.core.states.GameData.CURRENT_PLAYER;
import static eu.jonahbauer.wizard.core.states.GameData.PLAYERS;
@Unmodifiable
public abstract class GameState implements TimeoutState<GameState, Game> {

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.core.machine.states;
package eu.jonahbauer.wizard.core.states;
public class InvalidDataException extends RuntimeException {
public InvalidDataException(String message) {

@ -1,11 +1,10 @@
package eu.jonahbauer.wizard.core.machine.states;
package eu.jonahbauer.wizard.core.states;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.Game;
import java.util.HashSet;
import java.util.Optional;
@ -14,7 +13,7 @@ import java.util.UUID;
import java.util.function.Function;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.SYNC;
import static eu.jonahbauer.wizard.core.machine.states.GameData.PLAYERS;
import static eu.jonahbauer.wizard.core.states.GameData.PLAYERS;
public final class SyncState extends GameState implements TransientState {
private final transient Set<UUID> ready = new HashSet<>();

@ -0,0 +1,4 @@
package eu.jonahbauer.wizard.core.states;
public interface TransientState {
}

@ -1,8 +1,8 @@
package eu.jonahbauer.wizard.core.machine.states.game;
package eu.jonahbauer.wizard.core.states.game;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import java.util.List;
import java.util.Optional;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.machine.states.game;
package eu.jonahbauer.wizard.core.states.game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import lombok.Getter;
@Getter

@ -1,8 +1,8 @@
package eu.jonahbauer.wizard.core.machine.states.game;
package eu.jonahbauer.wizard.core.states.game;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import java.util.Optional;

@ -1,13 +1,13 @@
package eu.jonahbauer.wizard.core.machine.states.game;
package eu.jonahbauer.wizard.core.states.game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
import java.util.Optional;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class Finishing extends GameState {

@ -0,0 +1,22 @@
package eu.jonahbauer.wizard.core.states.game;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.round.StartingRound;
import java.util.Optional;
import static eu.jonahbauer.wizard.core.states.GameData.PLAYERS;
public final class Starting extends GameState {
public Starting(GameData data) {
super(data.require(PLAYERS));
}
@Override
public Optional<GameState> onEnter(Game game) {
return transition(new StartingRound(getData()));
}
}

@ -1,14 +1,14 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.InvalidDataException;
import java.util.HashMap;
import java.util.Map;
@ -16,7 +16,7 @@ import java.util.Optional;
import java.util.UUID;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.CHANGE_PREDICTION;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class ChangingPrediction extends RoundState {
private transient final int oldPrediction;

@ -1,15 +1,15 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.model.deck.Deck;
import java.util.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class Dealing extends RoundState {

@ -1,15 +1,15 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.model.card.GameCards;
import java.util.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class DeterminingTrump extends RoundState {

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
@ -7,17 +7,17 @@ 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 eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.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;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.TRUMP_CARD;
public final class DeterminingTrumpUserInput extends RoundState implements TransientState {
private final transient UUID player;

@ -1,9 +1,9 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.game.Finishing;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.game.Finishing;
import eu.jonahbauer.wizard.common.messages.observer.ScoreMessage;
import java.util.HashMap;
@ -11,7 +11,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class FinishingRound extends RoundState {

@ -1,10 +1,10 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.trick.StartingTrick;
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
@ -16,7 +16,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.MAKE_PREDICTION;
@Getter

@ -1,11 +1,11 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public abstract class RoundState extends GameState {
public static GameData requirements(GameData data) {

@ -1,8 +1,8 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
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.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import java.util.Optional;

@ -1,19 +1,19 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
import eu.jonahbauer.wizard.core.machine.states.round.ChangingPrediction;
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.InvalidDataException;
import eu.jonahbauer.wizard.core.states.round.ChangingPrediction;
import eu.jonahbauer.wizard.core.states.round.FinishingRound;
import eu.jonahbauer.wizard.common.messages.observer.TrickMessage;
import eu.jonahbauer.wizard.core.model.card.*;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import java.util.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class FinishingTrick extends TrickState {

@ -1,4 +1,4 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.TimeoutMessage;
@ -6,15 +6,15 @@ import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.GameState;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public final class Juggling extends TrickState {
private final transient Map<UUID, Card> juggledCards = new ConcurrentHashMap<>();

@ -1,22 +1,22 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
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.core.machine.GameState;
import eu.jonahbauer.wizard.core.machine.states.InvalidDataException;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.InvalidDataException;
import eu.jonahbauer.wizard.common.messages.observer.CardMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.core.model.card.GameCards;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.PLAY_CARD;
public final class PlayingCard extends TrickState {

@ -1,8 +1,8 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
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.Game;
import eu.jonahbauer.wizard.core.states.GameState;
import eu.jonahbauer.wizard.core.states.GameData;
import java.util.Optional;

@ -1,9 +1,9 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.round.RoundState;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.round.RoundState;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.*;
public abstract class TrickState extends RoundState {
public static GameData requirements(GameData data) {

@ -0,0 +1,8 @@
module eu.jonahbauer.wizard.core {
exports eu.jonahbauer.wizard.core;
requires eu.jonahbauer.wizard.common;
requires static lombok;
requires static org.jetbrains.annotations;
}

@ -1,6 +1,7 @@
package eu.jonahbauer.wizard.core.machine;
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.Game;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
@ -13,7 +14,7 @@ public class GameTest {
public void runDefault(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
Game game = new Game(
repetitionInfo.getCurrentRepetition(),
Configurations.DEFAULT.withTimeout(0).withSyncTimeout(0),
Configuration.DEFAULT, 0, 0,
(player, msg) -> System.out.println(msg)
);
var players = List.of(
@ -31,7 +32,7 @@ public class GameTest {
public void runAnniversary2016(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
Game game = new Game(
repetitionInfo.getCurrentRepetition(),
Configurations.ANNIVERSARY_2016.withTimeout(0).withSyncTimeout(0),
Configuration.ANNIVERSARY_2016, 0, 0,
(player, msg) -> System.out.println(msg)
);
var players = List.of(
@ -49,7 +50,7 @@ public class GameTest {
public void runAnniversary2021(RepetitionInfo repetitionInfo) throws InterruptedException, ExecutionException {
Game game = new Game(
repetitionInfo.getCurrentRepetition(),
Configurations.ANNIVERSARY_2021.withTimeout(0).withSyncTimeout(0),
Configuration.ANNIVERSARY_2021, 0, 0,
(player, msg) -> System.out.println(msg)
);
var players = List.of(

@ -1,6 +1,6 @@
package eu.jonahbauer.wizard.core.machine;
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
import eu.jonahbauer.wizard.core.states.game.Finished;
import lombok.experimental.UtilityClass;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;

@ -1,11 +1,11 @@
package eu.jonahbauer.wizard.core.machine;
package eu.jonahbauer.wizard.core;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.player.*;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.GameState;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@ -1,7 +1,7 @@
package eu.jonahbauer.wizard.core.model.card;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.common.util.Pair;
import org.junit.jupiter.api.Test;
import java.util.List;

@ -1,6 +1,5 @@
package eu.jonahbauer.wizard.core.machine.states;
package eu.jonahbauer.wizard.core.states;
import eu.jonahbauer.wizard.core.machine.GameState;
import org.junit.jupiter.api.Test;
import java.util.HashSet;

@ -1,17 +1,16 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.HandMessage;
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
import eu.jonahbauer.wizard.common.messages.observer.TrumpMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.MessageQueue;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.SyncState;
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.MessageQueue;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.SyncState;
import eu.jonahbauer.wizard.core.states.game.Finished;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
@ -20,9 +19,9 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
import static eu.jonahbauer.wizard.core.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.TypedValue.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -36,8 +35,8 @@ public class DeterminingTrumpTest {
@SneakyThrows
@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));
private Game performTest(Configuration configuration, int round, Map<UUID, List<Card>> hands, Card trumpCard, MessageQueue queue) {
Game game = spy(new Game(configuration, 10 * 60 * 1000, 10 * 60 * 1000, queue));
doFinish().when(game).transition(argThat(argument -> argument instanceof SyncState && argument.getData().has(TRUMP_SUIT)));
queue.setGame(game);
@ -70,7 +69,7 @@ public class DeterminingTrumpTest {
// play cards in given order
MessageQueue queue = new MessageQueue();
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.YELLOW_1, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 0, hands, Card.YELLOW_1, queue);
// validate messages
InOrder order = inOrder(game);
@ -95,7 +94,7 @@ public class DeterminingTrumpTest {
MessageQueue queue = new MessageQueue()
.sync(players);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_JESTER, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 0, hands, Card.GREEN_JESTER, queue);
// validate messages
InOrder order = inOrder(game);
@ -121,7 +120,7 @@ public class DeterminingTrumpTest {
.sync(players)
.addPickTrump(players[0], Card.Suit.GREEN);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.BLUE_WIZARD, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 0, hands, Card.BLUE_WIZARD, queue);
// validate messages
InOrder order = inOrder(game);
@ -149,7 +148,7 @@ public class DeterminingTrumpTest {
.sync(players)
.addPickTrump(players[3], Card.Suit.YELLOW);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, hands, Card.GREEN_1, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 0, hands, Card.GREEN_1, queue);
// validate messages
InOrder order = inOrder(game);

@ -1,17 +1,16 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.PredictionMessage;
import eu.jonahbauer.wizard.common.messages.observer.StateMessage;
import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameTestUtils;
import eu.jonahbauer.wizard.core.machine.MessageQueue;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
import eu.jonahbauer.wizard.core.machine.states.trick.StartingTrick;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.GameTestUtils;
import eu.jonahbauer.wizard.core.MessageQueue;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.game.Finished;
import eu.jonahbauer.wizard.core.states.trick.StartingTrick;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
@ -21,9 +20,9 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
import static eu.jonahbauer.wizard.core.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.TypedValue.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -37,7 +36,7 @@ public class PredictingTest {
@SneakyThrows
@SuppressWarnings("SameParameterValue")
private Game performTest(GameConfiguration configuration, int round, MessageQueue queue) {
private Game performTest(Configuration configuration, int round, MessageQueue queue) {
Map<UUID, List<Card>> hands = Map.of(
players[0], Collections.nCopies(round + 1, Card.HIDDEN),
players[1], Collections.nCopies(round + 1, Card.HIDDEN),
@ -45,7 +44,7 @@ public class PredictingTest {
players[3], Collections.nCopies(round + 1, Card.HIDDEN)
);
Game game = spy(new Game(configuration, queue));
Game game = spy(new Game(configuration, 10 * 60 * 1000, 10 * 60 * 1000, queue));
doFinish().when(game).transition(any(StartingTrick.class));
queue.setGame(game);
@ -77,7 +76,7 @@ public class PredictingTest {
.addPrediction(players[2], 3)
.addPrediction(players[3], 0);
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 3, queue);
// validate messages
InOrder order = inOrder(game);
@ -119,7 +118,7 @@ public class PredictingTest {
.addPrediction(players[3], 0)
.end();
Game game = performTest(Configurations.ANNIVERSARY_2021, 3, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 3, queue);
// validate messages
InOrder order = inOrder(game);
@ -153,7 +152,7 @@ public class PredictingTest {
.addPrediction(players[3], 0)
.end();
Game game = performTest(Configurations.ANNIVERSARY_2021_PM1, 3, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021_PM1, 3, queue);
// validate messages
InOrder order = inOrder(game);

@ -1,13 +1,12 @@
package eu.jonahbauer.wizard.core.machine.states.round;
package eu.jonahbauer.wizard.core.states.round;
import eu.jonahbauer.wizard.common.messages.observer.*;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.MessageQueue;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.game.Finished;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.MessageQueue;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.game.Finished;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
@ -17,9 +16,9 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.finish;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
import static eu.jonahbauer.wizard.core.GameTestUtils.finish;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.TypedValue.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -32,8 +31,8 @@ public class RoundTest {
};
@SneakyThrows
private Game performTest(long seed, GameConfiguration configuration, int round, MessageQueue queue) {
Game game = spy(new Game(seed, configuration, queue));
private Game performTest(long seed, Configuration configuration, int round, MessageQueue queue) {
Game game = spy(new Game(seed, configuration, 10 * 60 * 1000, 10 * 60 * 1000, queue));
doCallRealMethod().doAnswer(finish()).when(game).transition(any(StartingRound.class));
queue.setGame(game);
@ -106,7 +105,7 @@ public class RoundTest {
.addCard(players[2], Card.BLUE_6);
int round = 6;
Game game = performTest(0L, Configurations.DEFAULT, round, queue);
Game game = performTest(0L, Configuration.DEFAULT, round, queue);
game.await();
InOrder order = inOrder(game);
@ -232,7 +231,7 @@ public class RoundTest {
.end();
int round = 6;
Game game = performTest(227L, Configurations.ANNIVERSARY_2021_PM1, round, queue);
Game game = performTest(227L, Configuration.ANNIVERSARY_2021_PM1, round, queue);
game.await();
InOrder order = inOrder(game);

@ -1,14 +1,13 @@
package eu.jonahbauer.wizard.core.machine.states.trick;
package eu.jonahbauer.wizard.core.states.trick;
import eu.jonahbauer.wizard.common.messages.observer.*;
import eu.jonahbauer.wizard.common.model.Card;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.machine.GameTestUtils;
import eu.jonahbauer.wizard.core.machine.MessageQueue;
import eu.jonahbauer.wizard.core.machine.states.GameData;
import eu.jonahbauer.wizard.core.machine.states.round.FinishingRound;
import eu.jonahbauer.wizard.core.model.GameConfiguration;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.GameTestUtils;
import eu.jonahbauer.wizard.core.MessageQueue;
import eu.jonahbauer.wizard.core.states.GameData;
import eu.jonahbauer.wizard.core.states.round.FinishingRound;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
@ -17,10 +16,10 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.machine.GameTestUtils.finish;
import static eu.jonahbauer.wizard.core.machine.states.GameData.*;
import static eu.jonahbauer.wizard.core.machine.states.GameData.TypedValue.entry;
import static eu.jonahbauer.wizard.core.GameTestUtils.doFinish;
import static eu.jonahbauer.wizard.core.GameTestUtils.finish;
import static eu.jonahbauer.wizard.core.states.GameData.*;
import static eu.jonahbauer.wizard.core.states.GameData.TypedValue.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -35,8 +34,8 @@ public class TrickTest {
@SneakyThrows
@SuppressWarnings("SameParameterValue")
private Game performTest(GameConfiguration configuration, int round, int trick, Map<UUID, List<Card>> hands, Card.Suit trump, MessageQueue queue) {
Game game = spy(new Game(configuration, queue));
private Game performTest(Configuration configuration, int round, int trick, Map<UUID, List<Card>> hands, Card.Suit trump, MessageQueue queue) {
Game game = spy(new Game(configuration, 10 * 60 * 1000, 10 * 60 * 1000, queue));
doCallRealMethod().doAnswer(finish()).when(game).transition(any(StartingTrick.class));
doFinish().when(game).transition(any(FinishingRound.class));
queue.setGame(game);
@ -75,7 +74,7 @@ public class TrickTest {
.sync(players)
.addCards(List.of(players), hands, 0);
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
Game game = performTest(Configuration.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
// validate messages
InOrder order = inOrder(game);
@ -116,7 +115,7 @@ public class TrickTest {
.addCard(players[3], Card.BLUE_1)
.end();
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
Game game = performTest(Configuration.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
// validate messages
InOrder order = inOrder(game);
@ -159,7 +158,7 @@ public class TrickTest {
.addCard(players[3], Card.BLUE_1)
.addChangePrediction(players[2], 1);
Game game = performTest(Configurations.ANNIVERSARY_2021, 0, 0, hands, Card.Suit.GREEN, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 0, 0, hands, Card.Suit.GREEN, queue);
// validate messages
InOrder order = inOrder(game);
@ -203,7 +202,7 @@ public class TrickTest {
.addJuggle(players[3], Card.BLUE_1)
.end();
Game game = performTest(Configurations.ANNIVERSARY_2021, 1, 0, hands, Card.Suit.YELLOW, queue);
Game game = performTest(Configuration.ANNIVERSARY_2021, 1, 0, hands, Card.Suit.YELLOW, queue);
// validate messages
InOrder order = inOrder(game);
@ -248,7 +247,7 @@ public class TrickTest {
.addCard(players[2], Card.GREEN_1)
.addCard(players[3], Card.BLUE_1);
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
Game game = performTest(Configuration.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
// validate messages
InOrder order = inOrder(game);
@ -290,7 +289,7 @@ public class TrickTest {
.addCard(players[2], Card.GREEN_WIZARD)
.addCard(players[3], Card.BLUE_1);
Game game = performTest(Configurations.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
Game game = performTest(Configuration.DEFAULT, 0, 0, hands, Card.Suit.BLUE, queue);
// validate messages
InOrder order = inOrder(game);

@ -4,14 +4,22 @@ import eu.jonahbauer.wizard.common.messages.server.*;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.server.debug.DebugSession;
import eu.jonahbauer.wizard.server.machine.Player;
import eu.jonahbauer.wizard.server.management.LobbyMBean;
import eu.jonahbauer.wizard.server.management.SessionMBean;
import lombok.extern.log4j.Log4j2;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Lobby {
@Log4j2
public class Lobby implements LobbyMBean {
@Language("RegExp")
private static final String SESSION_NAME_PATTERN = "[a-zA-Z0-9_' ]{1,20}";
private static final Lobby INSTANCE = new Lobby();
@ -19,12 +27,17 @@ public class Lobby {
return INSTANCE;
}
private final MBeanServer mBeanServer;
private final Map<UUID, Session> sessions = new ConcurrentHashMap<>();
private final List<Player> players = new ArrayList<>();
// read lock is required whenever players are read or sessions are modified, write lock is required when players are modified
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Lobby() {}
private Lobby() {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
registerLobbyMBean();
}
public Session createSession(@NotNull String name, long timeout, @NotNull Configuration configuration) {
if (!name.matches(SESSION_NAME_PATTERN)) {
@ -42,6 +55,9 @@ public class Lobby {
session = new Session(UUID.randomUUID(), name, timeout, configuration);
} while (sessions.putIfAbsent(session.getUuid(), session) != null);
log.info("Created session {}.", session.getUuid());
registerSessionMBean(session);
notifyPlayers(new SessionCreatedMessage(session.toData()));
return session;
@ -58,6 +74,9 @@ public class Lobby {
session = new DebugSession(UUID.randomUUID(), name, timeout, configuration);
} while (sessions.putIfAbsent(session.getUuid(), session) != null);
log.info("Created debug session {}.", session.getUuid());
registerSessionMBean(session);
notifyPlayers(new SessionCreatedMessage(session.toData()));
return session;
@ -73,8 +92,11 @@ public class Lobby {
public void removeSession(UUID uuid) {
lock.readLock().lock();
try {
sessions.remove(uuid);
if (sessions.remove(uuid) != null) {
log.info("Removed session {}.", uuid);
unregisterSessionMBean(uuid);
notifyPlayers(new SessionRemovedMessage(uuid));
}
} finally {
lock.readLock().unlock();
}
@ -117,4 +139,53 @@ public class Lobby {
lock.readLock().unlock();
}
}
//<editor-fold desc="JMX" defaultstate="collapsed">
private void registerLobbyMBean() {
try {
var name = new ObjectName("eu.jonahbauer.wizard.server:type=Lobby");
mBeanServer.registerMBean(new StandardMBean(this, LobbyMBean.class), name);
} catch (Exception e) {
log.warn("Could not register LobbyMBean.", e);
}
}
private void registerSessionMBean(@NotNull Session session) {
try {
var name = new ObjectName("eu.jonahbauer.wizard.server:type=Session,name=" + session.getUuid());
mBeanServer.registerMBean(new StandardMBean(session, SessionMBean.class), name);
} catch (Exception e) {
log.warn("Could not register SessionMBean for session {}.", session.getUuid(), e);
}
}
private void unregisterSessionMBean(@NotNull UUID uuid) {
try {
var name = new ObjectName("eu.jonahbauer.wizard.server:type=Session,name=" + uuid);
mBeanServer.unregisterMBean(name);
} catch (Exception e) {
log.warn("Could not unregister SessionMBean for session {}.", uuid, e);
}
}
@Override
public int getPlayerCount() {
lock.readLock().lock();
try {
return players.size();
} finally {
lock.readLock().unlock();
}
}
@Override
public int getTotalPlayerCount() {
return sessions.values().stream().mapToInt(Session::getPlayerCount).sum() + getPlayerCount();
}
@Override
public int getSessionCount() {
return sessions.size();
}
//</editor-fold>
}

@ -1,6 +1,5 @@
package eu.jonahbauer.wizard.server;
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
@ -9,11 +8,11 @@ import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.messages.server.*;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.core.Game;
import eu.jonahbauer.wizard.core.Observer;
import eu.jonahbauer.wizard.common.util.Pair;
import eu.jonahbauer.wizard.server.machine.Player;
import eu.jonahbauer.wizard.server.management.SessionMBean;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -29,7 +28,7 @@ import java.util.concurrent.ThreadLocalRandom;
@Getter
@Log4j2
@EqualsAndHashCode(of = "uuid")
public class Session implements Observer {
public class Session implements Observer, SessionMBean {
@Language("RegExp")
private static final String PLAYER_NAME_PATTERN = "[a-zA-Z0-9_' ]{1,20}";
protected static final int MIN_PLAYERS = 3;
@ -54,6 +53,11 @@ public class Session implements Observer {
this.configuration = configuration;
}
@Override
public boolean isRunning() {
return game != null;
}
/**
* Associates the given player with this session and notifies all other players in the
* session with a {@link PlayerJoinedMessage}, the joining player with a {@link SessionJoinedMessage} and all
@ -64,7 +68,7 @@ public class Session implements Observer {
* @return the players uuid
*/
public synchronized UUID join(Player player, String name) {
if (game != null) {
if (isRunning()) {
throw new NackException(NackMessage.GAME_ALREADY_STARTED, "Game has already started.");
} else if (players.size() == MAX_PLAYERS) {
throw new NackException(NackMessage.SESSION_FULL, "Session is full.");
@ -81,7 +85,7 @@ public class Session implements Observer {
sessionPlayer.setPlayer(player);
notifyJoined(sessionPlayer.toData());
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
notifyLobby();
return sessionPlayer.getUuid();
}
@ -116,13 +120,13 @@ public class Session implements Observer {
}
public synchronized void leave(UUID player) {
if (game == null) {
if (!isRunning()) {
if (players.remove(player) != null) {
if (players.size() == 0) {
Lobby.getInstance().removeSession(uuid);
} else {
notifyPlayers(new PlayerLeftMessage(player));
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
notifyLobby();
}
}
} else {
@ -140,7 +144,7 @@ public class Session implements Observer {
var player = players.get(uuid);
if (player == null) {
throw new NackException(NackMessage.PLAYER_NOT_FOUND, "Who are you?");
} else if (game != null) {
} else if (isRunning()) {
throw new NackException(NackMessage.GAME_ALREADY_STARTED, "Game has already started.");
}
@ -165,15 +169,11 @@ public class Session implements Observer {
var player = players.get(uuid);
if (player == null) {
throw new NackException(NackMessage.PLAYER_NOT_FOUND, "Who are you?");
} else if (game == null) {
} else if (!isRunning()) {
throw new NackException(NackMessage.GAME_NOT_YET_STARTED, "Game hat not yet started.");
} else {
try {
game.execute(s -> {
// start buffering while game is locked
player.buffer();
return s.onMessage(game, uuid, message);
});
game.onMessage(uuid, message, player::buffer);
player.send(new AckMessage());
} catch (IllegalStateException e) {
throw new NackException(NackMessage.ILLEGAL_STATE, e.getMessage());
@ -183,10 +183,11 @@ public class Session implements Observer {
}
}
protected void startGame() {
protected synchronized void startGame() {
notifyPlayers(new StartingGameMessage());
messages.add(Pair.of(null, new StartingGameMessage()));
game = new Game(Configurations.get(configuration).withTimeout(timeout), this);
game = new Game(configuration, timeout, 10 * 60 * 1000, this);
notifyLobby();
game.start(List.copyOf(players.keySet()));
CompletableFuture.runAsync(() -> {
while (true) {
@ -200,8 +201,12 @@ public class Session implements Observer {
break;
}
}
finishGame();
});
}
protected synchronized void finishGame() {
players.forEach((id, player) -> player.setReady(false));
synchronized (this) {
game = null;
messages.clear();
for (SessionPlayer player : List.copyOf(players.values())) {
@ -209,11 +214,10 @@ public class Session implements Observer {
leave(player.getUuid());
}
}
}
});
notifyLobby();
}
protected void notifyJoined(PlayerData joined) {
protected synchronized void notifyJoined(PlayerData joined) {
var message = new PlayerJoinedMessage(joined);
for (SessionPlayer player : players.values()) {
if (player.getUuid().equals(joined.getUuid())) {
@ -229,14 +233,14 @@ public class Session implements Observer {
}
}
protected void notifyPlayers(ServerMessage message) {
protected synchronized void notifyPlayers(ServerMessage message) {
for (SessionPlayer player : players.values()) {
player.send(message);
}
}
public SessionData toData() {
return new SessionData(uuid, name, players.size(), configuration);
return new SessionData(uuid, name, players.size(), configuration, isRunning());
}
@Override
@ -271,6 +275,30 @@ public class Session implements Observer {
}
}
protected void notifyLobby() {
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
}
public void close() {
for (var sessionPlayer : getPlayers().values()) {
var player = sessionPlayer.getPlayer();
if (player != null) {
player.disconnect("Session was forcibly closed.");
}
}
}
//<editor-fold desc="JMX" defaulstate="collapsed">
public boolean isDebug() {
return false;
}
@Override
public synchronized int getPlayerCount() {
return players.size();
}
//</editor-fold>
@Data
@EqualsAndHashCode(of = "uuid")
protected static class SessionPlayer {

@ -3,14 +3,11 @@ package eu.jonahbauer.wizard.server.debug;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import eu.jonahbauer.wizard.common.messages.server.SessionModifiedMessage;
import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.server.Lobby;
import eu.jonahbauer.wizard.server.NackException;
import eu.jonahbauer.wizard.server.Session;
import eu.jonahbauer.wizard.server.machine.Player;
import java.io.IOException;
import java.util.UUID;
public class DebugSession extends Session {
@ -52,7 +49,7 @@ public class DebugSession extends Session {
sessionPlayer.setPlayer(player);
notifyJoined(sessionPlayer.toData());
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
notifyLobby();
return sessionPlayer.getUuid();
}
@ -60,19 +57,13 @@ public class DebugSession extends Session {
@Override
protected void startGame() {}
public void close() {
for (var sessionPlayer : getPlayers().values()) {
try {
var player = sessionPlayer.getPlayer();
if (player != null) {
player.disconnect();
}
} catch (IOException ignored) {}
}
}
@Override
public void notifyPlayers(ServerMessage message) {
super.notifyPlayers(message);
}
@Override
public boolean isDebug() {
return true;
}
}

@ -6,6 +6,7 @@ import eu.jonahbauer.wizard.common.messages.server.Response;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import eu.jonahbauer.wizard.server.machine.states.CreatedState;
import lombok.SneakyThrows;
import org.jetbrains.annotations.Nullable;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
@ -44,8 +45,16 @@ public class Player extends Context<ClientState, Player> {
shouldBuffer = Response.class;
}
public void disconnect() throws IOException {
session.close(CloseStatus.SERVER_ERROR);
public void disconnect() {
disconnect(null);
}
public void disconnect(@Nullable String reason) {
try {
session.close(new CloseStatus(CloseStatus.GOING_AWAY.getCode(), reason));
} catch (IOException ignored) {
// ignored
}
}
@SneakyThrows

@ -0,0 +1,11 @@
package eu.jonahbauer.wizard.server.management;
@SuppressWarnings("unused")
public interface LobbyMBean {
int getPlayerCount();
int getTotalPlayerCount();
int getSessionCount();
}

@ -0,0 +1,19 @@
package eu.jonahbauer.wizard.server.management;
import java.util.UUID;
@SuppressWarnings("unused")
public interface SessionMBean {
UUID getUuid();
String getName();
int getPlayerCount();
boolean isRunning();
boolean isDebug();
void close();
}
Loading…
Cancel
Save