reconnect in server and cli

main
Jonah Bauer 3 years ago
parent 17cb0190c9
commit 7c3fa7d0d6

@ -6,6 +6,7 @@ import eu.jonahbauer.wizard.client.cli.state.Lobby;
import eu.jonahbauer.wizard.client.cli.state.Menu; import eu.jonahbauer.wizard.client.cli.state.Menu;
import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage; import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage;
import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage; 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.messages.data.SessionData;
import eu.jonahbauer.wizard.common.model.Configuration; import eu.jonahbauer.wizard.common.model.Configuration;
import lombok.Getter; import lombok.Getter;
@ -85,6 +86,16 @@ public class LobbyCommand {
return new AwaitingJoinSession(); return new AwaitingJoinSession();
} }
@Command(name = "rejoin")
public AwaitingJoinSession rejoin(
@Parameters(index = "0", paramLabel = "<session>", description = "session uuid", completionCandidates = SessionCompleter.class) UUID session,
@Parameters(index = "1", paramLabel = "<player>", description = "player uuid") UUID player,
@Parameters(index = "2", paramLabel = "<secret>", description = "player secret") String secret
) {
client.send(new RejoinMessage(session, player, secret));
return new AwaitingJoinSession();
}
public static class SessionCompleter implements Iterable<String> { public static class SessionCompleter implements Iterable<String> {
private final Lobby lobby; private final Lobby lobby;

@ -22,6 +22,7 @@ import java.util.stream.Collectors;
public final class Game extends BaseState { public final class Game extends BaseState {
private final UUID self; private final UUID self;
private final UUID session; private final UUID session;
private final String secret;
private final Map<UUID, String> players; private final Map<UUID, String> players;
private final Map<UUID, Integer> scores = new HashMap<>(); private final Map<UUID, Integer> scores = new HashMap<>();
@ -37,9 +38,10 @@ public final class Game extends BaseState {
private Card trumpCard; private Card trumpCard;
private Card.Suit trumpSuit; private Card.Suit trumpSuit;
public Game(UUID self, UUID session, Map<UUID, String> players) { public Game(UUID self, UUID session, String secret, Map<UUID, String> players) {
this.self = self; this.self = self;
this.session = session; this.session = session;
this.secret = secret;
this.players = players; this.players = players;
} }
@ -185,6 +187,7 @@ public final class Game extends BaseState {
return Optional.of(new Session( return Optional.of(new Session(
self, self,
session, session,
secret,
false, false,
players.entrySet().stream() players.entrySet().stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(

@ -20,6 +20,7 @@ public final class Session extends BaseState {
private final UUID self; private final UUID self;
private final UUID session; private final UUID session;
private final String secret;
private final Map<UUID, PlayerData> players = new HashMap<>(); private final Map<UUID, PlayerData> players = new HashMap<>();
private boolean ready; private boolean ready;
@ -30,12 +31,14 @@ public final class Session extends BaseState {
this.self = joined.getPlayer(); this.self = joined.getPlayer();
this.session = joined.getSession(); this.session = joined.getSession();
this.ready = false; this.ready = false;
this.secret = joined.getSecret();
joined.getPlayers().forEach(player -> players.put(player.getUuid(), player)); joined.getPlayers().forEach(player -> players.put(player.getUuid(), player));
} }
public Session(UUID self, UUID session, boolean ready, Map<UUID, PlayerData> players) { public Session(UUID self, UUID session, String secret, boolean ready, Map<UUID, PlayerData> players) {
this.self = self; this.self = self;
this.session = session; this.session = session;
this.secret = secret;
this.players.putAll(players); this.players.putAll(players);
this.ready = ready; this.ready = ready;
} }
@ -47,6 +50,7 @@ public final class Session extends BaseState {
} else { } else {
client.printfln("Successfully joined session %s. There are %d other players.", session, players.size() - 1); 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); return super.onEnter(client);
} }
@ -70,6 +74,7 @@ public final class Session extends BaseState {
return Optional.of(new Game( return Optional.of(new Game(
self, self,
session, session,
secret,
players.values().stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName)) players.values().stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName))
)); ));
} else if (nextReady != null && message instanceof NackMessage nack) { } else if (nextReady != null && message instanceof NackMessage nack) {

@ -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_ALREADY_STARTED = 301;
public static final int GAME_NOT_YET_STARTED = 302; public static final int GAME_NOT_YET_STARTED = 302;
public static final int SESSION_FULL = 303; public static final int SESSION_FULL = 303;
public static final int ALREADY_CONNECTED = 304;
@Deprecated @Deprecated
public static final int BAD_REQUEST = 999; public static final int BAD_REQUEST = 999;

@ -9,6 +9,7 @@ import eu.jonahbauer.wizard.common.model.Configuration;
import eu.jonahbauer.wizard.core.machine.Game; import eu.jonahbauer.wizard.core.machine.Game;
import eu.jonahbauer.wizard.core.messages.Observer; import eu.jonahbauer.wizard.core.messages.Observer;
import eu.jonahbauer.wizard.core.model.Configurations; import eu.jonahbauer.wizard.core.model.Configurations;
import eu.jonahbauer.wizard.core.util.Pair;
import eu.jonahbauer.wizard.server.machine.Player; import eu.jonahbauer.wizard.server.machine.Player;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
@ -16,10 +17,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -40,6 +38,7 @@ public class Session implements Observer {
private final Map<UUID, SessionPlayer> players = new HashMap<>(); private final Map<UUID, SessionPlayer> players = new HashMap<>();
private Game game; private Game game;
private final List<Pair<UUID, ServerMessage>> messages = new ArrayList<>();
public Session(UUID uuid, String name, long timeout, Configuration configuration) { public Session(UUID uuid, String name, long timeout, Configuration configuration) {
@ -79,6 +78,34 @@ public class Session implements Observer {
return sessionPlayer.getUuid(); 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<UUID, ServerMessage> message : messages) {
if (message.getKey() == null || message.getKey().equals(uuid)) {
player.send(message.getValue());
}
}
return sessionPlayer.toData();
}
public synchronized void leave(UUID player) { public synchronized void leave(UUID player) {
if (game == null) { if (game == null) {
if (players.remove(player) != null) { if (players.remove(player) != null) {
@ -149,6 +176,7 @@ public class Session implements Observer {
private void startGame() { private void startGame() {
notifyPlayers(new StartingGameMessage()); notifyPlayers(new StartingGameMessage());
messages.add(Pair.of(null, new StartingGameMessage()));
game = new Game(Configurations.get(configuration).withTimeout(timeout), this); game = new Game(Configurations.get(configuration).withTimeout(timeout), this);
game.start(List.copyOf(players.keySet())); game.start(List.copyOf(players.keySet()));
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -166,6 +194,7 @@ public class Session implements Observer {
players.forEach((id, player) -> player.setReady(false)); players.forEach((id, player) -> player.setReady(false));
synchronized (this) { synchronized (this) {
game = null; game = null;
messages.clear();
for (SessionPlayer player : List.copyOf(players.values())) { for (SessionPlayer player : List.copyOf(players.values())) {
if (!player.isConnected()) { if (!player.isConnected()) {
leave(player.getUuid()); leave(player.getUuid());
@ -202,13 +231,17 @@ public class Session implements Observer {
} }
@Override @Override
public void notify(ObserverMessage message) { public synchronized void notify(ObserverMessage message) {
notifyPlayers(new GameMessage(message)); var gameMessage = new GameMessage(message);
notifyPlayers(gameMessage);
messages.add(Pair.of(null, gameMessage));
} }
@Override @Override
public void notify(UUID player, ObserverMessage message) { public synchronized void notify(UUID player, ObserverMessage message) {
players.get(player).send(new GameMessage(message)); var gameMessage = new GameMessage(message);
players.get(player).send(gameMessage);
messages.add(Pair.of(player, gameMessage));
} }
@Data @Data

@ -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.ClientMessage;
import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage; import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage;
import eu.jonahbauer.wizard.common.messages.client.JoinSessionMessage; 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.common.messages.server.NackMessage;
import eu.jonahbauer.wizard.server.Lobby; import eu.jonahbauer.wizard.server.Lobby;
import eu.jonahbauer.wizard.server.NackException; import eu.jonahbauer.wizard.server.NackException;
@ -51,6 +52,23 @@ public class LobbyState implements ClientState {
Lobby.getInstance().join(player); Lobby.getInstance().join(player);
return Optional.empty(); 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 { } else {
return ClientState.super.onMessage(player, message); return ClientState.super.onMessage(player, message);
} }

Loading…
Cancel
Save