Darkmode beta

pull/24/head
eric 2025-08-08 04:07:12 +02:00
parent 6a067d0c1e
commit e9ea337ef9
6 changed files with 112 additions and 96 deletions

View File

@ -23,12 +23,13 @@ public class App {
if (cfg.spotifyClientId == null || cfg.spotifyClientSecret == null || cfg.spotifyRedirectUri == null) { if (cfg.spotifyClientId == null || cfg.spotifyClientSecret == null || cfg.spotifyRedirectUri == null) {
throw new IllegalStateException("Spotify-Konfiguration fehlt: Bitte stelle sicher, dass ClientId, ClientSecret und RedirectUri gesetzt sind."); throw new IllegalStateException("Spotify-Konfiguration fehlt: Bitte stelle sicher, dass ClientId, ClientSecret und RedirectUri gesetzt sind.");
} }
GameService gs = new GameService();
SpotifyAuthService sas = new SpotifyAuthService( SpotifyAuthService sas = new SpotifyAuthService(
cfg.spotifyClientId, cfg.spotifyClientId,
cfg.spotifyClientSecret, cfg.spotifyClientSecret,
cfg.spotifyRedirectUri cfg.spotifyRedirectUri
); );
GameService gs = new GameService(sas);
Javalin app = Javalin.create(config -> { Javalin app = Javalin.create(config -> {
config.showJavalinBanner = false; config.showJavalinBanner = false;

View File

@ -10,15 +10,19 @@ import java.util.concurrent.*;
public class GameService { public class GameService {
private static final Logger log = LoggerFactory.getLogger(GameService.class); private static final Logger log = LoggerFactory.getLogger(GameService.class);
private final SpotifyAuthService authService;
private final Map<String, Set<WsContext>> sessions = new ConcurrentHashMap<>(); private final Map<String, Set<WsContext>> sessions = new ConcurrentHashMap<>();
private final Map<String, Game> games = new ConcurrentHashMap<>(); private final Map<String, Game> games = new ConcurrentHashMap<>();
public record Game(String id, List<String> players, Map<String,Integer> scores,String currentOwner, public GameService(SpotifyAuthService authService) { // <-- Konstruktor
String currentSong,List<String> allTracks) { this.authService = authService;
public static Game create(String id) {
return new Game(id, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>());
} }
public record Game(String id, List<String> players, Map<String,Integer> scores, String currentOwner,
String currentSong, List<String> allTracks, Map<String, List<String>> playerTracks) {
public static Game create(String id) {
return new Game(id, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>(), new ConcurrentHashMap<>());
}
} }
public Game getOrCreateGame(String gameId) { public Game getOrCreateGame(String gameId) {
@ -30,6 +34,9 @@ public class GameService {
if (user != null && !g.players().contains(user)) { if (user != null && !g.players().contains(user)) {
g.players().add(user); g.players().add(user);
g.scores().putIfAbsent(user, 0); g.scores().putIfAbsent(user, 0);
// Songs einmalig laden und speichern
List<String> tracks = authService.getRecentTracks(user);
g.playerTracks().put(user, tracks);
} }
} }
@ -54,25 +61,25 @@ public class GameService {
} }
public void createGame(String gameId) { public void createGame(String gameId) {
Game game = new Game(gameId, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>()); Game game = new Game(gameId, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>(), new ConcurrentHashMap<>());
games.put(gameId, game); games.put(gameId, game);
} }
public boolean gameExists(String gameId) { public boolean gameExists(String gameId) {
return games.containsKey(gameId); return games.containsKey(gameId);
} }
public Game startRound(String gameId, List<String> uris) { public Game startRound(String gameId, List<String> uris) {
Game g = getOrCreateGame(gameId); Game g = getOrCreateGame(gameId);
if (g.players().isEmpty()) throw new IllegalStateException("No players"); if (g.players().isEmpty()) throw new IllegalStateException("No players");
String owner = g.players().get(ThreadLocalRandom.current().nextInt(g.players().size())); String owner = g.players().get(ThreadLocalRandom.current().nextInt(g.players().size()));
String song = uris.get(ThreadLocalRandom.current().nextInt(uris.size())); String song = uris.get(ThreadLocalRandom.current().nextInt(uris.size()));
Game updated = new Game(gameId, g.players(), g.scores(), owner, song, uris); Game updated = new Game(gameId, g.players(), g.scores(), owner, song, uris, g.playerTracks());
games.put(gameId, updated); games.put(gameId, updated);
return updated; return updated;
} }
// In GameService.java
public Set<WsContext> getSessions(String gameId) { public Set<WsContext> getSessions(String gameId) {
return sessions.getOrDefault(gameId, Collections.emptySet()); return sessions.getOrDefault(gameId, Collections.emptySet());
} }
} }

View File

@ -78,7 +78,7 @@ public class GameWebSocketHandler {
// Songs von allen Spielern sammeln // Songs von allen Spielern sammeln
List<String> allTracks = new ArrayList<>(); List<String> allTracks = new ArrayList<>();
for (String player : game.players()) { for (String player : game.players()) {
allTracks.addAll(authService.getRecentTracks(player)); allTracks.addAll(game.playerTracks().getOrDefault(player, List.of()));
} }
if (allTracks.isEmpty()) { if (allTracks.isEmpty()) {
// TODO: Fehler an Client senden, dass keine Songs da sind // TODO: Fehler an Client senden, dass keine Songs da sind
@ -90,6 +90,17 @@ public class GameWebSocketHandler {
// Jetzt Broadcast mit den aktuellen Daten // Jetzt Broadcast mit den aktuellen Daten
broadcastRoundStart(gameId); broadcastRoundStart(gameId);
} }
case "next-round" -> {
var currentGame = service.getOrCreateGame(gameId);
List<String> allTracks = new ArrayList<>();
for (String player : currentGame.players()) {
allTracks.addAll(authService.getRecentTracks(player));
}
if (!allTracks.isEmpty()) {
service.startRound(gameId, allTracks);
}
broadcastRoundStart(gameId);
}
} }
}); });
} }

View File

@ -7,12 +7,13 @@
<style> <style>
:root { :root {
--primary-color: #1DB954; --primary-color: #1DB954;
--bg-light: #f2f2f2; --bg-light: #121212; /* vorher: #f2f2f2 */
--bg-dark: #121212; --bg-dark: #000000; /* vorher: #121212 */
--text-light: #ffffff; --text-light: #eaeaea; /* vorher: #ffffff */
--text-dark: #333333; --text-dark: #eaeaea; /* vorher: #333333 */
--accent: #191414; --accent: #191414;
--option-circle-size: 250px; --option-circle-size: 80%;
--option-max-size: 400px;
--option-radius: 120px; --option-radius: 120px;
} }
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
@ -79,7 +80,8 @@
/* Rundenbereich */ /* Rundenbereich */
#roundArea { #roundArea {
margin-top: 1rem; margin-top: 1rem;
background: var(--text-light); background: var(--bg-dark);
color: var(--text-light);
padding: 0.7rem 1rem; padding: 0.7rem 1rem;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(0,0,0,0.1);
@ -139,6 +141,7 @@
<!-- Buttons zu Auswahl verteilen: JS erzeugt <button class="player-option">Name</button> --> <!-- Buttons zu Auswahl verteilen: JS erzeugt <button class="player-option">Name</button> -->
</div> </div>
<div id="result"></div> <div id="result"></div>
<button id="nextRound" hidden>Weiter</button>
</section> </section>
<section> <section>
<div class="section-title">Scoreboard</div> <div class="section-title">Scoreboard</div>

View File

@ -188,12 +188,18 @@ function handleRoundResult({ scores, guesses, owner }) {
resultP.appendChild(p); resultP.appendChild(p);
}); });
setTimeout(() => { const nextBtn = document.getElementById("nextRound");
nextBtn.hidden = false;
nextBtn.disabled = false;
nextBtn.onclick = () => {
socket.send(JSON.stringify({ type: "next-round" }));
nextBtn.hidden = true;
nextBtn.disabled = true;
resultP.textContent = ""; resultP.textContent = "";
startBtn.hidden = true; startBtn.hidden = true;
startBtn.disabled = true; startBtn.disabled = true;
roundArea.hidden = true; roundArea.hidden = true;
}, 3000); };
} }
function handleGameEnd({winner}) { function handleGameEnd({winner}) {

View File

@ -1,4 +1,4 @@
// src/test/java/eric/roulette/service/GameServiceTest.java // src/test/java/eric/Roullette/service/GameServiceTest.java
package eric.Roullette.service; package eric.Roullette.service;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -9,7 +9,9 @@ class GameServiceTest {
@Test @Test
void testGetOrCreateGame() { void testGetOrCreateGame() {
GameService service = new GameService(); // Dummy-Parameter für SpotifyAuthService
SpotifyAuthService sas = new SpotifyAuthService("dummy", "dummy", "http://localhost");
GameService service = new GameService(sas);
// Erstes Mal anlegen // Erstes Mal anlegen
GameService.Game g1 = service.getOrCreateGame("g1"); GameService.Game g1 = service.getOrCreateGame("g1");
assertNotNull(g1); assertNotNull(g1);
@ -18,18 +20,4 @@ class GameServiceTest {
GameService.Game g2 = service.getOrCreateGame("g1"); GameService.Game g2 = service.getOrCreateGame("g1");
assertSame(g1, g2); assertSame(g1, g2);
} }
// @Test
// void testAddPlayerAndScores() {
// GameService service = new GameService();
// service.getOrCreateGame("g2"); // Spiel anlegen
// service.addPlayer("g2", "Alice"); // Spieler hinzufügen
// GameService.Game game = service.getOrCreateGame("g2");
// // Spieler-Liste korrekt
// assertTrue(game.players().contains("Alice"));
// assertEquals(1, game.players().size());
// // Score für neuen Spieler initial 0
// assertEquals(0, game.scores().get("Alice").intValue());
// // Duplikate vermeiden
// }
} }