pull/24/head
eric 2025-08-08 03:29:15 +02:00
parent 3d633aff70
commit 6a067d0c1e
2 changed files with 187 additions and 71 deletions

View File

@ -1,43 +1,159 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spotify Roulette Spiel</title> <title>Spotify Roulette Spiel</title>
<style> <style>
body { font-family: sans-serif; max-width: 600px; margin: auto; padding: 1rem; } :root {
#options button { margin: 0.5rem; } --primary-color: #1DB954;
#scoreboard { margin-top: 1rem; } --bg-light: #f2f2f2;
</style> --bg-dark: #121212;
--text-light: #ffffff;
--text-dark: #333333;
--accent: #191414;
--option-circle-size: 250px;
--option-radius: 120px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg-light);
color: var(--text-dark);
display: grid;
grid-template-areas:
"header header"
"main aside"
"footer footer";
grid-template-columns: 3fr 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
header {
grid-area: header;
background: var(--primary-color);
color: var(--text-light);
padding: 1rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
}
main { grid-area: main; padding: 2rem; }
aside {
grid-area: aside;
background: var(--accent);
color: var(--text-light);
padding: 1.5rem;
overflow-y: auto;
}
footer {
grid-area: footer;
background: var(--accent);
color: var(--text-light);
text-align: center;
padding: 0.5rem;
font-size: 0.9rem;
}
.section-title {
margin-bottom: 1rem;
font-size: 1.2rem;
border-bottom: 2px solid var(--primary-color);
padding-bottom: 0.3rem;
}
ul { list-style: none; }
#controls, .round-controls { margin: 1rem 0; text-align: center; }
button {
background: var(--primary-color);
border: none;
color: var(--text-light);
padding: 0.6rem 1.2rem;
margin: 0.3rem;
font-size: 1rem;
border-radius: 20px;
cursor: pointer;
transition: background 0.2s;
}
button:hover:not(:disabled) { background: #0c903a; }
button:disabled { opacity: 0.6; cursor: not-allowed; }
/* Rundenbereich */
#roundArea {
margin-top: 1rem;
background: var(--text-light);
padding: 0.7rem 1rem;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
#songEmbed iframe { border-radius: 6px; }
:root {
/* 80% der Haupt-Container-Breite, max. 400px */
--option-circle-size: 80%;
--option-max-size: 400px;
}
#options {
position: relative;
width: var(--option-circle-size);
max-width: var(--option-max-size);
aspect-ratio: 1; /* Höhe = Breite */
margin: 2rem auto;
}
#options .player-option {
position: absolute;
width: 100px;
height: 40px;
left: 50%;
top: 50%;
transform-origin: center calc(-50% - 10px);
text-align: center;
padding: 0.5rem;
border-radius: 4px;
}
#options .player-option:nth-child(1) { transform: rotate(0deg) translate(0, var(--option-radius)); }
#options .player-option:nth-child(2) { transform: rotate(90deg) translate(0, var(--option-radius)); }
#options .player-option:nth-child(3) { transform: rotate(180deg) translate(0, var(--option-radius)); }
#options .player-option:nth-child(4) { transform: rotate(270deg) translate(0, var(--option-radius)); }
</style>
</head> </head>
<body> <body>
<h1>Spotify Roulette</h1> <header>
<p>Spiel-Code: <strong id="gameId"></strong></p> <h1>Spotify Roulette</h1>
<div>Spiel-Code: <span id="gameId" class="game-code"></span></div>
<div id="songListArea" style="position:fixed; right:0; top:0; width:200px; height:100vh; overflow-y:auto; background:#f8f8f8; border-left:1px solid #ccc; padding:1rem; z-index:10;"> </header>
<h3>Geladene Songs</h3> <main>
<ul id="songList"></ul> <section>
</div> <div class="section-title">Teilnehmer</div>
<ul id="playersList"></ul>
<h2>Teilnehmer</h2> </section>
<ul id="playersList"></ul> <div id="controls">
<button id="startRound">Runde starten</button>
<div id="controls"> </div>
<button id="startRound">Runde starten</button> <section id="roundArea" hidden>
</div> <div class="section-title">Wer hats gehört?</div>
<div id="songEmbed"></div>
<div id="roundArea" hidden> <div class="round-controls" id="options">
<h2>Wer hats gehört?</h2> <!-- Buttons zu Auswahl verteilen: JS erzeugt <button class="player-option">Name</button> -->
<div id="songEmbed"></div> </div>
<div id="options"></div> <div id="result"></div>
<p id="result"></p> </section>
</div> <section>
<div class="section-title">Scoreboard</div>
<h2>Scoreboard</h2> <ul id="scoreboard"></ul>
<ul id="scoreboard"></ul> </section>
</main>
<aside>
<div class="section-title">Geladene Songs</div>
<ul id="songList"></ul>
<div id="deviceSelectArea"></div>
</aside>
<footer> Spotify Roulette alles ausser arbeiten </footer>
<script type="module" src="/js/utils.js"></script>
<script type="module" src="/js/start-round.js"></script>
<script type="module" src="/js/game.js"></script> <script type="module" src="/js/game.js"></script>
<div id="deviceSelectArea"></div>
<script type="module" src="/js/device-select.js"></script> <script type="module" src="/js/device-select.js"></script>
</body> </body>
</html> </html>

View File

@ -98,7 +98,7 @@ function connectWebSocket() {
} }
connectWebSocket(); connectWebSocket();
// 8) Funktion zum Anzeigen einer neuen Runde // Zugriff auf DOM-Elemente
const startBtn = document.getElementById("startRound"); const startBtn = document.getElementById("startRound");
const roundArea = document.getElementById("roundArea"); const roundArea = document.getElementById("roundArea");
const songEmbed = document.getElementById("songEmbed"); const songEmbed = document.getElementById("songEmbed");
@ -106,6 +106,7 @@ const optionsDiv = document.getElementById("options");
const resultP = document.getElementById("result"); const resultP = document.getElementById("result");
const scoreboard = document.getElementById("scoreboard"); const scoreboard = document.getElementById("scoreboard");
// 8) Funktion zum Anzeigen einer neuen Runde
function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) { function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) {
// UI zurücksetzen // UI zurücksetzen
resultP.textContent = ""; resultP.textContent = "";
@ -120,36 +121,44 @@ function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) {
width="100%" height="80" frameborder="0" width="100%" height="80" frameborder="0"
allow="encrypted-media"> allow="encrypted-media">
</iframe>`; </iframe>`;
// Song automatisch abspielen (sofern deviceId bereit und Username vorhanden)
if (window.playOnSpotify && typeof window.playOnSpotify === "function") { if (window.playOnSpotify && typeof window.playOnSpotify === "function") {
window.playOnSpotify(songUri, username); window.playOnSpotify(songUri, username);
} }
// Tipp-Buttons erzeugen // Dynamische Kreisverteilung der Buttons
ownerOptions.forEach(user => { // Warten, bis #options gerendert ist
const btn = document.createElement("button"); setTimeout(() => {
btn.textContent = user; const optsRect = optionsDiv.getBoundingClientRect();
btn.addEventListener("click", () => { const radius = Math.min(optsRect.width, optsRect.height) / 2 - 50; // 50px Abstand zum Rand
socket.send(JSON.stringify({
type: "guess", ownerOptions.forEach((user, i) => {
username: username, const btn = document.createElement("button");
guess: user btn.textContent = user;
})); btn.classList.add("player-option");
// Nach Tipp alle Buttons deaktivieren const angle = 360 * i / ownerOptions.length;
optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true); btn.style.transform = `rotate(${angle}deg) translateY(-${radius}px) rotate(${-angle}deg)`;
}); btn.addEventListener("click", () => {
optionsDiv.append(btn); socket.send(JSON.stringify({
}); type: "guess",
username: username,
guess: user
}));
optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true);
});
optionsDiv.appendChild(btn);
});
}, 0);
// UI anzeigen
startBtn.hidden = true; startBtn.hidden = true;
roundArea.hidden = false; roundArea.hidden = false;
const songList = document.getElementById("songList"); const songList = document.getElementById("songList");
songList.innerHTML = ""; songList.innerHTML = "";
if (Array.isArray(trackInfos)) { if (Array.isArray(trackInfos)) {
trackInfos.forEach(trackInfo => { trackInfos.forEach(trackInfo => {
const li = document.createElement("li"); const li = document.createElement("li");
li.textContent = trackInfo li.textContent = trackInfo;
songList.appendChild(li); songList.appendChild(li);
}); });
} }
@ -166,43 +175,38 @@ function renderScoreboard(scores) {
} }
function handleRoundResult({ scores, guesses, owner }) { function handleRoundResult({ scores, guesses, owner }) {
// Scoreboard updaten
renderScoreboard(scores); renderScoreboard(scores);
// Ergebnis für alle Spieler anzeigen resultP.innerHTML = "";
resultP.innerHTML = ""; // Vorher leeren
Object.entries(guesses).forEach(([user, guess]) => { Object.entries(guesses).forEach(([user, guess]) => {
const correct = guess === owner; const correct = guess === owner;
const icon = correct ? "✅" : "❌"; const icon = correct ? "✅" : "❌";
const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"}`; const delta = correct ? "+3" : "-1";
const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`;
const p = document.createElement("p"); const p = document.createElement("p");
p.textContent = msg; p.textContent = msg;
resultP.appendChild(p); resultP.appendChild(p);
}); });
// Nach kurzer Pause für die nächste Runde vorbereiten
setTimeout(() => { setTimeout(() => {
resultP.textContent = ""; resultP.textContent = "";
startBtn.hidden = true; startBtn.hidden = true;
startBtn.disabled = true; startBtn.disabled = true;
roundArea.hidden = true; roundArea.hidden = true;
}, 3000); }, 3000);
} }
function handleGameEnd({winner}) { function handleGameEnd({winner}) {
resultP.textContent = `🎉 ${winner} hat gewonnen!`; resultP.textContent = `🎉 ${winner} hat gewonnen!`;
setTimeout(() => { setTimeout(() => {
startBtn.hidden = false; startBtn.hidden = false;
roundArea.hidden = true; roundArea.hidden = true;
startBtn.disabled = false; startBtn.disabled = false;
// scoreboard leeren
scoreboard.innerHTML = ""; scoreboard.innerHTML = "";
}, 6000); }, 6000);
} }
// public/js/game.js // Spotify-Playback Funktion
async function playOnSpotify(trackUri, username) { async function playOnSpotify(trackUri, username) {
const deviceId = document.getElementById("deviceSelect")?.value; const deviceId = document.getElementById("deviceSelect")?.value;
if (!deviceId) { if (!deviceId) {
@ -213,11 +217,7 @@ async function playOnSpotify(trackUri, username) {
const response = await fetch("/api/spotify/play", { const response = await fetch("/api/spotify/play", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({ username, device_id: deviceId, track_uri: trackUri })
username,
device_id: deviceId,
track_uri: trackUri
})
}); });
if (!response.ok) { if (!response.ok) {
const error = await response.text(); const error = await response.text();