'use strict'; // プリフロップの判定: 配られた手が登録してあるハンドレンジの何色で、そのとおりに行動できたか。 // 自分より前に誰もコール・レイズしていない: 後ろの人数(自分を含めず、まだ行動していない人)に対応する色 // 以上ならレイズ(オープン)、それより弱ければフォールド // 自分より前にコール・レイズした人がいる: その人のレンジを色で推定し(その人の後ろの人数に対応する色)、 // 自分の手がその色の一つ上ならコール、二つ上ならレイズ、それより弱ければフォールド // 2人目以降に参加した人のレンジは、コールなら直前までで一番強いレンジの一つ上、レイズなら二つ上と推定する // (同じ決まりで打つ人がコール・レイズできる一番弱い色)。 const HandRange = require('../../admin/range-data'); const TOP_TIER = 7; // 紺 const ACTION_KIND = { FOLD: 'fold', CHECK: 'check', CALL: 'call', BET: 'raise', RAISE: 'raise' }; const ACTION_LABEL = { fold: 'フォールド', check: 'チェック', call: 'コール', raise: 'レイズ' }; const VERDICTS = { good: { mark: '◎', score: 100, label: 'レンジどおり' }, passive: { mark: '△', score: 50, label: 'レイズできる手でコール(チェック)した' }, aggressive: { mark: '△', score: 40, label: 'コールまでの手でレイズした' }, loose: { mark: '✕', score: 0, label: 'レンジ外の手で参加した' }, tight: { mark: '✕', score: 0, label: '参加できる手で降りた' }, needless: { mark: '✕', score: 0, label: 'チェックできるのに降りた' }, }; // 9人のテーブルの呼び方。UTG から CO までは人数に合わせて後ろから当てはめる const MIDDLE_POSITIONS = ['UTG', 'UTG+1', 'UTG+2', 'LJ', 'HJ', 'CO']; function positionOf(hand, seat) { const n = hand.order.length; if (seat === hand.button) return n === 2 ? 'BTN/SB' : 'BTN'; if (seat === hand.sbSeat) return 'SB'; if (seat === hand.bbSeat) return 'BB'; const middle = hand.preflopOrder.slice(0, n - 3); const names = MIDDLE_POSITIONS.slice(-middle.length); return names[middle.indexOf(seat)] || '?'; } const tierName = (tier) => (tier > TOP_TIER ? 'なし' : HandRange.categoryOfTier(Math.max(0, tier)).name); // 推奨: 'raise' / 'call' / 'fold'(チェックできるときの 'fold' と 'call' は 'check') function recommend(tier, situation, entry) { let rec; if (situation.kind === 'open') { rec = situation.behind === 0 ? 'check' : tier >= situation.required ? 'raise' : 'fold'; } else { const ref = situation.refTier; if (tier >= ref + 2) rec = 'raise'; else if (tier >= ref + 1 || (tier === TOP_TIER && ref >= TOP_TIER)) rec = 'call'; else rec = 'fold'; } if (rec === 'raise' && !entry.canRaise) rec = 'call'; if ((rec === 'call' || rec === 'fold') && entry.canCheck) rec = 'check'; return rec; } function verdictOf(rec, actual) { if (rec === actual) return 'good'; if (actual === 'fold') return rec === 'check' ? 'needless' : 'tight'; if (rec === 'raise') return 'passive'; // コール・チェックした if (rec === 'call') return 'aggressive'; // レイズした return 'loose'; // 降りる(チェックする)べき手で参加した } // プリフロップの各行動を順に見て、hero の判断ごとに判定する。あわせて各席の推定レンジ(色の段階)を返す function judgePreflop(hand, hero) { const tier = HandRange.tierOf(HandRange.handKey(hand.players.get(hero).cards)); const order = hand.preflopOrder; const acted = new Set(); const estimated = new Map(); // 席 → 推定レンジの一番弱い色の段階 let maxTier = null; let maxSeat = null; const judgments = []; // まだ行動していない後ろの人数(自分を含めない) const behindOf = (seat) => order.slice(order.indexOf(seat) + 1).filter((s) => !acted.has(s)).length; hand.log.forEach((e, index) => { if (e.street !== 'PREFLOP') return; const kind = ACTION_KIND[e.type]; if (e.seat === hero && !e.auto) { const situation = maxTier == null ? { kind: 'open', behind: behindOf(hero), required: HandRange.requiredTier(behindOf(hero)) } : { kind: 'facing', refTier: maxTier, refSeat: maxSeat }; const rec = recommend(tier, situation, e); const verdict = verdictOf(rec, kind); judgments.push({ index, situation, recommended: rec, actual: kind, verdict, ...VERDICTS[verdict] }); } if (kind === 'call' || kind === 'raise') { const est = maxTier == null ? HandRange.requiredTier(behindOf(e.seat)) : Math.min(TOP_TIER, maxTier + (kind === 'raise' ? 2 : 1)); estimated.set(e.seat, Math.max(estimated.get(e.seat) || 0, est)); if (maxTier == null || est >= maxTier) { maxTier = est; maxSeat = e.seat; } } acted.add(e.seat); }); return { tier, judgments, estimated }; } // 状況の説明(画面表示用) function describeSituation(situation, nameOf) { if (situation.kind === 'open') { if (situation.behind === 0) return '全員が降りてBBに回ってきた'; return `前に参加者なし・後ろに${situation.behind}人 → ${tierName(situation.required)}以上でオープン`; } const ref = situation.refTier; return `${nameOf(situation.refSeat)} の推定レンジ ${tierName(ref)} → ${tierName(ref + 1)}以上でコール、${tierName(ref + 2)}以上でレイズ`; } module.exports = { judgePreflop, positionOf, describeSituation, tierName, ACTION_KIND, ACTION_LABEL, VERDICTS };