From 7c3fa7d0d67c46c6f5f87a3bdccc40c18d025778 Mon Sep 17 00:00:00 2001 From: Jonah Bauer Date: Sun, 5 Dec 2021 11:21:46 +0100 Subject: [PATCH] reconnect in server and cli --- .../client/cli/commands/LobbyCommand.java | 11 +++++ .../wizard/client/cli/state/Game.java | 5 +- .../wizard/client/cli/state/Session.java | 7 ++- .../common/messages/server/NackMessage.java | 1 + .../eu/jonahbauer/wizard/server/Session.java | 49 ++++++++++++++++--- .../server/machine/states/LobbyState.java | 18 +++++++ 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/commands/LobbyCommand.java b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/commands/LobbyCommand.java index ba73de8..bb1b57a 100644 --- a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/commands/LobbyCommand.java +++ b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/commands/LobbyCommand.java @@ -6,6 +6,7 @@ import eu.jonahbauer.wizard.client.cli.state.Lobby; import eu.jonahbauer.wizard.client.cli.state.Menu; import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage; import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage; +import eu.jonahbauer.wizard.common.messages.client.RejoinMessage; import eu.jonahbauer.wizard.common.messages.data.SessionData; import eu.jonahbauer.wizard.common.model.Configuration; import lombok.Getter; @@ -85,6 +86,16 @@ public class LobbyCommand { return new AwaitingJoinSession(); } + @Command(name = "rejoin") + public AwaitingJoinSession rejoin( + @Parameters(index = "0", paramLabel = "", description = "session uuid", completionCandidates = SessionCompleter.class) UUID session, + @Parameters(index = "1", paramLabel = "", description = "player uuid") UUID player, + @Parameters(index = "2", paramLabel = "", description = "player secret") String secret + ) { + client.send(new RejoinMessage(session, player, secret)); + return new AwaitingJoinSession(); + } + public static class SessionCompleter implements Iterable { private final Lobby lobby; diff --git a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Game.java b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Game.java index 961c853..ed66e99 100644 --- a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Game.java +++ b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Game.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; public final class Game extends BaseState { private final UUID self; private final UUID session; + private final String secret; private final Map players; private final Map scores = new HashMap<>(); @@ -37,9 +38,10 @@ public final class Game extends BaseState { private Card trumpCard; private Card.Suit trumpSuit; - public Game(UUID self, UUID session, Map players) { + public Game(UUID self, UUID session, String secret, Map players) { this.self = self; this.session = session; + this.secret = secret; this.players = players; } @@ -185,6 +187,7 @@ public final class Game extends BaseState { return Optional.of(new Session( self, session, + secret, false, players.entrySet().stream() .collect(Collectors.toMap( diff --git a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Session.java b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Session.java index 8e1bf68..d422ec9 100644 --- a/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Session.java +++ b/wizard-client/wizard-client-cli/src/main/java/eu/jonahbauer/wizard/client/cli/state/Session.java @@ -20,6 +20,7 @@ public final class Session extends BaseState { private final UUID self; private final UUID session; + private final String secret; private final Map players = new HashMap<>(); private boolean ready; @@ -30,12 +31,14 @@ public final class Session extends BaseState { this.self = joined.getPlayer(); this.session = joined.getSession(); this.ready = false; + this.secret = joined.getSecret(); joined.getPlayers().forEach(player -> players.put(player.getUuid(), player)); } - public Session(UUID self, UUID session, boolean ready, Map players) { + public Session(UUID self, UUID session, String secret, boolean ready, Map players) { this.self = self; this.session = session; + this.secret = secret; this.players.putAll(players); this.ready = ready; } @@ -47,6 +50,7 @@ public final class Session extends BaseState { } else { client.printfln("Successfully joined session %s. There are %d other players.", session, players.size() - 1); } + client.printfln("You are %s. Your secret is %s", self, secret); return super.onEnter(client); } @@ -70,6 +74,7 @@ public final class Session extends BaseState { return Optional.of(new Game( self, session, + secret, players.values().stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName)) )); } else if (nextReady != null && message instanceof NackMessage nack) { diff --git a/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/server/NackMessage.java b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/server/NackMessage.java index 3a4c2c5..ad17fc5 100644 --- a/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/server/NackMessage.java +++ b/wizard-common/src/main/java/eu/jonahbauer/wizard/common/messages/server/NackMessage.java @@ -20,6 +20,7 @@ public final class NackMessage extends ServerMessage implements Response { public static final int GAME_ALREADY_STARTED = 301; public static final int GAME_NOT_YET_STARTED = 302; public static final int SESSION_FULL = 303; + public static final int ALREADY_CONNECTED = 304; @Deprecated public static final int BAD_REQUEST = 999; diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java index e6449cb..b6c455d 100644 --- a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java @@ -9,6 +9,7 @@ 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.server.machine.Player; import lombok.AccessLevel; import lombok.Data; @@ -16,10 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.extern.log4j.Log4j2; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; @@ -40,6 +38,7 @@ public class Session implements Observer { private final Map players = new HashMap<>(); private Game game; + private final List> messages = new ArrayList<>(); public Session(UUID uuid, String name, long timeout, Configuration configuration) { @@ -79,6 +78,34 @@ public class Session implements Observer { return sessionPlayer.getUuid(); } + public synchronized PlayerData rejoin(Player player, UUID uuid, String secret) { + if (!players.containsKey(uuid)) { + throw new NackException(NackMessage.ILLEGAL_ARGUMENT, "An error occurred."); + } + + SessionPlayer sessionPlayer = players.get(uuid); + if (sessionPlayer.isConnected()) { + throw new NackException(NackMessage.ILLEGAL_ARGUMENT, "An error occurred."); + } else if (!sessionPlayer.getSecret().equals(secret)) { + throw new NackException(NackMessage.ILLEGAL_ARGUMENT, "An error occurred."); + } + + sessionPlayer.setPlayer(player); + player.send(new SessionJoinedMessage( + this.uuid, + uuid, + players.values().stream().map(SessionPlayer::toData).toList(), + secret + )); + for (Pair message : messages) { + if (message.getKey() == null || message.getKey().equals(uuid)) { + player.send(message.getValue()); + } + } + + return sessionPlayer.toData(); + } + public synchronized void leave(UUID player) { if (game == null) { if (players.remove(player) != null) { @@ -149,6 +176,7 @@ public class Session implements Observer { private void startGame() { notifyPlayers(new StartingGameMessage()); + messages.add(Pair.of(null, new StartingGameMessage())); game = new Game(Configurations.get(configuration).withTimeout(timeout), this); game.start(List.copyOf(players.keySet())); CompletableFuture.runAsync(() -> { @@ -166,6 +194,7 @@ public class Session implements Observer { players.forEach((id, player) -> player.setReady(false)); synchronized (this) { game = null; + messages.clear(); for (SessionPlayer player : List.copyOf(players.values())) { if (!player.isConnected()) { leave(player.getUuid()); @@ -202,13 +231,17 @@ public class Session implements Observer { } @Override - public void notify(ObserverMessage message) { - notifyPlayers(new GameMessage(message)); + public synchronized void notify(ObserverMessage message) { + var gameMessage = new GameMessage(message); + notifyPlayers(gameMessage); + messages.add(Pair.of(null, gameMessage)); } @Override - public void notify(UUID player, ObserverMessage message) { - players.get(player).send(new GameMessage(message)); + public synchronized void notify(UUID player, ObserverMessage message) { + var gameMessage = new GameMessage(message); + players.get(player).send(gameMessage); + messages.add(Pair.of(player, gameMessage)); } @Data diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java index 2d6b26c..8b7449e 100644 --- a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java @@ -3,6 +3,7 @@ package eu.jonahbauer.wizard.server.machine.states; import eu.jonahbauer.wizard.common.messages.client.ClientMessage; import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage; import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage; +import eu.jonahbauer.wizard.common.messages.client.RejoinMessage; import eu.jonahbauer.wizard.common.messages.server.NackMessage; import eu.jonahbauer.wizard.server.Lobby; import eu.jonahbauer.wizard.server.NackException; @@ -51,6 +52,23 @@ public class LobbyState implements ClientState { Lobby.getInstance().join(player); return Optional.empty(); } + } else if (message instanceof RejoinMessage rejoin) { + Lobby.getInstance().leave(player); + try { + player.buffer(); + var session = Lobby.getInstance().getSession(rejoin.getSession()); + if (session == null) { + throw new NackException(NackMessage.NOT_FOUND, "Session not found."); + } else { + var data = session.rejoin(player, rejoin.getPlayer(), rejoin.getSecret()); + return Optional.of(new SessionState(session.getUuid(), data.getUuid(), data.getName())); + } + } catch (NackException nack) { + // nack must be sent before joinlobby + player.send(nack.toMessage()); + Lobby.getInstance().join(player); + return Optional.empty(); + } } else { return ClientState.super.onMessage(player, message); }