'use strict'; // 計算型: poker-calculations(ネイティブ実装)の decideAction に、勝率・ポットオッズ・コールの期待値から決めさせる。 // スタックが少ないときは、同じライブラリのオールインの損益分岐勝率と比べてプッシュかフォールドにする。 const { situation, CALL, FOLD, CHECK, passive, allIn, raiseToAmount, seedFrom, between } = require('./common'); // ネイティブアドオンが読み込めない環境では、この思考は使わない let pc = null; try { pc = require('poker-calculations'); } catch { pc = null; } const available = () => pc !== null; function randomStyle(rng) { return { // 小さいほどベット・レイズが多い(0.4 未満は無謀になり、0.6 を超えるとほとんどレイズしない) aggressionThreshold: between(rng, 0.4, 0.52), riskTolerance: between(rng, 0.4, 0.6), // 大きいほど薄い勝率でもコールする raisePotFraction: between(rng, 0.55, 0.9), // ベット・レイズの大きさ(ポットに対する割合) pushBias: between(rng, -0.04, 0.04), // プッシュの基準勝率への上乗せ(負ほど広くプッシュ) }; } const PHASES = ['preflop', null, null, 'flop', 'turn', 'river']; // decideAction に渡すテーブルの状態。他人の手札は空にする function tableState(hand, seat) { const list = hand.list; const index = (s) => list.findIndex((p) => p.seat === s); const firstToAct = hand.board.length === 0 ? (index(hand.bbSeat) + 1) % list.length : 0; return { players: list.map((p, i) => ({ holeCards: p.seat === seat ? p.cards : [], stack: hand.seats[p.seat].stack, committedThisStreet: p.bet, totalCommittedHand: p.committed, folded: p.folded, seat: i, })), communityCards: hand.board, phase: PHASES[hand.board.length], pot: hand.potTotal, currentBet: hand.currentBet, buttonSeat: index(hand.button), smallBlind: hand.sb, bigBlind: hand.bb, actingIndex: index(seat), lastRaiseIncrement: hand.state.lastRaiseAmount || hand.bb, streetOpeningIndex: firstToAct, // このストリートで動いた人(直前のアクションの表示はストリートが変わると消える) actedThisStreet: list.map((p) => Boolean(p.last && p.last.type !== 'FOLD')), }; } function decide(hand, seat, { style, rng = Math.random, iterations = 500 } = {}) { const s = situation(hand, seat); if (!s) return null; const { o, p, stack, bb, preflop, opponents, pot } = s; const villains = Math.max(1, opponents); // スタックが 12BB 以下ならプッシュかフォールド if (preflop && stack + p.bet <= 12 * bb) { const eq = pc.simulateHandOutcome(p.cards, [], iterations, seedFrom(rng), villains); const breakeven = pc.pushFoldSymmetricBreakevenEquity(stack + p.bet, pot - p.bet); if (eq >= breakeven + style.pushBias + 0.04 * (villains - 1)) return o.canRaise ? allIn(o) : CALL; return passive(o); } const d = pc.decideAction(tableState(hand, seat), { aggressionThreshold: style.aggressionThreshold, riskTolerance: style.riskTolerance, raisePotFraction: style.raisePotFraction, monteCarloSimulations: iterations, monteCarloVillains: villains, rngSeed: seedFrom(rng), }); if (d.action === 'raise' && o.canRaise) return raiseToAmount(hand, seat, hand.currentBet + d.raiseBy); if (d.action === 'raise' || d.action === 'call') return o.canCheck ? CHECK : CALL; if (d.action === 'check') return passive(o); return FOLD; } module.exports = { decide, randomStyle, available };