diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index ec4cdf8..45b5e16 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -185,10 +185,10 @@ private void broadcastRoundResult(String gameId) { }else{ // nächste Runde starten // ... - new Thread(() -> { - try { Thread.sleep(2000); } catch (InterruptedException ignored) {} - nextround(gameId); - }).start(); +// new Thread(() -> { +// try { Thread.sleep(2000); } catch (InterruptedException ignored) {} +// nextround(gameId); +// }).start(); } } diff --git a/src/main/resources/public/create-game.html b/src/main/resources/public/create-game.html index 8d64cd6..f99bfe5 100644 --- a/src/main/resources/public/create-game.html +++ b/src/main/resources/public/create-game.html @@ -1,12 +1,107 @@ - - Spiel erstellen – Spotify Roulette + + + Spiel erstellen – Spotify Roulette + + + + -

Neues Spiel erstellen

- +
+
+ +

Neues Spiel erstellen

+ Angemeldet als +
+ +
+
+

Spiel starten

+
+
+ +
+ Standard-Spiel + Erstellt eine Lobby mit Standardregeln. +
+
+ +

Du bekommst einen Spiel‑Code zum Teilen.

+
+
+ +
+

WIP Einstellungen

+
+
+
BaldMax. Spieler • Rundenanzahl • Song‑Ausschnitt • Privat/Öffentlich
+
Noch in arbeit
+
+
+
+
+
+ + + diff --git a/src/main/resources/public/game.html b/src/main/resources/public/game.html index e27eafc..2c4e32a 100644 --- a/src/main/resources/public/game.html +++ b/src/main/resources/public/game.html @@ -1,162 +1,228 @@ - - + + Spotify Roulette – Spiel + + + + + + -
-

Spotify Roulette

-
Spiel-Code:
-
-
-
-
Teilnehmer
- -
-
- -
- -
-
Scoreboard
- -
-
- + + + + - + + +
+ + + - diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html index d2dcc5d..af9e34a 100644 --- a/src/main/resources/public/index.html +++ b/src/main/resources/public/index.html @@ -1,17 +1,77 @@ - - Spotify – Zuletzt gehörte Songs + + + Spotify Roulette – Login + + + + -

Spotify – Zuletzt gehörte Songs

-
- - - -
- +
+
+ +

Spotify Roulette

+ Status: Abgemeldet +
+ +
+
+
+ + +
+ +
+
+

Du wirst zu Spotify weitergeleitet, um die Berechtigungen zu bestätigen.

+
+
+
diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js index a9e7505..ee93bbb 100644 --- a/src/main/resources/public/js/game.js +++ b/src/main/resources/public/js/game.js @@ -6,17 +6,31 @@ import { setupStartRound } from "./start-round.js"; const gameId = getParam("gameId"); const username = getParam("username"); +// --- kleine Helper --- +function showToast(msg, ms = 2200) { + const t = document.getElementById("toast"); + if (!t) return; + t.textContent = msg; + t.classList.add("show"); + setTimeout(() => t.classList.remove("show"), ms); +} +function esc(s) { + const d = document.createElement("div"); + d.textContent = String(s ?? ""); + return d.innerHTML; +} + // 1) Parameter prüfen if (!gameId || !username) { alert("Ungültige oder fehlende URL-Parameter!"); throw new Error("Missing gameId or username"); } -// 2) Copy to clipboard (unverändert) +// 2) Copy to clipboard (unverändert, aber mit Toast) (function copyCodeToClipboard(code) { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(code) - .then(() => console.log(`GameCode ${code} copied to clipboard`)) + .then(() => showToast(`Spiel-Code ${code} kopiert`)) .catch(err => console.error("Clipboard write failed:", err)); } else { const ta = document.createElement("textarea"); @@ -28,7 +42,7 @@ if (!gameId || !username) { ta.select(); try { document.execCommand("copy"); - console.log(`GameCode ${code} copied via execCommand`); + showToast(`Spiel-Code ${code} kopiert`); } catch (err) { console.error("Fallback copy failed:", err); } @@ -36,16 +50,14 @@ if (!gameId || !username) { } })(gameId); -// 3) Visuelles Feedback (unverändert) -const notice = document.createElement("div"); -notice.textContent = `Spiel-Code ${gameId} in die Zwischenablage kopiert!`; -Object.assign(notice.style, { /* … Styles … */ }); -document.body.append(notice); -setTimeout(() => notice.remove(), 3000); - -// 4) Spiel-Code ins DOM schreiben +// 3) Spiel-Code ins DOM schreiben document.getElementById("gameId").textContent = gameId; +// 4) Drawer toggles für Songliste +const drawer = document.getElementById("songDrawer"); +document.getElementById("toggleSongList")?.addEventListener("click", () => drawer?.classList.toggle("open")); +document.getElementById("closeDrawer")?.addEventListener("click", () => drawer?.classList.remove("open")); + // 5) WebSocket mit Reconnect-Logik let socket; function connectWebSocket() { @@ -57,7 +69,7 @@ function connectWebSocket() { socket.addEventListener("open", () => { console.log("WebSocket connected. Requesting player list..."); socket.send(JSON.stringify({ type: "requestPlayers" })); - setupStartRound(socket); + setupStartRound(socket); // deaktiviert den Start-Button beim Klicken }); socket.addEventListener("message", async ({ data }) => { @@ -66,7 +78,6 @@ function connectWebSocket() { switch (msg.type) { case "players": - console.log("Empfangene Spieler:", msg.players); renderList("#playersList", msg.players, username); break; case "reload": @@ -106,13 +117,25 @@ const optionsDiv = document.getElementById("options"); const resultP = document.getElementById("result"); const scoreboard = document.getElementById("scoreboard"); -// 8) Funktion zum Anzeigen einer neuen Runde -//let playLock = false; +// --- SVG helpers für Pie-Slices --- +const SVGNS = "http://www.w3.org/2000/svg"; +const VB = 1000; // viewBox 0..1000 +const CX = 500, CY = 500, R = 480; -async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos}) { +function polar(cx, cy, r, deg){ + const rad = (deg - 90) * Math.PI/180; // 0° oben + return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; +} +function wedgePath(cx, cy, r, a0, a1){ + const p0 = polar(cx, cy, r, a0); + const p1 = polar(cx, cy, r, a1); + const largeArc = ((a1 - a0 + 360) % 360) > 180 ? 1 : 0; + return `M ${cx} ${cy} L ${p0.x} ${p0.y} A ${r} ${r} 0 ${largeArc} 1 ${p1.x} ${p1.y} Z`; +} + +// 6) Neue Runde anzeigen +async function handleRoundStart({ ownerOptions, songUri, trackInfos }) { // UI zurücksetzen - //if (playLock) return; // Verhindert parallele Ausführung - //playLock = true; resultP.textContent = ""; optionsDiv.innerHTML = ""; songEmbed.innerHTML = ""; @@ -120,60 +143,103 @@ async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos}) // Song einbetten const trackId = songUri.split(":")[2]; songEmbed.innerHTML = ` - `; + `; + // Spotify-Playback (optional, wenn vorhanden) if (window.playOnSpotify && typeof window.playOnSpotify === "function") { - await window.playOnSpotify(songUri, username); // Warten bis fertig + await window.playOnSpotify(songUri, username); } - //if (window.playOnSpotify && typeof window.playOnSpotify === "function") { - // window.playOnSpotify(songUri, username); - //} + // Optionen als Tortenstücke (SVG) rendern + const svg = document.createElementNS(SVGNS, "svg"); + svg.setAttribute("viewBox", `0 0 ${VB} ${VB}`); + svg.setAttribute("class", "options-svg"); + optionsDiv.innerHTML = ""; + optionsDiv.appendChild(svg); - // Dynamische Kreisverteilung der Buttons - // Warten, bis #options gerendert ist - setTimeout(() => { - const optsRect = optionsDiv.getBoundingClientRect(); - const radius = Math.min(optsRect.width, optsRect.height) / 2 - 50; // 50px Abstand zum Rand + const n = ownerOptions.length; + ownerOptions.forEach((user, i) => { + const a0 = (360 / n) * i; + const a1 = (360 / n) * (i + 1); - ownerOptions.forEach((user, i) => { - const btn = document.createElement("button"); - btn.textContent = user; - btn.classList.add("player-option"); - const angle = 360 * i / ownerOptions.length; - btn.style.transform = `rotate(${angle}deg) translateY(-${radius}px) rotate(${-angle}deg)`; - btn.addEventListener("click", () => { - socket.send(JSON.stringify({ - type: "guess", - username: username, - guess: user - })); - optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true); - }); - optionsDiv.appendChild(btn); + const path = document.createElementNS(SVGNS, "path"); + path.setAttribute("d", wedgePath(CX, CY, R, a0, a1)); + path.setAttribute("class", "wedge"); + path.setAttribute("data-user", user); + path.addEventListener("click", () => { + socket.send(JSON.stringify({ + type: "guess", + username: username, + guess: user + })); + svg.querySelectorAll(".wedge").forEach(w => w.classList.add("disabled")); + path.classList.add("selected"); }); - }, 0); - startBtn.hidden = true; - roundArea.hidden = false; + // Label mittig im Segment + const mid = (a0 + a1) / 2; + const P = polar(CX, CY, R * 0.58, mid); + const text = document.createElementNS(SVGNS, "text"); - const songList = document.getElementById("songList"); - songList.innerHTML = ""; - //trackinfos ist eine map bestehend aus aus Spielername und Liste von Track-Infos - const userTracks = trackInfos?.[username] ?? []; - userTracks.forEach(trackInfo => { - const li = document.createElement("li"); - li.textContent = trackInfo; - songList.appendChild(li); + const span = a1 - a0; // Winkelbreite des Segments + const font = Math.max(18, Math.min(42, 26 * (span / 60))); + text.setAttribute("font-size", font); + text.setAttribute("font-weight", "800"); + // Bessere Lesbarkeit auf dunklem Slice + text.setAttribute("paint-order", "stroke"); + text.setAttribute("stroke", "#000"); + text.setAttribute("stroke-width", "2"); + text.setAttribute("fill", "#fff"); + // überschreibt CSS-Größe + text.setAttribute("x", P.x); + text.setAttribute("y", P.y); + text.setAttribute("class", "wedge-label"); + text.setAttribute("text-anchor", "middle"); + text.setAttribute("dominant-baseline", "middle"); + text.textContent = user; + + svg.appendChild(path); + svg.appendChild(text); }); - //playLock = false; + + // Start-Button ausblenden + Rundensektion einblenden + startBtn.hidden = true; + startBtn.disabled = true; + roundArea.hidden = false; + + // Geladene Songs (beide Orte befüllen: Aside + Drawer) + const songList = document.getElementById("songList"); + const songListArea = document.getElementById("songListArea"); + const userTracks = trackInfos?.[username] ?? []; + + if (songList) { + songList.innerHTML = ""; + userTracks.forEach(t => { + const li = document.createElement("li"); + li.textContent = t; + songList.appendChild(li); + }); + } + if (songListArea) { + songListArea.innerHTML = ""; + userTracks.forEach(t => { + // Erwartetes Format "Titel - Künstler" + const [title, artists] = String(t).split(" - "); + const row = document.createElement("div"); + row.className = "track"; + row.innerHTML = ` +
+
${esc(title ?? t)}${esc(artists ?? "")}
`; + songListArea.appendChild(row); + }); + } } -// 9) Funktion zum Anzeigen des Ergebnisses +// 7) Ergebnis + Weiter-Button function renderScoreboard(scores) { scoreboard.innerHTML = ""; Object.entries(scores).forEach(([user, pts]) => { @@ -186,14 +252,35 @@ function renderScoreboard(scores) { function handleRoundResult({ scores, guesses, owner }) { renderScoreboard(scores); + try { + const wedges = document.querySelectorAll("#options .wedge"); + + // Owner-Slice immer grün + wedges.forEach(w => { + if (w.getAttribute("data-user") === owner) w.classList.add("correct"); + }); + + // Nur die EIGENE Auswahl einfärben: rot wenn falsch, sonst grün + const myGuess = guesses?.[username]; + if (myGuess) { + const myWedge = Array.from(wedges).find(w => w.getAttribute("data-user") === myGuess); + if (myWedge) { + if (myGuess === owner) { + myWedge.classList.add("correct"); + } else { + myWedge.classList.add("wrong"); // nur dieser wird rot + } + } + } + } catch (e) {} + resultP.innerHTML = ""; - Object.entries(guesses).forEach(([user, guess]) => { + Object.entries(guesses || {}).forEach(([user, guess]) => { const correct = guess === owner; const icon = correct ? "✅" : "❌"; const delta = correct ? "+3" : "-1"; - const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`; - const p = document.createElement("p"); - p.textContent = msg; + const p = document.createElement("p"); + p.textContent = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`; resultP.appendChild(p); }); @@ -205,24 +292,25 @@ function handleRoundResult({ scores, guesses, owner }) { nextBtn.hidden = true; nextBtn.disabled = true; resultP.textContent = ""; - startBtn.hidden = true; + startBtn.hidden = true; startBtn.disabled = true; - roundArea.hidden = true; + roundArea.hidden = true; }; } -function handleGameEnd({winner}) { + +// 8) Spielende -> Start-Button zurück +function handleGameEnd({ winner }) { resultP.textContent = `🎉 ${winner} hat gewonnen!`; setTimeout(() => { startBtn.hidden = false; - roundArea.hidden = true; startBtn.disabled = false; + roundArea.hidden = true; scoreboard.innerHTML = ""; }, 6000); } -// Spotify-Playback Funktion -// game.js +// Spotify-Playback Funktion (unverändert) async function playOnSpotify(trackUri, username) { const deviceId = document.getElementById("deviceSelect")?.value; if (!deviceId) { diff --git a/src/main/resources/public/js/lobby.js b/src/main/resources/public/js/lobby.js index 99df629..a4c44ab 100644 --- a/src/main/resources/public/js/lobby.js +++ b/src/main/resources/public/js/lobby.js @@ -12,8 +12,13 @@ document.getElementById("createGame").addEventListener("click", () => { window.location.href = `/create-game.html?username=${encodeURIComponent(username)}`; }); -document.getElementById("joinGame").addEventListener("click", () => { - window.location.href = username - ? `/join-game.html?username=${encodeURIComponent(username)}` - : "/join-game.html"; +//document.getElementById("joinGame").addEventListener("click", () => { + // window.location.href = username + // ? `/join-game.html?username=${encodeURIComponent(username)}` + // : "/join-game.html"; +//}); +// NEU: statt Redirect -> Fokus auf Eingabe +document.getElementById("joinGame")?.addEventListener("click", () => { + const input = document.getElementById("gameId"); + if (input) { input.focus(); input.select(); } }); diff --git a/src/main/resources/public/lobby.html b/src/main/resources/public/lobby.html index 63d7e23..32eabcf 100644 --- a/src/main/resources/public/lobby.html +++ b/src/main/resources/public/lobby.html @@ -1,15 +1,231 @@ - - Lobby – Spotify Roulette + + + Spotify Roulette – Lobby + + + + -

Lobby

-

Was möchtest du tun?

- - +
+
+ +

Lobby

+ Angemeldet als +
+ +
+
+

Neues Spiel

+
+
+ +
Spiel erstellenStarte eine neue Runde und teile den Code.
+
+ +
+
+ +
+

Spiel beitreten

+
+
+ +
Bestehendem Spiel beitretenDu hast schon einen Code? Gib ihn unten ein.
+
+ + +
+
+ +
+ + + 4-stellig +
+
+ + + + + + + + +
+ +

Tipp: Code ist vierstellig (z. B. 6767).

+
+
+
+
+ + + + + + + +