Newer
Older
Intro-Q-2025-ver2 / public / host.html
<!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;
    }
    #showAnswerBtn { background-color: #5bc0de; color: white; border-color: #46b8da; margin-top: 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>
    <button id="showAnswerBtn">💡 曲名ヒント/正解発表</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");
    const showAnswerBtn = document.getElementById("showAnswerBtn");
    let musicFiles = [];
    let currentIndex = -1;
    correctBtn.disabled = true;
    incorrectBtn.disabled = true;

    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)); });
    
    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 = "";
    });
    showAnswerBtn.addEventListener("click", () => {
        if (currentIndex === -1) {
            alert("曲が選択されていません。");
            return;
        }
        const answerFile = musicFiles[currentIndex];
        if (confirm(`正解を発表しますか?\n\n答え: ${answerFile}`)) {
            conn.send(JSON.stringify({ type: "show_answer", answer: answerFile }));
        }
    });

    // ▼▼▼【ここを修正】onmessageの処理を完全に復元 ▼▼▼
    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>