diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java index 369f229..0a374e7 100644 --- a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java @@ -5,7 +5,10 @@ import eu.jonahbauer.wizard.common.messages.server.ServerMessage; import eu.jonahbauer.wizard.common.messages.server.SessionCreatedMessage; import eu.jonahbauer.wizard.common.messages.server.SessionListMessage; import eu.jonahbauer.wizard.common.messages.server.SessionRemovedMessage; +import eu.jonahbauer.wizard.common.model.Configuration; +import eu.jonahbauer.wizard.server.debug.DebugSession; import eu.jonahbauer.wizard.server.machine.Player; +import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +47,22 @@ public class Lobby { } } + public DebugSession createDebugSession(@NotNull String name, long timeout, @NotNull Configuration configuration) { + lock.readLock().lock(); + try { + DebugSession session; + do { + session = new DebugSession(UUID.randomUUID(), name, timeout, configuration); + } while (sessions.putIfAbsent(session.getUuid(), session) != null); + + notifyPlayers(new SessionCreatedMessage(session.toData())); + + return session; + } finally { + lock.readLock().unlock(); + } + } + public Session getSession(UUID uuid) { return sessions.get(uuid); } 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 046923a..97c548e 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 @@ -26,15 +26,15 @@ import java.util.concurrent.ThreadLocalRandom; @Log4j2 @EqualsAndHashCode(of = "uuid") public class Session implements Observer { - private static final int MIN_PLAYERS = 3; - private static final int MAX_PLAYERS = 6; + protected static final int MIN_PLAYERS = 3; + protected static final int MAX_PLAYERS = 6; private final UUID uuid; private final String name; private final long timeout; private final Configuration configuration; - @Getter(AccessLevel.NONE) + @Getter(AccessLevel.PROTECTED) private final Map players = new LinkedHashMap<>(); private Game game; @@ -174,7 +174,7 @@ public class Session implements Observer { } } - private void startGame() { + protected void startGame() { notifyPlayers(new StartingGameMessage()); messages.add(Pair.of(null, new StartingGameMessage())); game = new Game(Configurations.get(configuration).withTimeout(timeout), this); @@ -204,7 +204,7 @@ public class Session implements Observer { }); } - private void notifyJoined(PlayerData joined) { + protected void notifyJoined(PlayerData joined) { var message = new PlayerJoinedMessage(joined); for (SessionPlayer player : players.values()) { if (player.getUuid().equals(joined.getUuid())) { @@ -220,7 +220,7 @@ public class Session implements Observer { } } - private void notifyPlayers(ServerMessage message) { + protected void notifyPlayers(ServerMessage message) { for (SessionPlayer player : players.values()) { player.send(message); } @@ -246,7 +246,7 @@ public class Session implements Observer { @Data @EqualsAndHashCode(of = "uuid") - private static class SessionPlayer { + protected static class SessionPlayer { private final UUID uuid; private final String name; private final String secret = generateSecret(); diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSession.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSession.java new file mode 100644 index 0000000..27ae085 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSession.java @@ -0,0 +1,75 @@ +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 { + public DebugSession(UUID uuid, String name, long timeout, Configuration configuration) { + super(uuid, name, timeout, configuration); + + var dummy1 = new SessionPlayer(new UUID(0, 1), "Dummy #1"); + var dummy2 = new SessionPlayer(new UUID(0, 2), "Dummy #2"); + var dummy3 = new SessionPlayer(new UUID(0, 3), "Dummy #3"); + var dummy4 = new SessionPlayer(new UUID(0, 4), "Dummy #4"); + getPlayers().put(dummy1.getUuid(), dummy1); + getPlayers().put(dummy2.getUuid(), dummy2); + getPlayers().put(dummy3.getUuid(), dummy3); + getPlayers().put(dummy4.getUuid(), dummy4); + } + + @Override + public synchronized void handlePlayerMessage(UUID uuid, PlayerMessage message) { + var player = getPlayers().get(uuid); + if (player == null) { + throw new NackException(NackMessage.PLAYER_NOT_FOUND, "Who are you?"); + } + } + + public synchronized UUID join(Player player, String name) { + if (getGame() != null) { + throw new NackException(NackMessage.GAME_ALREADY_STARTED, "Game has already started."); + } else if (getPlayers().size() == MAX_PLAYERS) { + throw new NackException(NackMessage.SESSION_FULL, "Session is full."); + } else if (getPlayers().values().stream().anyMatch(p -> p.getName().equalsIgnoreCase(name))) { + throw new NackException(NackMessage.NAME_TAKEN, "Name is already taken."); + } + + SessionPlayer sessionPlayer; + long i = 0; + do { + sessionPlayer = new SessionPlayer(new UUID(0, i++), name); + } while (getPlayers().putIfAbsent(sessionPlayer.getUuid(), sessionPlayer) != null); + sessionPlayer.setPlayer(player); + + notifyJoined(sessionPlayer.toData()); + Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData())); + + return sessionPlayer.getUuid(); + } + + @Override + protected void startGame() {} + + public void close() { + for (var player : getPlayers().values()) { + try { + player.getPlayer().disconnect(); + } catch (IOException ignored) {} + } + } + + @Override + public void notifyPlayers(ServerMessage message) { + super.notifyPlayers(message); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSocketHandler.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSocketHandler.java new file mode 100644 index 0000000..d74d579 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugSocketHandler.java @@ -0,0 +1,61 @@ +package eu.jonahbauer.wizard.server.debug; + +import eu.jonahbauer.wizard.common.messages.ParseException; +import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage; +import eu.jonahbauer.wizard.common.messages.server.NackMessage; +import eu.jonahbauer.wizard.common.messages.server.ServerMessage; +import eu.jonahbauer.wizard.common.model.Configuration; +import eu.jonahbauer.wizard.server.Lobby; +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +@Component +@Profile("debug") +public class DebugSocketHandler extends TextWebSocketHandler { + private final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(@NotNull WebSocketSession session) { + var dummySession = Lobby.getInstance().createDebugSession("Debug Session", 10, Configuration.DEFAULT); + sessions.put(session.getId(), dummySession); + + log.info("Dummy connection #{} from {}.", session.getId(), session.getRemoteAddress()); + } + + @Override + protected void handleTextMessage(@NotNull WebSocketSession session, @NotNull TextMessage text) throws IOException { + var dummySession = sessions.get(session.getId()); + for (String line : text.getPayload().lines().toArray(String[]::new)) { + try { + dummySession.notify(ObserverMessage.parse(line)); + } catch (ParseException e) { + try { + dummySession.notifyPlayers(ServerMessage.parse(line)); + } catch (ParseException e2) { + var nack = new NackMessage(NackMessage.MALFORMED_MESSAGE,"Could not parse " + text.getPayload() + ".").toString(); + session.sendMessage(new TextMessage(nack)); + } + } + } + } + + @Override + public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) { + var dummySession = sessions.get(session.getId()); + dummySession.close(); + Lobby.getInstance().removeSession(dummySession.getUuid()); + + log.info("Dummy connection #{} closed {}.", session.getId(), status); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugWebSocketConfig.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugWebSocketConfig.java new file mode 100644 index 0000000..f4583f9 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/debug/DebugWebSocketConfig.java @@ -0,0 +1,26 @@ +package eu.jonahbauer.wizard.server.debug; + +import lombok.AccessLevel; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@Profile("debug") +public class DebugWebSocketConfig implements WebSocketConfigurer { + + @Getter(AccessLevel.PRIVATE) + private final DebugSocketHandler debugSocketHandler; + + public DebugWebSocketConfig(DebugSocketHandler debugSocketHandler) { + this.debugSocketHandler = debugSocketHandler; + } + + @Override + public void registerWebSocketHandlers(@NotNull WebSocketHandlerRegistry registry) { + registry.addHandler(this.getDebugSocketHandler(), "/debug").setAllowedOrigins("*"); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java index f4d82bf..2915ae0 100644 --- a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java @@ -10,6 +10,7 @@ import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; +import java.io.IOException; import java.util.ArrayList; public class Player extends Context { @@ -43,6 +44,10 @@ public class Player extends Context { shouldBuffer = Response.class; } + public void disconnect() throws IOException { + session.close(CloseStatus.SERVER_ERROR); + } + @SneakyThrows public void send(ServerMessage message) { synchronized (session) {