// ハンドレンジ学習(管理者専用): レンジ表と色・分類クイズ (function () { 'use strict'; const $ = (id) => document.getElementById(id); const RANKS = ['A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2']; const CATEGORIES = [ { id: 'navy', label: '8人(強)' }, { id: 'red', label: '8人(弱)' }, { id: 'yellow', label: '6〜7人' }, { id: 'green', label: '4〜5人' }, { id: 'blue', label: '3人' }, { id: 'white', label: '2人' }, { id: 'purple', label: '紫枠' }, { id: 'gray', label: 'フォールド' }, ]; // 全169ハンドの分類(載っていないハンドはフォールド)。紫枠はクイズ上の独立した回答 const RANGE = { AA: 'navy', AKs: 'navy', AQs: 'red', AJs: 'red', ATs: 'red', A9s: 'green', A8s: 'green', A7s: 'green', A6s: 'green', A5s: 'green', A4s: 'green', A3s: 'green', A2s: 'green', AKo: 'navy', KK: 'navy', KQs: 'red', KJs: 'yellow', KTs: 'green', K9s: 'green', K8s: 'white', K7s: 'white', K6s: 'white', K5s: 'white', K4s: 'white', K3s: 'white', K2s: 'white', AQo: 'red', KQo: 'yellow', QQ: 'navy', QJs: 'yellow', QTs: 'green', Q9s: 'blue', Q8s: 'white', Q7s: 'white', Q6s: 'white', Q5s: 'purple', Q4s: 'purple', Q3s: 'purple', Q2s: 'purple', AJo: 'yellow', KJo: 'green', QJo: 'blue', JJ: 'red', JTs: 'yellow', J9s: 'blue', J8s: 'white', J7s: 'white', J6s: 'purple', ATo: 'green', KTo: 'blue', QTo: 'white', JTo: 'blue', TT: 'red', T9s: 'green', T8s: 'blue', T7s: 'purple', A9o: 'blue', K9o: 'white', Q9o: 'white', J9o: 'white', T9o: 'white', 99: 'red', '98s': 'blue', '97s': 'white', '96s': 'purple', A8o: 'white', '98o': 'purple', 88: 'yellow', '87s': 'white', '86s': 'purple', A7o: 'white', 77: 'yellow', '76s': 'white', '75s': 'purple', A6o: 'purple', 66: 'green', '65s': 'white', '64s': 'purple', 55: 'green', '54s': 'purple', 44: 'blue', 33: 'blue', 22: 'blue', }; const handAt = (r, c) => (r === c ? RANKS[r] + RANKS[c] : r < c ? RANKS[r] + RANKS[c] + 's' : RANKS[c] + RANKS[r] + 'o'); const categoryOf = (hand) => RANGE[hand] || 'gray'; const labelOf = (id) => (CATEGORIES.find((c) => c.id === id) || CATEGORIES[CATEGORIES.length - 1]).label; function swatch(id) { const s = document.createElement('span'); s.className = `swatch-dot cat-${id}`; return s; } // ---------- タブ ---------- function showView(name) { $('view-range').hidden = name !== 'range'; $('view-quiz').hidden = name !== 'quiz'; for (const b of $('tabs').children) b.classList.toggle('active', b.dataset.view === name); } $('tabs').addEventListener('click', (e) => { const b = e.target.closest('[data-view]'); if (b) showView(b.dataset.view); }); // ---------- レンジ表 ---------- for (const c of CATEGORIES) { const item = document.createElement('span'); item.append(swatch(c.id), c.label); $('legend').append(item); } for (let r = 0; r < 13; r++) { for (let c = 0; c < 13; c++) { const hand = handAt(r, c); const cat = categoryOf(hand); const b = document.createElement('button'); b.type = 'button'; b.className = `range-cell cat-${cat}`; b.textContent = hand; b.title = `${hand}:${labelOf(cat)}`; b.addEventListener('click', () => select(b, hand, cat)); $('matrix').append(b); } } function select(cell, hand, cat) { document.querySelectorAll('.range-cell.selected').forEach((x) => x.classList.remove('selected')); cell.classList.add('selected'); const box = $('selected'); box.replaceChildren(); const name = document.createElement('div'); name.className = 'sel-hand'; name.textContent = hand; const label = document.createElement('div'); label.className = 'sel-label'; label.append(swatch(cat), labelOf(cat)); const note = document.createElement('div'); note.className = 'hint'; note.textContent = cat === 'purple' ? '灰色+紫枠。クイズの答えは「紫枠」です。' : 'このハンドの分類です。'; box.append(name, label, note); } $('start-from-range').addEventListener('click', () => { showView('quiz'); startQuiz(); }); // ---------- クイズ ---------- const SUITS = ['hearts', 'clubs', 'diamonds', 'spades']; const RANK_FILE = { A: 'ace', K: 'king', Q: 'queen', J: 'jack', T: '10' }; let quizCount = 10; let deck = []; let index = 0; let correct = 0; let answered = 0; let locked = false; $('count').addEventListener('click', (e) => { const b = e.target.closest('[data-count]'); if (!b) return; quizCount = Number(b.dataset.count); for (const x of $('count').children) x.classList.toggle('active', x === b); }); function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } function startQuiz() { const pool = []; for (let r = 0; r < 13; r++) for (let c = 0; c < 13; c++) pool.push(handAt(r, c)); deck = shuffle(pool); if (quizCount > 0) deck = deck.slice(0, quizCount); index = 0; correct = 0; answered = 0; $('start-quiz').textContent = '最初から'; renderQuestion(); } // スーテッドは同じマーク、それ以外は別々のマーク function showCards(hand) { const s1 = SUITS[Math.floor(Math.random() * 4)]; let s2 = s1; if (hand[2] !== 's') while (s2 === s1) s2 = SUITS[Math.floor(Math.random() * 4)]; [hand[0], hand[1]].forEach((rank, i) => { const img = $(`card-${i + 1}`); const suit = i === 0 ? s1 : s2; img.hidden = true; img.onload = () => { img.hidden = false; }; img.onerror = () => { img.hidden = true; }; img.alt = `${RANK_FILE[rank] || rank} of ${suit}`; img.src = `/range/files/cards/${RANK_FILE[rank] || rank}_of_${suit}.png`; }); } function updateStats() { $('q-score').textContent = `正解 ${correct} / ${answered}`; $('q-accuracy').textContent = `正答率 ${answered ? Math.round((correct / answered) * 100) + '%' : '--'}`; } function renderQuestion() { locked = false; $('answers').replaceChildren(); $('feedback').hidden = true; $('next-question').disabled = true; if (index >= deck.length) return finishQuiz(); const hand = deck[index]; $('q-hand').textContent = hand; $('q-prompt').textContent = 'このハンドの色・分類は?'; $('q-count').textContent = `問題 ${index + 1} / ${deck.length}`; $('progress-bar').style.width = `${(index / deck.length) * 100}%`; updateStats(); showCards(hand); for (const cat of CATEGORIES) { const b = document.createElement('button'); b.type = 'button'; b.className = 'btn'; b.dataset.cat = cat.id; b.append(swatch(cat.id), cat.label); b.addEventListener('click', () => answer(cat.id, b)); $('answers').append(b); } } function answer(choice, button) { if (locked) return; locked = true; const hand = deck[index]; const right = categoryOf(hand); const ok = choice === right; if (ok) correct++; answered++; for (const b of $('answers').children) { if (b.dataset.cat === right) b.classList.add('correct'); else if (b === button) b.classList.add('wrong'); b.disabled = true; } const fb = $('feedback'); fb.className = `quiz-feedback ${ok ? 'good' : 'bad'}`; fb.textContent = `${ok ? '正解!' : '不正解'} ${hand} は「${labelOf(right)}」です。`; fb.hidden = false; updateStats(); $('next-question').disabled = false; } function finishQuiz() { $('q-hand').textContent = '終了!'; $('q-prompt').textContent = `${deck.length}問中 ${correct}問正解(${deck.length ? Math.round((correct / deck.length) * 100) : 0}%)`; $('q-count').textContent = '完了'; $('progress-bar').style.width = '100%'; $('card-1').hidden = $('card-2').hidden = true; const fb = $('feedback'); fb.className = 'quiz-feedback'; fb.textContent = '「最初から」で再挑戦できます。'; fb.hidden = false; } $('start-quiz').addEventListener('click', startQuiz); $('next-question').addEventListener('click', () => { index++; renderQuestion(); }); })();