@idealic/poker-engine
Version:
Poker game engine and hand evaluator
123 lines (106 loc) • 4.22 kB
text/typescript
import type { Game } from '../Game';
import type { Player } from '../types';
import { getRemainingPlayers } from './position';
import { isShowdown } from './showdown';
/**
* Determines if the betting round is complete.
* Betting is complete if:
* - Only one active player remains
* - All active players are all-in
* - All active players have matched the highest bet or are all-in
*/
export function completeBetting(game: Game) {
const activePlayers = getRemainingPlayers(game);
const highestBet = Math.max(...game.players.map((p: Player) => p.totalBet));
if (
activePlayers.length < 2 ||
activePlayers.every((p: Player) => p.isAllIn) ||
activePlayers.every((p: Player) => (p.totalBet === highestBet && p.hasActed) || p.isAllIn) ||
game.isRunOut
) {
game.isBettingComplete = true;
game.isShowdown ||= isShowdown(game);
if (game.isShowdown) {
resetBettingState(game);
}
}
}
/**
* Updates player state when matching a bet
*/
export function matchBet(game: Game, playerIndex: number, targetAmount: number): void {
setPlayerBet(game, playerIndex, targetAmount);
const player = game.players[playerIndex];
// Player has acted if they matched the bet or went all-in
player.hasActed = player.roundBet === targetAmount || player.isAllIn;
}
/**
* Updates player state when making a new bet
*/
export function makeBet(game: Game, playerIndex: number, targetAmount: number) {
// Calculate the raise amount
const previousBet = game.lastCompleteBet;
const raiseAmount = addWithPrecision(targetAmount, -previousBet);
// The minimum allowable raise increment is stored in game.minBet
const minRaiseIncrement = game.minBet;
const player = game.players[playerIndex];
const isFacingIncompleteRaise = game.bet > (game.lastCompleteBet || 0);
const hasActedOnLastFullBet = player.roundBet >= (game.lastCompleteBet || 0);
setPlayerBet(game, playerIndex, targetAmount);
if (raiseAmount >= minRaiseIncrement && (!isFacingIncompleteRaise || !hasActedOnLastFullBet)) {
// Update the minimum raise increment for future raises
game.minBet = raiseAmount;
game.lastCompleteBet = targetAmount;
}
// Reset bettingComplete since we have a new bet
game.isBettingComplete = false;
const correctedAction = `p${playerIndex + 1} cbr ${targetAmount}`;
game.lastBetAction = correctedAction;
player.hasActed = true;
// Reset hasActed for all non-all-in players except the bettor
// This starts a new betting round, so everyone needs to act again
game.players.forEach((p: Player, i: number) => {
// Only reset hasActed for players who:
// 1. Are not the bettor
// 2. Have not folded
// 3. Are not all-in
// 4. Have a lower bet than the current bet (need to act on the raise)
if (i !== playerIndex && !p.hasFolded && !p.isAllIn && p.roundBet < game.bet) {
p.hasActed = false;
}
});
}
export function setPlayerAnte(game: Game, playerIndex: number, amount: number) {
const player = game.players[playerIndex];
player.stack = addWithPrecision(player.stack, -amount);
game.pot = addWithPrecision(game.pot, amount);
}
export function setPlayerBet(game: Game, playerIndex: number, absoluteAmount: number) {
const player = game.players[playerIndex];
const amount = Math.min(absoluteAmount - player.roundBet, player.stack);
player.stack = addWithPrecision(player.stack, -amount);
player.roundBet = addWithPrecision(player.roundBet, amount);
player.totalBet = addWithPrecision(player.totalBet, amount);
player.roundInvestments = player.roundBet;
player.totalInvestments = player.totalBet;
player.isAllIn = player.stack == 0;
game.pot = addWithPrecision(game.pot, amount);
game.bet = Math.max(game.bet, player.roundBet);
}
export function addWithPrecision(a: number, b: number) {
return Math.round((a + b) * 10000) / 10000;
}
/**
* Helper: resetBettingState
* Resets all betting-related state when a hand is complete
*/
// todo: use it everywhere
export function resetBettingState(game: Game): void {
game.minBet = game.bigBlind;
game.lastCompleteBet = game.minBet;
game.bet = 0;
game.players.forEach((p: Player) => {
p.roundBet = 0;
p.roundAction = null;
});
}