UNPKG

poker-ts

Version:

Texas Hold 'Em Poker table model with convenience features for running real games.

417 lines 20.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AutomaticAction = void 0; var deck_1 = __importDefault(require("./deck")); var community_cards_1 = __importDefault(require("./community-cards")); var dealer_1 = __importStar(require("./dealer")); var assert_1 = __importDefault(require("assert")); var bit_1 = require("../util/bit"); var player_1 = __importDefault(require("./player")); var AutomaticAction; (function (AutomaticAction) { AutomaticAction[AutomaticAction["FOLD"] = 1] = "FOLD"; AutomaticAction[AutomaticAction["CHECK_FOLD"] = 2] = "CHECK_FOLD"; AutomaticAction[AutomaticAction["CHECK"] = 4] = "CHECK"; AutomaticAction[AutomaticAction["CALL"] = 8] = "CALL"; AutomaticAction[AutomaticAction["CALL_ANY"] = 16] = "CALL_ANY"; AutomaticAction[AutomaticAction["ALL_IN"] = 32] = "ALL_IN"; })(AutomaticAction = exports.AutomaticAction || (exports.AutomaticAction = {})); var Table = /** @class */ (function () { function Table(forcedBets, numSeats) { if (numSeats === void 0) { numSeats = 9; } this._firstTimeButton = true; this._buttonSetManually = false; // has the button been set manually this._button = 0; assert_1.default(numSeats <= 23, 'Maximum 23 players'); this._numSeats = numSeats; this._forcedBets = forcedBets; this._tablePlayers = new Array(numSeats).fill(null); this._staged = new Array(numSeats).fill(false); this._deck = new deck_1.default(); } Table.prototype.playerToAct = function () { assert_1.default(this.bettingRoundInProgress(), 'Betting round must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.playerToAct(); }; Table.prototype.button = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.button(); }; Table.prototype.seats = function () { return this._tablePlayers; }; Table.prototype.handPlayers = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.players(); }; Table.prototype.numActivePlayers = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.numActivePlayers(); }; Table.prototype.pots = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.pots(); }; Table.prototype.forcedBets = function () { return this._forcedBets; }; Table.prototype.setForcedBets = function (forcedBets) { assert_1.default(!this.handInProgress(), 'Hand must not be in progress'); this._forcedBets = forcedBets; }; Table.prototype.numSeats = function () { return this._numSeats; }; Table.prototype.startHand = function (seat) { assert_1.default(!this.handInProgress(), 'Hand must not be in progress'); assert_1.default(this._tablePlayers.filter(function (player) { return player !== null; }).length >= 2, 'There must be at least 2 players at the table'); if (seat !== undefined) { this._button = seat; this._buttonSetManually = true; } this._staged = new Array(this._numSeats).fill(false); this._automaticActions = new Array(this._numSeats).fill(null); this._handPlayers = this._tablePlayers.map(function (player) { return player ? new player_1.default(player) : null; }); this.incrementButton(); this._deck.fillAndShuffle(); this._communityCards = new community_cards_1.default(); this._dealer = new dealer_1.default(this._handPlayers, this._button, this._forcedBets, this._deck, this._communityCards); this._dealer.startHand(); this.updateTablePlayers(); }; Table.prototype.handInProgress = function () { var _a, _b; return (_b = (_a = this._dealer) === null || _a === void 0 ? void 0 : _a.handInProgress()) !== null && _b !== void 0 ? _b : false; }; Table.prototype.bettingRoundInProgress = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.bettingRoundInProgress(); }; Table.prototype.bettingRoundsCompleted = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.bettingRoundsCompleted(); }; Table.prototype.roundOfBetting = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.roundOfBetting(); }; Table.prototype.communityCards = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._communityCards !== undefined); return this._communityCards; }; Table.prototype.legalActions = function () { assert_1.default(this.bettingRoundInProgress(), 'Betting round must be in progress'); assert_1.default(this._dealer !== undefined); return this._dealer.legalActions(); }; Table.prototype.holeCards = function () { assert_1.default(this.handInProgress() || this.bettingRoundsCompleted(), 'Hand must be in progress or showdown must have ended'); assert_1.default(this._dealer !== undefined); return this._dealer.holeCards(); }; Table.prototype.actionTaken = function (action, bet) { assert_1.default(this.bettingRoundInProgress(), 'Betting round must be in progress'); assert_1.default(this._dealer !== undefined); assert_1.default(this._automaticActions !== undefined); this._dealer.actionTaken(action, bet); while (this._dealer.bettingRoundInProgress()) { this.amendAutomaticActions(); var playerToAct = this.playerToAct(); var automaticAction = this._automaticActions[playerToAct]; if (automaticAction !== null) { this.takeAutomaticAction(automaticAction); this._automaticActions[playerToAct] = null; } else { break; } } if (this.bettingRoundInProgress() && this.singleActivePlayerRemaining()) { // We only need to take action for this one player, and the other automatic actions will unfold automatically. this.actPassively(); } this.updateTablePlayers(); }; Table.prototype.endBettingRound = function () { assert_1.default(!this.bettingRoundInProgress(), 'Betting round must not be in progress'); assert_1.default(!this.bettingRoundsCompleted(), 'Betting rounds must not be completed'); assert_1.default(this._dealer !== undefined); this._dealer.endBettingRound(); this.amendAutomaticActions(); this.updateTablePlayers(); this.clearFoldedBets(); }; Table.prototype.showdown = function () { assert_1.default(!this.bettingRoundInProgress(), 'Betting round must not be in progress'); assert_1.default(this.bettingRoundsCompleted(), 'Betting rounds must be completed'); assert_1.default(this._dealer !== undefined); this._dealer.showdown(); this.updateTablePlayers(); this.standUpBustedPlayers(); }; Table.prototype.winners = function () { var _a, _b; assert_1.default(!this.handInProgress(), 'Hand must not be in progress'); return (_b = (_a = this._dealer) === null || _a === void 0 ? void 0 : _a.winners()) !== null && _b !== void 0 ? _b : []; }; Table.prototype.automaticActions = function () { assert_1.default(this.handInProgress(), 'Hand must be in progress'); assert_1.default(this._automaticActions !== undefined); return this._automaticActions; }; Table.prototype.canSetAutomaticAction = function (seat) { assert_1.default(this.bettingRoundInProgress(), 'Betting round must be in progress'); assert_1.default(this._staged !== undefined); // (1) This is only ever true for players that have been in the hand since the start. // Every following sit-down is accompanied by a _staged[s] = true // (2) If a player is not seated at the table, he obviously cannot set his automatic actions. return !this._staged[seat] && this._tablePlayers[seat] !== null; }; Table.prototype.legalAutomaticActions = function (seat) { assert_1.default(this.canSetAutomaticAction(seat), 'Player must be allowed to set automatic actions'); assert_1.default(this._dealer !== undefined); // fold, all_in -- always viable // check, check_fold -- viable when biggest_bet - bet_size == 0 // call -- when biggest_bet - bet_size > 0 ("else" of the previous case) // call_only -- available always except when biggest_bet >= total_chips (no choice/"any" then) // // fallbacks: // check_fold -> fold // check -> nullopt // call_any -> check var biggestBet = this._dealer.biggestBet(); var player = this._tablePlayers[seat]; assert_1.default(player !== null); var betSize = player.betSize(); var totalChips = player.totalChips(); var legalActions = AutomaticAction.FOLD | AutomaticAction.ALL_IN; var canCheck = biggestBet - betSize === 0; if (canCheck) { legalActions |= AutomaticAction.CHECK_FOLD | AutomaticAction.CHECK; } else { legalActions |= AutomaticAction.CALL; } if (biggestBet < totalChips) { legalActions |= AutomaticAction.CALL_ANY; } return legalActions; }; Table.prototype.setAutomaticAction = function (seat, action) { assert_1.default(this.canSetAutomaticAction(seat), 'Player must be allowed to set automatic actions'); assert_1.default(seat !== this.playerToAct(), 'Player must not be the player to act'); assert_1.default(action === null || bit_1.bitCount(action) === 1, 'Player must pick one automatic action or null'); assert_1.default(action === null || action & this.legalAutomaticActions(seat), 'Given automatic action must be legal'); assert_1.default(this._automaticActions !== undefined); this._automaticActions[seat] = action; }; Table.prototype.sitDown = function (seat, buyIn) { assert_1.default(seat < this._numSeats && seat >= 0, 'Given seat index must be valid'); assert_1.default(this._tablePlayers[seat] === null, 'Given seat must not be occupied'); this._tablePlayers[seat] = new player_1.default(buyIn); this._staged[seat] = true; }; Table.prototype.standUp = function (seat) { assert_1.default(seat < this._numSeats && seat >= 0, 'Given seat index must be valid'); assert_1.default(this._tablePlayers[seat] !== null, 'Given seat must be occupied'); if (this.handInProgress()) { assert_1.default(this.bettingRoundInProgress()); assert_1.default(this._handPlayers !== undefined); if (seat === this.playerToAct()) { this.actionTaken(dealer_1.Action.FOLD); this._tablePlayers[seat] = null; this._staged[seat] = true; } else if (this._handPlayers[seat] !== null) { this.setAutomaticAction(seat, AutomaticAction.FOLD); this._tablePlayers[seat] = null; this._staged[seat] = true; if (this.singleActivePlayerRemaining()) { // We only need to take action for this one player, and the other automatic actions will unfold automatically. this.actPassively(); } } } else { this._tablePlayers[seat] = null; } }; Table.prototype.takeAutomaticAction = function (automaticAction) { assert_1.default(this._dealer !== undefined); assert_1.default(this._handPlayers !== undefined); var player = this._handPlayers[this._dealer.playerToAct()]; assert_1.default(player !== null); var biggestBet = this._dealer.biggestBet(); var betGap = biggestBet - player.betSize(); var totalChips = player.totalChips(); switch (automaticAction) { case AutomaticAction.FOLD: return this._dealer.actionTaken(dealer_1.Action.FOLD); case AutomaticAction.CHECK_FOLD: return this._dealer.actionTaken(betGap === 0 ? dealer_1.Action.CHECK : dealer_1.Action.FOLD); case AutomaticAction.CHECK: return this._dealer.actionTaken(dealer_1.Action.CHECK); case AutomaticAction.CALL: return this._dealer.actionTaken(dealer_1.Action.CALL); case AutomaticAction.CALL_ANY: return this._dealer.actionTaken(betGap === 0 ? dealer_1.Action.CHECK : dealer_1.Action.CALL); case AutomaticAction.ALL_IN: if (totalChips < biggestBet) { return this._dealer.actionTaken(dealer_1.Action.CALL); } return this._dealer.actionTaken(dealer_1.Action.RAISE, totalChips); default: assert_1.default(false); } }; // fold, all_in -- no need to fallback, always legal // check_fold, check -- (if the bet_gap becomes >0 then check is no longer legal) // call -- you cannot lose your ability to call if you were able to do it in the first place // call_any -- you can lose your ability to call_any, which only leaves the normal call (doubt cleared) // condition: biggest_bet >= total_chips Table.prototype.amendAutomaticActions = function () { assert_1.default(this._dealer !== undefined); assert_1.default(this._automaticActions !== undefined); assert_1.default(this._handPlayers !== undefined); var biggestBet = this._dealer.biggestBet(); for (var s = 0; s < this._numSeats; s++) { var automaticAction = this._automaticActions[s]; if (automaticAction !== null) { var player = this._handPlayers[s]; assert_1.default(player !== null); var isContested = this._dealer.isContested(); var betGap = biggestBet - player.betSize(); var totalChips = player.totalChips(); if (automaticAction & AutomaticAction.CHECK_FOLD && betGap > 0) { this._automaticActions[s] = AutomaticAction.FOLD; } else if (automaticAction & AutomaticAction.CHECK && betGap > 0) { this._automaticActions[s] = null; } /* else if (automaticAction & AutomaticAction.CALL && isContested) { this._automaticActions[s] = null }*/ else if (automaticAction & AutomaticAction.CALL_ANY && biggestBet >= totalChips) { this._automaticActions[s] = AutomaticAction.CALL; } } } }; // Make the current player act passively: // - check if possible or; // - call if possible. Table.prototype.actPassively = function () { assert_1.default(this._dealer !== undefined); var legalActions = this._dealer.legalActions(); if (legalActions.action & dealer_1.Action.BET) { this.actionTaken(dealer_1.Action.CHECK); } else { assert_1.default(legalActions.action & dealer_1.Action.CALL); this.actionTaken(dealer_1.Action.CALL); } }; Table.prototype.incrementButton = function () { assert_1.default(this._handPlayers !== undefined); if (this._buttonSetManually) { this._buttonSetManually = false; this._firstTimeButton = false; this._button = this._handPlayers[this._button] ? this._button : this._handPlayers.findIndex(function (player) { return player !== null; }); assert_1.default(this._button !== -1); } else if (this._firstTimeButton) { var seat = this._handPlayers.findIndex(function (player) { return player !== null; }); assert_1.default(seat !== -1); this._button = seat; this._firstTimeButton = false; } else { var offset = this._button + 1; var seat = this._handPlayers.slice(offset).findIndex(function (player) { return player !== null; }); this._button = seat !== -1 ? seat + offset : this._handPlayers.findIndex(function (player) { return player !== null; }); } }; Table.prototype.clearFoldedBets = function () { assert_1.default(this._handPlayers !== undefined); for (var s = 0; s < this._numSeats; s++) { var handPlayer = this._handPlayers[s]; var tablePlayer = this._tablePlayers[s]; if (!this._staged[s] && handPlayer === null && tablePlayer !== null && tablePlayer.betSize() > 0) { // Has folded bet assert_1.default(this._tablePlayers[s] !== null); this._tablePlayers[s] = new player_1.default(tablePlayer.stack()); } } }; Table.prototype.updateTablePlayers = function () { assert_1.default(this._handPlayers !== undefined); for (var s = 0; s < this._numSeats; s++) { var handPlayer = this._handPlayers[s]; if (!this._staged[s] && handPlayer !== null) { assert_1.default(this._tablePlayers[s] !== null); this._tablePlayers[s] = new player_1.default(handPlayer); } } }; // A player is considered active (in class table context) if // he started in the current betting round, has not stood up or folded. Table.prototype.singleActivePlayerRemaining = function () { var _this = this; assert_1.default(this.bettingRoundInProgress()); assert_1.default(this._dealer !== undefined); // What dealer::betting_round_players filter returns is all the players // who started the current betting round and have not folded. Players who // actually fold are manually discarded internally (to help with pot evaluation). var bettingRoundPlayers = this._dealer.bettingRoundPlayers(); var activePlayers = bettingRoundPlayers.filter(function (player, index) { return player !== null && !_this._staged[index]; }); return activePlayers.length === 1; }; Table.prototype.standUpBustedPlayers = function () { assert_1.default(!this.handInProgress()); for (var s = 0; s < this._numSeats; s++) { var player = this._tablePlayers[s]; if (player !== null && player.totalChips() === 0) { this._tablePlayers[s] = null; } } }; return Table; }()); exports.default = Table; //# sourceMappingURL=table.js.map