SpotifyRoulette/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java

209 lines
8.2 KiB
Java

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<String, Map<String, String>> currentGuesses = new ConcurrentHashMap<>();
private final Map<String, List<String>> trackInfoCache = new ConcurrentHashMap<>();
private final Map<String, List<String>> allTracksCache = new ConcurrentHashMap<>();
//Map<gameId, Map<player, List<String>>
private Map<String, Map<String, List<String>>> playerTracksCache = new ConcurrentHashMap<>();
private Map<String, Map<String, List<String>>> 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<String, List<String>> allPlayerTracks = currentGame.playerTracks();
// alle tracks sammeln
List<String> 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<String, List<String>> 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<String> 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<String> opts = game.players();
String songUri = game.currentSong();
List<String> allTracks = game.allTracks();
Map<String, List<String>> 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<String,Integer> scores = game.scores();
Map<String,String> guesses = currentGuesses.remove(gameId);
String owner = game.currentOwner();
// Für jeden Tippenden Score anpassen
for (Map.Entry<String, String> 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<WsContext> sessions = service.getSessions(gameId);
if (sessions == null) return;
sessions.stream()
.filter(s -> s.session.isOpen())
.forEach(ctx -> {
try {
ctx.send(msg);
} catch (Exception ignore) {}
});
}
}