Replace the hand-rolled betting rules, side pots, showdown and hand evaluator with the published @pokertools/engine and @pokertools/evaluator libraries (MIT, pinned to 2.1.0). - server/hand.js now wraps a PokerEngine per hand: it maps seats, mirrors stacks, staggers the all-in runout, and pays host-chosen winners on cash-only tables. - server/cards.js and server/ai.js use the library evaluator. - Out-of-turn folds go through the engine's stand; all-in hands stay live when the player leaves; uncalled bets return to a leaver. - The engine refuses some check-able folds that would leave a side pot without eligible players; those now return a message (or check for a leaving player) instead of crashing. - BB must be greater than SB (engine requirement); Node >= 24. - Tests rig cards via engine snapshots and cover the new edge cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.0 KiB
JavaScript
51 lines
2.0 KiB
JavaScript
'use strict';
|
||
|
||
// 役の判定は @pokertools/evaluator(@pokertools/engine のショーダウンと同じ判定器)を使う。
|
||
// ここでは表示用に、最強の5枚と日本語の役名を求める。
|
||
// カードは "As"(スペードのA), "Td"(ダイヤの10)のような2文字
|
||
const { evaluate, getCardCodes, rank, HandRank } = require('@pokertools/evaluator');
|
||
|
||
const HAND_NAMES = {
|
||
[HandRank.HighCard]: 'ハイカード',
|
||
[HandRank.OnePair]: 'ワンペア',
|
||
[HandRank.TwoPair]: 'ツーペア',
|
||
[HandRank.ThreeOfAKind]: 'スリーカード',
|
||
[HandRank.Straight]: 'ストレート',
|
||
[HandRank.Flush]: 'フラッシュ',
|
||
[HandRank.FullHouse]: 'フルハウス',
|
||
[HandRank.FourOfAKind]: 'フォーカード',
|
||
[HandRank.StraightFlush]: 'ストレートフラッシュ',
|
||
};
|
||
const ROYAL_FLUSH = 1; // 判定器のスコアは小さいほど強く、1 がロイヤルフラッシュ
|
||
|
||
// 5〜7枚の中で最も強い役。score は小さいほど強い
|
||
function bestHand(cards) {
|
||
const codes = getCardCodes(cards);
|
||
const score = evaluate(codes);
|
||
const name = score === ROYAL_FLUSH ? 'ロイヤルフラッシュ' : HAND_NAMES[rank(codes)];
|
||
// 役を作っている5枚(7枚から5枚を選ぶ21通りで、全体と同じ強さになる組)
|
||
let five = cards.slice(0, 5);
|
||
if (cards.length > 5) {
|
||
const n = cards.length;
|
||
const pick = (start, chosen) => {
|
||
if (chosen.length === 5) return evaluate(chosen.map((i) => codes[i])) === score ? chosen.slice() : null;
|
||
for (let i = start; i <= n - (5 - chosen.length); i++) {
|
||
chosen.push(i);
|
||
const found = pick(i + 1, chosen);
|
||
chosen.pop();
|
||
if (found) return found;
|
||
}
|
||
return null;
|
||
};
|
||
five = pick(0, []).map((i) => cards[i]);
|
||
}
|
||
return { score, cards: five, name };
|
||
}
|
||
|
||
// a が強ければ正、b が強ければ負、同じ強さなら 0
|
||
function compareScore(a, b) {
|
||
return b - a;
|
||
}
|
||
|
||
module.exports = { bestHand, compareScore };
|