// ハンドレンジ学習(管理者専用): レンジ表と色・分類クイズ (function () { 'use strict'; const $ = (id) => document.getElementById(id); const { RANKS, CATEGORIES, FACING_ACTIONS, handAt, categoryOf, labelOf, facingOf, facingAction } = window.HandRange; Fluid.navBar($('nav'), $('large-title')); const tabsSeg = Fluid.Segmented($('tabs')); Fluid.Segmented($('count')); function swatch(id) { const s = document.createElement('span'); s.className = `swatch-dot cat-${id}`; return s; } // レイズを受けたときの対応の枠(3bet は水色、コールは黄) function frameSwatch(id) { const s = document.createElement('span'); s.className = `swatch-dot swatch-frame facing-${id}`; return s; } // ---------- タブ ---------- function showView(name) { $('view-range').hidden = name !== 'range'; $('view-quiz').hidden = name !== 'quiz'; for (const b of $('tabs').querySelectorAll('[data-view]')) b.classList.toggle('active', b.dataset.view === name); tabsSeg.update(); } $('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 (const a of FACING_ACTIONS) { const item = document.createElement('span'); item.append(frameSwatch(a.id), `レイズを受けたら${a.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'; const facing = facingOf(hand); b.className = `range-cell cat-${cat}${facing ? ` facing-${facing}` : ''}`; b.textContent = hand; b.title = `${hand}:${labelOf(cat)}${facing ? `/レイズを受けたら${facingAction(facing).label}` : ''}`; 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 facing = facingOf(hand); const vs = document.createElement('div'); vs.className = 'sel-label'; if (facing) vs.append(frameSwatch(facing), `レイズを受けたら${facingAction(facing).label}`); else vs.append('レイズを受けたらフォールド'); const note = document.createElement('div'); note.className = 'hint'; note.textContent = (cat === 'gray' ? 'どのポジションからもオープンしない手です。' : `${labelOf(cat)} からオープンできる一番狭い分類です。`) + '枠は CO のオープンを BTN で受けたときの対応です。'; box.append(name, label, vs, 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').querySelectorAll('[data-count]')) 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; // 間違えたら選んだボタンが揺れる(振動と同じ瞬間に) if (!ok) Fluid.shake(button); Fluid.haptic(ok ? 10 : [18, 50, 18]); 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(); }); })();