'use strict'; // 直感型(ルーズ): pokersolver で「いまできている役」だけを見て打つ。勝率の計算はしない。 // 参加する手が広く、ペアやドローがあればなかなか降りない。強い役ができたときだけ大きく打つ。 const { Hand } = require('pokersolver'); const { situation, CHECK, CALL, FOLD, passive, keepGoing, allIn, raiseToAmount, raisePot, between } = require('./common'); // pokersolver のカードのランク: 2 → 1 … A → 13。役のランク: ハイカード 1、ワンペア 2、ツーペア 3 … const PAIR = 2; const TWO_PAIR = 3; const rankOf = (card) => '23456789TJQKA'.indexOf(card[0]) + 1; function randomStyle(rng) { return { callCapBb: between(rng, 4, 9), // プリフロップでコールする上限(BB) stickiness: between(rng, 0.8, 1.5), // ペアでコールするベットの上限(ポットに対する割合) looseness: between(rng, 0.15, 0.4), // 何もなくても小さいベットにはコールする頻度 bluff: between(rng, 0.05, 0.2), // ドローでベットする頻度 betSize: between(rng, 0.5, 0.85), }; } function preflop(hand, seat, s, style, rng) { const { o, p, stack, bb, raises } = s; const solved = Hand.solve(p.cards); const [hi, lo] = p.cards.map(rankOf).sort((a, b) => b - a); const pair = solved.rank === PAIR; const suited = p.cards[0][1] === p.cards[1][1]; const premium = (pair && hi >= 10) || (hi === 13 && lo === 12); // JJ 以上・AK const playable = pair || hi === 13 || lo >= 9 || suited || (hi - lo <= 2 && lo >= 4); // スタックが 10BB 以下なら、ペア・エース・プレミアムはオールイン if (stack + p.bet <= 10 * bb) { if (pair || hi === 13 || premium) return o.canRaise ? allIn(o) : CALL; return passive(o); } if (premium && o.canRaise && raises < 2) { return raiseToAmount(hand, seat, raises === 0 ? Math.max(3 * bb, hand.currentBet * 3) : hand.currentBet * 3); } if (premium) return keepGoing(o); if (playable && o.toCall <= style.callCapBb * bb) { // たまにリンプではなく小さくオープンする if (raises === 0 && o.canRaise && rng() < 0.15) return raiseToAmount(hand, seat, 2.5 * bb); return keepGoing(o); } return passive(o); } // フラッシュドローかストレートドロー(オープンエンド)があるか。自分の手札を使うものだけ function hasDraw(cards, board) { if (board.length >= 5) return false; const all = cards.concat(board); for (const suit of 'shdc') { const n = all.filter((c) => c[1] === suit).length; if (n === 4 && cards.some((c) => c[1] === suit)) return true; } const ranks = new Set(all.map(rankOf)); if (ranks.has(13)) ranks.add(0); // A は 1 としても数える const mine = new Set(cards.map(rankOf)); for (let lo = 0; lo <= 9; lo++) { const run = [lo, lo + 1, lo + 2, lo + 3]; if (run.every((r) => ranks.has(r)) && run.some((r) => mine.has(r) || (r === 0 && mine.has(13)))) return true; } return false; } function decide(hand, seat, { style, rng = Math.random } = {}) { const s = situation(hand, seat); if (!s) return null; if (s.preflop) return preflop(hand, seat, s, style, rng); const { o, p, pot, raises } = s; const mine = Hand.solve(p.cards.concat(hand.board)); const onBoard = Hand.solve(hand.board); const improved = mine.rank > onBoard.rank; // 自分の手札で役が上がっている const strong = improved && mine.rank >= TWO_PAIR; const boardTop = Math.max(...hand.board.map(rankOf)); const topPair = improved && mine.rank === PAIR && mine.cards[0].rank >= boardTop; const pair = improved && mine.rank === PAIR; const draw = hasDraw(p.cards, hand.board); if (o.canCheck) { if (strong && o.canRaise) return raisePot(hand, seat, style.betSize); if (topPair && o.canRaise && rng() < 0.6) return raisePot(hand, seat, style.betSize * 0.7); if (draw && o.canRaise && rng() < style.bluff) return raisePot(hand, seat, 0.5); return CHECK; } const bet = o.toCall / Math.max(1, pot - o.toCall); // ポットに対するベットの大きさ if (strong) return o.canRaise && raises < 2 ? raisePot(hand, seat, style.betSize) : CALL; if (pair && bet <= style.stickiness * (topPair ? 1.3 : 0.8)) return CALL; if (draw && bet <= 0.7) return CALL; if (bet <= 0.35 && rng() < style.looseness) return CALL; return FOLD; } module.exports = { decide, randomStyle, hasDraw };