package eric.Roullette.websocket; import com.fasterxml.jackson.databind.JsonNode; import eric.Roullette.service.GameService; //import eric.Roullette.service.SpotifyAuthService; import eric.Roullette.util.JsonUtil; import io.javalin.websocket.WsConfig; import io.javalin.websocket.WsContext; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * WebSocket-Handler für Spiel-Sessions. Verwaltet Spieler-Sessions, * broadcastet Spieler-Listen, Runden-Starts und Rundenergebnisse. */ public class GameWebSocketHandler { private final GameService service; //private final SpotifyAuthService authService; // Spiel-ID → (Username → deren Guess) private final Map> currentGuesses = new ConcurrentHashMap<>(); private final Map> trackInfoCache = new ConcurrentHashMap<>(); private final Map> allTracksCache = new ConcurrentHashMap<>(); //Map> private Map>> playerTracksCache = new ConcurrentHashMap<>(); private Map>> playerTrackInfoCache = new ConcurrentHashMap<>(); public GameWebSocketHandler(GameService gameService) { this.service = gameService; //this.authService = authService; } /** * Registriert Connect/Close/Message-Handler für eine WebSocket-Route. */ public void register(WsConfig ws) { // Neue Connection ws.onConnect(ctx -> { String gameId = ctx.pathParam("gameId"); String username = ctx.queryParam("username"); // Spiel- und Session-Registrierung service.addPlayer(gameId, username); // Alle Clients über neue Spielerliste informieren service.registerSession(gameId, ctx); service.broadcastPlayers(gameId); }); // Connection geschlossen ws.onClose(ctx -> { String gameId = ctx.pathParam("gameId"); service.removeSession(gameId, ctx); }); // Eingehende Nachrichten (Guesses & Player-Requests) ws.onMessage(ctx -> { String gameId = ctx.pathParam("gameId"); JsonNode node = JsonUtil.fromJson(ctx.message(), JsonNode.class); String type = node.get("type").asText(); switch (type) { case "guess" -> { String user = node.get("username").asText(); String guess = node.get("guess").asText(); // Guess speichern currentGuesses .computeIfAbsent(gameId, id -> new ConcurrentHashMap<>()) .put(user, guess); // Wenn alle getippt haben, Ergebnis broadcasten int numPlayers = service.getOrCreateGame(gameId).players().size(); if (currentGuesses.get(gameId).size() == numPlayers) { broadcastRoundResult(gameId); } } case "requestPlayers" -> service.broadcastPlayers(gameId); case "next-round" -> { nextround(gameId); } case "start-round" -> { var currentGame = service.getOrCreateGame(gameId); if (currentGame.players().isEmpty()) return; // Tracks pro Spieler sammeln Map> allPlayerTracks = currentGame.playerTracks(); // alle tracks sammeln List allTracks = allPlayerTracks.values().stream() .flatMap(List::stream) .toList(); System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundenstart)"); if (!allTracks.isEmpty()) { service.startRound(gameId, allTracks); } // Trackinfos für alle Spieler sammeln Map> allTrackInfos = service.getTrackInfos(allPlayerTracks); // Cache für Trackinfos pro Spiel-ID playerTrackInfoCache.put(gameId, allTrackInfos); System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + allTrackInfos.size() + " Spieler (rundenstart)"); broadcastRoundStart(gameId); } } }); } public void nextround(String gameId) { var game = service.getOrCreateGame(gameId); if (game.players().isEmpty()) return; // Songs von allen Spielern sammeln List allTracks = new ArrayList<>(); for (String player : game.players()) { allTracks.addAll(game.playerTracks().getOrDefault(player, List.of())); } if (allTracks.isEmpty()) { // TODO: Fehler an Client senden, dass keine Songs da sind return; } // TODO funktionalität bei neu joinenden Spielern überprüfen // Runde im Service starten, um Song und Owner zu setzen service.startRound(gameId, allTracks); // Jetzt Broadcast mit den aktuellen Daten broadcastRoundStart(gameId); } // ----- Broadcast-Methoden ----- /** Broadcastet den Runden-Start (Song + Optionen) an alle Clients. */ public void broadcastRoundStart(String gameId) { var game = service.getOrCreateGame(gameId); List opts = game.players(); String songUri = game.currentSong(); List allTracks = game.allTracks(); Map> trackInfos = playerTrackInfoCache.get(gameId); // Cache pro Spiel-ID nutzen String msg = JsonUtil.toJson(Map.of( "type", "round-start", "ownerOptions", opts, "songUri", songUri, "allTracks", allTracks, "trackInfos", trackInfos )); broadcastToAll(gameId, msg); } /** Broadcastet das Rundenergebnis (Scores, wer richtig, wer getippt hat). */ // Punkte für alle Guess-Teilnehmer anpassen private void broadcastRoundResult(String gameId) { var game = service.getOrCreateGame(gameId); Map scores = game.scores(); Map guesses = currentGuesses.remove(gameId); String owner = game.currentOwner(); // Für jeden Tippenden Score anpassen for (Map.Entry entry : guesses.entrySet()) { String guesser = entry.getKey(); boolean correct = owner.equals(entry.getValue()); scores.merge(guesser, correct ? 3 : -1, Integer::sum); } String msg = JsonUtil.toJson(Map.of( "type", "round-result", "scores", scores, "guesses", guesses, "owner", owner )); broadcastToAll(gameId, msg); // Prüfe auf Gewinner String winner = scores.entrySet().stream() .filter(e -> e.getValue() >= 30) .map(Map.Entry::getKey) .findFirst() .orElse(null); if (winner != null) { // Broadcast an alle, dass das Spiel vorbei ist String winMsg = JsonUtil.toJson(Map.of( "type", "game-end", "winner", winner, "scores", scores )); broadcastToAll(gameId, winMsg); game.scores().replaceAll((user , pts) -> 0); // Reset Scores für alle Spieler }else{ // nächste Runde starten // ... // new Thread(() -> { // try { Thread.sleep(2000); } catch (InterruptedException ignored) {} // nextround(gameId); // }).start(); } } /** Hilfsmethode: Sendet eine Nachricht an alle WebSocket-Sessions eines Spiels. */ private void broadcastToAll(String gameId, String msg) { // Holt alle WsContext, die der Service beim Connect registriert hat Set sessions = service.getSessions(gameId); if (sessions == null) return; sessions.stream() .filter(s -> s.session.isOpen()) .forEach(ctx -> { try { ctx.send(msg); } catch (Exception ignore) {} }); } }