From 9c64f0397a09418715b35a2bcb7fee7ef5940c72 Mon Sep 17 00:00:00 2001 From: Alexander Hirmer Date: Tue, 23 Nov 2021 16:00:14 +0100 Subject: [PATCH] Ticket #14 --- .../common/messages/server/NackMessage.java | 11 +- wizard-server/build.gradle.kts | 11 ++ .../eu/jonahbauer/wizard/server/Lobby.java | 90 +++++++++++ .../wizard/server/NackException.java | 22 +++ .../eu/jonahbauer/wizard/server/Server.java | 12 ++ .../eu/jonahbauer/wizard/server/Session.java | 142 ++++++++++++++++++ .../wizard/server/machine/ClientState.java | 21 +++ .../wizard/server/machine/Player.java | 43 ++++++ .../wizard/server/machine/states/Closed.java | 14 ++ .../server/machine/states/CreatedState.java | 19 +++ .../wizard/server/machine/states/InGame.java | 46 ++++++ .../server/machine/states/LobbyState.java | 51 +++++++ .../server/machine/states/SessionState.java | 55 +++++++ .../wizard/server/socket/WebSocketConfig.java | 26 ++++ .../server/socket/WizardSocketHandler.java | 40 +++++ 15 files changed, 601 insertions(+), 2 deletions(-) create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/NackException.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/Server.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/ClientState.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/Closed.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/CreatedState.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/InGame.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/SessionState.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WebSocketConfig.java create mode 100644 wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WizardSocketHandler.java 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 9eebf94..6e52ac6 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 @@ -2,12 +2,19 @@ package eu.jonahbauer.wizard.common.messages.server; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import org.intellij.lang.annotations.MagicConstant; @Getter -@RequiredArgsConstructor @EqualsAndHashCode(callSuper = true) public final class NackMessage extends ServerMessage implements Response { + public static final int BAD_REQUEST = 400; + public static final int NOT_FOUND = 404; + private final int code; private final String message; + + public NackMessage(@MagicConstant(valuesFromClass = NackMessage.class) int code, String message) { + this.code = code; + this.message = message; + } } diff --git a/wizard-server/build.gradle.kts b/wizard-server/build.gradle.kts index 871379d..128a8dc 100644 --- a/wizard-server/build.gradle.kts +++ b/wizard-server/build.gradle.kts @@ -1,4 +1,15 @@ +plugins { + id("org.springframework.boot").version("2.5.6") + //id("io.spring.dependency-management").version("1.0.7-RELEASE") + id("java") +} + +repositories { + mavenCentral() +} dependencies { implementation(project(":wizard-core")) + implementation(project(":wizard-common")) + implementation("org.springframework.boot:spring-boot-starter-websocket:2.5.6") } 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 new file mode 100644 index 0000000..369f229 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Lobby.java @@ -0,0 +1,90 @@ +package eu.jonahbauer.wizard.server; + +import eu.jonahbauer.wizard.common.messages.client.CreateSessionMessage; +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.server.machine.Player; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class Lobby { + private static final Lobby INSTANCE = new Lobby(); + public static Lobby getInstance() { + return INSTANCE; + } + + private final Map sessions = new ConcurrentHashMap<>(); + private final List players = new ArrayList<>(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + private Lobby() {} + + public Session createSession(CreateSessionMessage create) { + lock.readLock().lock(); + try { + Session session; + do { + session = new Session( + UUID.randomUUID(), + create.getSessionName(), + create.getTimeout(), + create.getConfiguration() + ); + } 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); + } + + public void removeSession(UUID uuid) { + lock.readLock().lock(); + try { + sessions.remove(uuid); + notifyPlayers(new SessionRemovedMessage(uuid)); + } finally { + lock.readLock().unlock(); + } + } + + public void join(Player player) { + lock.writeLock().lock(); + try { + players.add(player); + player.send(new SessionListMessage(sessions.values().stream().map(Session::toData).toList())); + } finally { + lock.writeLock().unlock(); + } + } + + public void leave(Player player) { + lock.writeLock().lock(); + try { + players.remove(player); + } finally { + lock.writeLock().unlock(); + } + } + + public void notifyPlayers(ServerMessage message) { + lock.readLock().lock(); + try { + for (Player player : players) { + player.send(message); + } + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/NackException.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/NackException.java new file mode 100644 index 0000000..a60bcc0 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/NackException.java @@ -0,0 +1,22 @@ +package eu.jonahbauer.wizard.server; + +import eu.jonahbauer.wizard.common.messages.server.NackMessage; +import org.intellij.lang.annotations.MagicConstant; + +public class NackException extends RuntimeException { + private final int code; + + public NackException(@MagicConstant(valuesFromClass = NackMessage.class) int code, String message) { + super(message); + this.code = code; + } + + @MagicConstant(valuesFromClass = NackMessage.class) + public int getCode() { + return code; + } + + public NackMessage toMessage() { + return new NackMessage(code, getMessage()); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Server.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Server.java new file mode 100644 index 0000000..ab549ee --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Server.java @@ -0,0 +1,12 @@ +package eu.jonahbauer.wizard.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Server { + + public static void main(String[] args) { + SpringApplication.run(Server.class, args); + } +} 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 new file mode 100644 index 0000000..296bbdf --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/Session.java @@ -0,0 +1,142 @@ +package eu.jonahbauer.wizard.server; + +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 eu.jonahbauer.wizard.server.machine.Player; +import lombok.AccessLevel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +@Getter +@EqualsAndHashCode(of = "uuid") +public class Session { + private static final int MIN_PLAYERS = 3; + private 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) + private final Map players = new HashMap<>(); + + + public Session(UUID uuid, String name, long timeout, Configuration configuration) { + this.uuid = uuid; + this.name = name; + this.timeout = timeout; + this.configuration = configuration; + } + + /** + * Associates the given player with this session, removes him from the lobby, notifies all other players in the + * session with a {@link PlayerJoinedMessage}, the joining player with a {@link SessionJoinedMessage} and all + * players in the lobby with a {@link SessionModifiedMessage}. + * + * @param player the player + * @param name the players chosen name + * @return the players uuid + */ + public synchronized UUID join(Player player, String name) { + if (players.size() == MAX_PLAYERS) { + throw new NackException(NackMessage.BAD_REQUEST, "Session is full."); + } else if (players.values().stream().anyMatch(p -> p.getName().equalsIgnoreCase(name))) { + throw new NackException(NackMessage.BAD_REQUEST, "Name is already taken."); + } + + Lobby.getInstance().leave(player); + + SessionPlayer sessionPlayer; + do { + sessionPlayer = new SessionPlayer(UUID.randomUUID(), name, player); + } while (players.putIfAbsent(sessionPlayer.getUuid(), sessionPlayer) != null); + + notifyJoined(sessionPlayer.toData()); + Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData())); + + return sessionPlayer.getUuid(); + } + + public synchronized void leave(UUID player) { + if (players.remove(player) != null) { + if (players.size() == 0) { + Lobby.getInstance().removeSession(uuid); + } else { + notifyPlayers(new PlayerLeftMessage(player)); + Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData())); + } + } + } + + public synchronized void ready(UUID player, boolean ready) { + var sessionPlayer = players.get(player); + if (sessionPlayer == null) { + throw new NackException(NackMessage.BAD_REQUEST, "Who are you?"); + } + + if (sessionPlayer.isReady() != ready) { + sessionPlayer.setReady(ready); + notifyPlayers(new PlayerModifiedMessage(sessionPlayer.toData())); + } + + if (players.size() >= MIN_PLAYERS && players.values().stream().allMatch(SessionPlayer::isReady)) { + // TODO start game + } + } + + private void notifyJoined(PlayerData joined) { + var message = new PlayerJoinedMessage(joined); + for (SessionPlayer player : players.values()) { + if (player.getUuid().equals(joined.getUuid())) { + player.getPlayer().send(new SessionJoinedMessage( + getUuid(), + player.getUuid(), + players.values().stream().map(SessionPlayer::toData).toList(), + player.getSecret() + )); + } else { + player.getPlayer().send(message); + } + } + } + + private void notifyPlayers(ServerMessage message) { + for (SessionPlayer player : players.values()) { + player.getPlayer().send(message); + } + } + + public SessionData toData() { + return new SessionData(uuid, name, players.size(), configuration); + } + + @Data + @EqualsAndHashCode(of = "uuid") + private static class SessionPlayer { + private final UUID uuid; + private final String name; + private final Player player; + private final String secret = generateSecret(); + private boolean ready; + + private static String generateSecret() { + return ThreadLocalRandom.current() + .ints(32, 'a', 'z' + 1) + .collect(StringBuilder::new, (sb, i) -> sb.append((char) i), StringBuilder::append) + .toString(); + } + + public PlayerData toData() { + return new PlayerData(uuid, name, ready); + } + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/ClientState.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/ClientState.java new file mode 100644 index 0000000..7fa4c36 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/ClientState.java @@ -0,0 +1,21 @@ +package eu.jonahbauer.wizard.server.machine; + +import eu.jonahbauer.wizard.common.machine.State; +import eu.jonahbauer.wizard.common.messages.client.ClientMessage; +import eu.jonahbauer.wizard.common.messages.server.NackMessage; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; + +public interface ClientState extends State { + default Optional onOpen(Player player) { + throw new IllegalStateException(); // TODO nachdenken + } + + default Optional onMessage(Player player, ClientMessage message) { + player.send(new NackMessage(NackMessage.BAD_REQUEST, "Unexpected message.")); + return Optional.empty(); + } + + Optional onClose(Player player, CloseStatus status); +} 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 new file mode 100644 index 0000000..c732945 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/Player.java @@ -0,0 +1,43 @@ +package eu.jonahbauer.wizard.server.machine; + +import eu.jonahbauer.wizard.common.machine.Context; +import eu.jonahbauer.wizard.common.messages.client.ClientMessage; +import eu.jonahbauer.wizard.common.messages.server.ServerMessage; +import eu.jonahbauer.wizard.server.machine.states.CreatedState; +import lombok.SneakyThrows; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +public class Player extends Context { + private final WebSocketSession session; + + public Player(WebSocketSession session) { + super(new CreatedState()); + this.session = session; + } + + @Override + protected void handleError(Throwable t) { + + } + + public void onOpen() { + execute(s -> s.onOpen(this)); + } + + public void onMessage(ClientMessage message) { + execute(s -> s.onMessage(this, message)); + } + + public void onClose(CloseStatus status) { + execute(s -> s.onClose(this, status)); + } + + @SneakyThrows + public void send(ServerMessage message) { + synchronized (session) { + session.sendMessage(new TextMessage(message.toString())); + } + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/Closed.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/Closed.java new file mode 100644 index 0000000..5f3d46d --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/Closed.java @@ -0,0 +1,14 @@ +package eu.jonahbauer.wizard.server.machine.states; + +import eu.jonahbauer.wizard.server.machine.ClientState; +import eu.jonahbauer.wizard.server.machine.Player; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; + +public class Closed implements ClientState { + @Override + public Optional onClose(Player player, CloseStatus status) { + return Optional.empty(); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/CreatedState.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/CreatedState.java new file mode 100644 index 0000000..900c484 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/CreatedState.java @@ -0,0 +1,19 @@ +package eu.jonahbauer.wizard.server.machine.states; + +import eu.jonahbauer.wizard.server.machine.ClientState; +import eu.jonahbauer.wizard.server.machine.Player; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; + +public class CreatedState implements ClientState { + @Override + public Optional onOpen(Player player) { + return Optional.of(new LobbyState()); + } + + @Override + public Optional onClose(Player player, CloseStatus status) { + throw new IllegalStateException(); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/InGame.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/InGame.java new file mode 100644 index 0000000..40db728 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/InGame.java @@ -0,0 +1,46 @@ +package eu.jonahbauer.wizard.server.machine.states; + +import eu.jonahbauer.wizard.common.messages.client.ClientMessage; +import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage; +import eu.jonahbauer.wizard.common.messages.server.NackMessage; +import eu.jonahbauer.wizard.common.messages.server.StartingGameMessage; +import eu.jonahbauer.wizard.server.machine.ClientState; +import eu.jonahbauer.wizard.server.machine.Player; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; + +public class InGame implements ClientState { + @Override + public Optional onEnter(Player context) { + context.send(new StartingGameMessage()); + + return ClientState.super.onEnter(context); + } + + @Override + public void onExit(Player context) { + ClientState.super.onExit(context); + } + + @Override + public Optional onOpen(Player player) { + return Optional.empty(); + } + + @Override + public Optional onMessage(Player player, ClientMessage message) { + if(message instanceof LeaveSessionMessage) { + //? + return Optional.empty(); + } else { + player.send(new NackMessage(0, "Error: Invalid Message!")); + return Optional.empty(); + } + } + + @Override + public Optional onClose(Player player, CloseStatus status) { + return Optional.empty(); + } +} 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 new file mode 100644 index 0000000..fd0879b --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/LobbyState.java @@ -0,0 +1,51 @@ +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.server.NackMessage; +import eu.jonahbauer.wizard.server.Lobby; +import eu.jonahbauer.wizard.server.NackException; +import eu.jonahbauer.wizard.server.machine.ClientState; +import eu.jonahbauer.wizard.server.machine.Player; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; + +public class LobbyState implements ClientState { + @Override + public Optional onEnter(Player player) { + Lobby.getInstance().join(player); + return Optional.empty(); + } + + @Override + public Optional onMessage(Player player, ClientMessage message) { + if (message instanceof CreateSessionMessage create) { + Lobby.getInstance().leave(player); + var session = Lobby.getInstance().createSession(create); + var uuid = session.join(player, create.getPlayerName()); + return Optional.of(new SessionState(session.getUuid(), uuid, create.getPlayerName())); + } else if (message instanceof JoinSessionMessage join) { + var session = Lobby.getInstance().getSession(join.getSession()); + if (session == null) { + throw new NackException(NackMessage.NOT_FOUND, "Session not found."); + } else { + var uuid = session.join(player, join.getPlayerName()); + return Optional.of(new SessionState(session.getUuid(), uuid, join.getPlayerName())); + } + } else { + return ClientState.super.onMessage(player, message); + } + } + + @Override + public Optional onClose(Player player, CloseStatus status) { + return Optional.of(new Closed()); + } + + @Override + public void onExit(Player player) { + Lobby.getInstance().leave(player); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/SessionState.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/SessionState.java new file mode 100644 index 0000000..bf9b328 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/machine/states/SessionState.java @@ -0,0 +1,55 @@ +package eu.jonahbauer.wizard.server.machine.states; + +import eu.jonahbauer.wizard.common.messages.client.ClientMessage; +import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage; +import eu.jonahbauer.wizard.common.messages.client.ReadyMessage; +import eu.jonahbauer.wizard.common.messages.server.NackMessage; +import eu.jonahbauer.wizard.server.Lobby; +import eu.jonahbauer.wizard.server.machine.ClientState; +import eu.jonahbauer.wizard.server.machine.Player; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.web.socket.CloseStatus; + +import java.util.Optional; +import java.util.UUID; + +@Getter +@RequiredArgsConstructor +public class SessionState implements ClientState { + private final UUID session; + private final UUID self; + private final String name; + private boolean ready; + + @Override + public Optional onOpen(Player player) { + throw new IllegalStateException(); + } + + @Override + public Optional onMessage(Player player, ClientMessage message) { + if (message instanceof ReadyMessage ready) { + Lobby.getInstance().getSession(session).ready(self, ready.isReady()); + this.ready = ready.isReady(); + return Optional.empty(); + } else if (message instanceof LeaveSessionMessage) { + Lobby.getInstance().getSession(session).leave(self); + return Optional.of(new LobbyState()); + } else { + player.send(new NackMessage(0, "Error: Invalid Message!")); + return Optional.empty(); + } + } + + @Override + public Optional onClose(Player player, CloseStatus status) { + return Optional.of(new Closed()); + } + + @Override + public void onExit(Player context) { + var session = Lobby.getInstance().getSession(this.session); + if (session != null) session.leave(self); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WebSocketConfig.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WebSocketConfig.java new file mode 100644 index 0000000..68eea83 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WebSocketConfig.java @@ -0,0 +1,26 @@ +package eu.jonahbauer.wizard.server.socket; + +import lombok.AccessLevel; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Getter(AccessLevel.PRIVATE) + private final WizardSocketHandler wizardSocketHandler; + + public WebSocketConfig(WizardSocketHandler wizardSocketHandler) { + this.wizardSocketHandler = wizardSocketHandler; + } + + @Override + public void registerWebSocketHandlers(@NotNull WebSocketHandlerRegistry registry) { + registry.addHandler(getWizardSocketHandler(), "/").setAllowedOrigins("*"); + } +} diff --git a/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WizardSocketHandler.java b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WizardSocketHandler.java new file mode 100644 index 0000000..42c1176 --- /dev/null +++ b/wizard-server/src/main/java/eu/jonahbauer/wizard/server/socket/WizardSocketHandler.java @@ -0,0 +1,40 @@ +package eu.jonahbauer.wizard.server.socket; + +import eu.jonahbauer.wizard.common.messages.client.ClientMessage; +import eu.jonahbauer.wizard.server.machine.Player; +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +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.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +@Component +public class WizardSocketHandler extends TextWebSocketHandler { + private final Map players = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(@NotNull WebSocketSession session) { + var player = new Player(session); + player.onOpen(); + + players.put(session.getId(), player); + log.info("Connection #{} from {}.", session.getId(), session.getRemoteAddress()); + } + + @Override + protected void handleTextMessage(@NotNull WebSocketSession session, @NotNull TextMessage text) { + players.get(session.getId()).onMessage(ClientMessage.parse(text.getPayload())); + } + + @Override + public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) { + players.get(session.getId()).onClose(status); + log.info("Connection #{} closed {}.", session.getId(), status); + } +}