<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>出題者ページ</title> <link rel="stylesheet" href="host.css"> <style> .answer-buttons, .teams-section { margin-top: 15px; border-top: 1px solid #ccc; padding-top: 15px; } .answer-buttons button { font-size: 1.1em; padding: 8px 16px; cursor: pointer; border-radius: 5px; } #correctBtn { background-color: #28a745; color: white; border: 1px solid #28a745; } #incorrectBtn { background-color: #dc3545; color: white; border: 1px solid #dc3545; } .answer-buttons button:disabled { background-color: #6c757d; border-color: #6c757d; cursor: not-allowed; } #announce { white-space: pre-wrap; } #teamsTable { width: 100%; border-collapse: collapse; margin-top: 10px; } #teamsTable th, #teamsTable td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; } #teamsTable th { background-color: #f2f2f2; } #teamsTable td:last-child { min-height: 1.5em; } #scoreResetBtn { background-color: #f0ad4e; color: white; border-color: #eea236;} .score-adjust-section { margin-top: 15px; padding: 15px; border: 1px dashed #ccc; border-radius: 5px; } .score-adjust-section h3 { margin-top: 0; margin-bottom: 10px; font-size: 1.2em; } .score-adjust-section select, .score-adjust-section input, .score-adjust-section button { padding: 5px; font-size: 1em; margin-right: 10px; } </style> </head> <body> <h1>出題者ページ</h1> <p id="currentSong">現在の曲: 未選択</p> <div class="button-group"> <button id="prevBtn">前の曲</button> <button id="nextBtn">🎶 次の曲</button> <button id="playBtn">▶ 再生</button> <button id="pauseBtn">⏸ 一時停止</button> <button id="stopBtn">⏹ 停止</button> <button id="resetBtn">🔁 挙手リセット</button> <button id="scoreResetBtn">💰 得点リセット</button> </div> <pre id="announce">誰も挙手していません</pre> <div class="answer-buttons"> <button id="correctBtn">⭕ 正解</button> <button id="incorrectBtn">❌ 不正解</button> </div> <div class="score-adjust-section"> <h3>手動得点調整</h3> <select id="adjustGroupSelect"> <option value="1班">1班</option> <option value="2班">2班</option> <option value="3班">3班</option> <option value="4班">4班</option> <option value="5班">5班</option> </select> <input type="number" id="adjustScoreInput" placeholder="点数 (例: 5, -5)"> <button id="adjustScoreBtn">決定</button> </div> <div class="teams-section"> <h2>参加チーム一覧</h2> <table id="teamsTable"> <thead> <tr> <th style="width: 15%;">グループ</th> <th style="width: 15%;">ポイント</th> <th>参加メンバー</th> </tr> </thead> <tbody> <tr><td>1班</td><td id="points-1">0</td><td id="members-1">(まだ誰もいません)</td></tr> <tr><td>2班</td><td id="points-2">0</td><td id="members-2">(まだ誰もいません)</td></tr> <tr><td>3班</td><td id="points-3">0</td><td id="members-3">(まだ誰もいません)</td></tr> <tr><td>4班</td><td id="points-4">0</td><td id="members-4">(まだ誰もいません)</td></tr> <tr><td>5班</td><td id="points-5">0</td><td id="members-5">(まだ誰もいません)</td></tr> </tbody> </table> </div> <script> const songLabel = document.getElementById("currentSong"); const playBtn = document.getElementById("playBtn"); const pauseBtn = document.getElementById("pauseBtn"); const stopBtn = document.getElementById("stopBtn"); const announce = document.getElementById("announce"); const resetBtn = document.getElementById("resetBtn"); const prevBtn = document.getElementById("prevBtn"); const nextBtn = document.getElementById("nextBtn"); const correctBtn = document.getElementById("correctBtn"); const incorrectBtn = document.getElementById("incorrectBtn"); const scoreResetBtn = document.getElementById("scoreResetBtn"); const teamsTableBody = document.querySelector("#teamsTable tbody"); const adjustGroupSelect = document.getElementById("adjustGroupSelect"); const adjustScoreInput = document.getElementById("adjustScoreInput"); const adjustScoreBtn = document.getElementById("adjustScoreBtn"); let musicFiles = []; let currentIndex = -1; correctBtn.disabled = true; incorrectBtn.disabled = true; // ★★★ 音楽サーバーのIPアドレスは、当日のあなたのPCのIPに合わせてください ★★★ fetch(`http://192.168.1.138:8890/music_list.json`) .then(res => res.json()) .then(data => { musicFiles = data.files.sort((a, b) => (parseInt(a.match(/^\d+/), 10) || 0) - (parseInt(b.match(/^\d+/), 10) || 0)); }); // ▼▼▼【変更】WebSocket接続先を、ローカル運用に合わせて大学サーバーのIPに固定 ▼▼▼ const conn = new WebSocket('ws://172.17.54.116:8804/'); playBtn.addEventListener("click", () => { if (currentIndex === -1) return; conn.send(JSON.stringify({ type: "play" })); }); pauseBtn.addEventListener("click", () => conn.send(JSON.stringify({ type: "pause" }))); stopBtn.addEventListener("click", () => conn.send(JSON.stringify({ type: "stop" }))); resetBtn.addEventListener("click", () => conn.send(JSON.stringify({ type: "reset" }))); correctBtn.addEventListener("click", () => { if (currentIndex === -1) return; conn.send(JSON.stringify({ type: "correct", answer: musicFiles[currentIndex] })); }); incorrectBtn.addEventListener("click", () => conn.send(JSON.stringify({ type: "incorrect" }))); prevBtn.addEventListener("click", () => { if (musicFiles.length === 0) return; currentIndex = (currentIndex - 1 + musicFiles.length) % musicFiles.length; const file = musicFiles[currentIndex]; songLabel.textContent = `現在の曲: ${file}`; conn.send(JSON.stringify({ type: "select", file: file })); }); nextBtn.addEventListener("click", () => { if (musicFiles.length === 0) return; currentIndex = (currentIndex + 1) % musicFiles.length; const file = musicFiles[currentIndex]; songLabel.textContent = `現在の曲: ${file}`; conn.send(JSON.stringify({ type: "select", file: file })); }); scoreResetBtn.addEventListener("click", () => { const confirmCode = "1111"; const input = prompt(`【得点リセット確認】\n\n確認コード「${confirmCode}」を入力してください。`); if (input === confirmCode) { if (confirm("本当によろしいですか?\nすべてのチームの得点が0点にリセットされます。")) { conn.send(JSON.stringify({ type: "reset_scores" })); alert("全チームの得点をリセットしました。"); } } else if (input !== null) { alert("確認コードが違います。"); } }); adjustScoreBtn.addEventListener("click", () => { const group = adjustGroupSelect.value; const scoreText = adjustScoreInput.value; const score = parseInt(scoreText, 10); if (!group) { alert("班を選択してください。"); return; } if (isNaN(score) || scoreText.trim() === "") { alert("有効な数値を入力してください。"); return; } conn.send(JSON.stringify({ type: "adjust_score", group: group, score: score })); console.log(`${group}の得点を${score}点調整します。`); adjustScoreInput.value = ""; }); conn.onmessage = ev => { let msg; try { msg = JSON.parse(ev.data); } catch { return; } switch (msg.type) { case "select": announce.textContent = "曲選択完了、待機中..."; break; case "announce": const queue = msg.queue || []; if (queue.length === 0) { announce.textContent = "誰も挙手していません"; correctBtn.disabled = true; incorrectBtn.disabled = true; } else { announce.textContent = queue.map((p, i) => `${i + 1}. ${p.group} - ${p.name} さん`).join("\n"); correctBtn.disabled = false; incorrectBtn.disabled = false; } break; case "update_teams": const participants = msg.teams || []; const points = msg.points || {}; const teamMembers = { '1班': [], '2班': [], '3班': [], '4班': [], '5班': [] }; participants.forEach(p => { if (teamMembers[p.group]) { teamMembers[p.group].push(p.name); } }); for (let i = 1; i <= 5; i++) { const groupName = `${i}班`; const memberCell = document.getElementById(`members-${i}`); const members = teamMembers[groupName]; if (members.length > 0) { memberCell.innerHTML = members.join('<br>'); } else { memberCell.textContent = '(まだ誰もいません)'; } const pointsCell = document.getElementById(`points-${i}`); pointsCell.textContent = points[groupName] || 0; } break; } }; conn.onopen = () => console.log("WebSocket 接続成功!"); conn.onerror = (e) => console.error("WebSocket エラー:", e); conn.onclose = () => { console.log("WebSocket 切断"); songLabel.textContent = "サーバーとの接続が切れました。"; }; </script> </body> </html>