casino
Version:
A module for simulating and playing Blackjack games
921 lines (837 loc) • 26.1 kB
JavaScript
const CARDS = {
'A': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'10': 10,
'J': 10,
'Q': 10,
'K': 10
};
const SUITS = ['S', 'C', 'D', 'H'];
const CARD_ARRAY = SUITS.reduce((arr, suit) => {
return arr.concat(Object.keys(CARDS).map(card => suit + card));
}, []);
const BASIC_STRATEGY_MATRIX = {
// Uses value (1) to refer to Ace
HARD: {
'8': {
'2': 'HIT',
'3': 'HIT',
'4': 'HIT',
'5': 'HIT',
'6': 'HIT',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
'9': {
'2': 'HIT',
'3': 'DD',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
'10': {
'2': 'DD',
'3': 'DD',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'DD',
'8': 'DD',
'9': 'DD',
'10': 'HIT',
'1': 'HIT'
},
'11': {
'2': 'DD',
'3': 'DD',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'DD',
'8': 'DD',
'9': 'DD',
'10': 'DD',
'1': 'HIT'
},
'12': {
'2': 'HIT',
'3': 'HIT',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
'13': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
'14': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
'15': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'SURRENDER',
'1': 'HIT'
},
'16': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'HIT',
'8': 'HIT',
'9': 'SURRENDER',
'10': 'SURRENDER',
'1': 'SURRENDER'
},
'17': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'STAND',
'8': 'STAND',
'9': 'STAND',
'10': 'STAND',
'1': 'SURRENDER'
}
},
SOFT: {
// A2
'3': {
'2': 'HIT',
'3': 'HIT',
'4': 'HIT',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A3
'4': {
'2': 'HIT',
'3': 'HIT',
'4': 'HIT',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A4
'5': {
'2': 'HIT',
'3': 'HIT',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A5
'6': {
'2': 'HIT',
'3': 'HIT',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A6
'7': {
'2': 'HIT',
'3': 'DD',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'HIT',
'8': 'HIT',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A7
'8': {
'2': 'STAND',
'3': 'DD',
'4': 'DD',
'5': 'DD',
'6': 'DD',
'7': 'STAND',
'8': 'STAND',
'9': 'HIT',
'10': 'HIT',
'1': 'HIT'
},
// A8
'9': {
'2': 'STAND',
'3': 'STAND',
'4': 'STAND',
'5': 'STAND',
'6': 'STAND',
'7': 'STAND',
'8': 'STAND',
'9': 'STAND',
'10': 'STAND',
'1': 'STAND'
}
},
PAIR: {
// 22
'4': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'7': 'SPLIT'
},
// 33
'6': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'7': 'SPLIT'
},
// 44
'8': {
'5': 'SPLIT',
'6': 'SPLIT'
},
// 66
'12': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT'
},
// 77
'14': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'7': 'SPLIT'
},
// 88
'16': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'7': 'SPLIT',
'8': 'SPLIT',
'9': 'SPLIT',
'10': 'SPLIT'
},
// 99
'18': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'8': 'SPLIT',
'9': 'SPLIT'
},
// AA
'2': {
'2': 'SPLIT',
'3': 'SPLIT',
'4': 'SPLIT',
'5': 'SPLIT',
'6': 'SPLIT',
'7': 'SPLIT',
'8': 'SPLIT',
'9': 'SPLIT',
'10': 'SPLIT',
'1': 'SPLIT'
}
}
};
/**
* Stateless Blackjack functionality.
* To simulate a table session, use new Blackjack.Table()
* @namespace
*/
class Blackjack {
/**
* Creates an array of card decks (based on deckCount) and shuffles them
* @param {string} deckCount Number of decks in shuffle
* @returns {array}
*/
static shuffle (deckCount) {
let deck = CARD_ARRAY.slice();
while (--deckCount) {
deck = deck.concat(CARD_ARRAY);
}
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
return deck;
}
/**
* Determines the sum of a hand in the form of ['SA', 'C6'] (Ace of Spades, Six of Clubs)
* @param {array} hand The hand of the player
* @returns {number}
*/
static handSum (hand) {
return hand.reduce((sum, card) => {
// remove suit from handSum
return sum + CARDS[card.substr(1)];
}, 0);
}
/**
* Gets the card value the dealer is showing
* @param {array} dealerHand The hand of the dealer
* @returns {number}
*/
static dealerShow (dealerHand) {
return CARDS[dealerHand[dealerHand.length - 1].substr(1)];
}
/**
* Determines if hand is a Blackjack
* @param {array} hand The hand of the player
* @returns {boolean}
*/
static isBlackjack (hand) {
return this.hasAce(hand) && this.handSum(hand) === 11;
}
/**
* Determines if hand contains an ace
* @param {array} hand The hand of the player
* @returns {boolean}
*/
static hasAce (hand) {
// SA would be ace of spades, so remove first character
return hand.filter(card => card.substr(1) === 'A').length > 0;
}
/**
* Determines the optimal move a player should take based on what the dealer
* happens to be showing: note that this is non-European play (it's assumed
* that dealer has one face-down card)
* @param {object} matrix The strategy matrix to use
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @returns {string} = one of ['STAND', 'HIT', 'DD', 'SURRENDER']
*/
static playerIteration (matrix, playerHand, dealerHand) {
let playerSum = this.handSum(playerHand);
let dealerShow = this.dealerShow(dealerHand);
if (this.hasAce(playerHand) && playerSum >= 3 && playerSum <= 9) {
if (!matrix.SOFT[playerSum][dealerShow]) {
throw new Error(`Cannot find SOFT strategy for Player: ${playerSum} vs Dealer: ${dealerShow}`);
}
return matrix.SOFT[playerSum][dealerShow];
} else if (this.hasAce(playerHand) && playerSum >= 10 && playerSum <= 11) {
// Always stand on A9, A10 equivalents
return 'STAND';
} else if (playerSum <= 7) {
// Always hit under 7
return 'HIT';
} else if (playerSum >= 18) {
// Always stand on hard 18 or greater
return 'STAND';
} else if (!matrix.HARD[playerSum][dealerShow]) {
throw new Error(`Cannot find HARD strategy for Player: ${playerSum} vs Dealer: ${dealerShow}`);
} else {
return matrix.HARD[playerSum][dealerShow];
}
}
/**
* Determines the optimal move a dealer should make, independent of other hands
* @param {array} dealerHand The hand of the player
* @returns {string} = one of ['STAND', 'HIT']
*/
static dealerIteration (dealerHand) {
let dealerSum = this.handSum(dealerHand);
let hasAce = this.hasAce(dealerHand);
if (dealerSum >= 17 || (hasAce && dealerSum >= 8 && dealerSum <= 11)) {
return 'STAND'
} else {
return 'HIT';
}
}
/**
* Automatically simulates a Blackjack hand using player and dealer strategies
* from .playerIteration and .dealerIteration functions
* NOTE: This function ignores bankroll limitations and will draw from
* bankroll into negative in current implementation
* @param {object} matrix The strategy matrix to use
* @param {array} deck The deck of cards to draw from
* @param {number} bankroll The player's bankroll
* @param {number} bet The amount being bet
* @param {number} blackjackPayout The payout when a player gets a blackjack, usually 3/2 or 6/5
* @param {boolean} allowSurrender Is the player allowed to Surrender
* @param {number} maxSplit Maximum number of times player is allowed to split, usually 2 - 4
* @param {boolean} debug Debug mode: enables logging if true
* @returns {number} -'ve for loss, +'ve for win, 0 for push overall
*/
static playHand (matrix, deck, bankroll, bet, blackjackPayout = 3 / 2, allowSurrender = true, maxSplit = 4, debug = false) {
if (bet <= 0) {
throw new Error('Bet must be positive and greater than zero');
}
let bets = [bet];
let splitAces = []
let hands = this.setupHands(deck);
let playerHands = [hands.player]
let dealerHand = hands.dealer;
debug && console.log('PLAYING HAND...');
debug && console.log('Dealer', dealerHand);
debug && console.log('Player', playerHands[0]);
// Assume dealer checks blackjack first (American)
if (this.checkBlackjack(playerHands[0], dealerHand)) {
return this.blackjackResult(playerHands[0], dealerHand, bet, blackjackPayout, debug);
}
let handIndex = 0;
do {
let splitAce = false;
let hasSplit = false;
let playerHand = playerHands[handIndex];
bets[handIndex] = bet;
bankroll -= bet;
// If we split on a previous iteration, add a card
if (playerHand.length === 1) {
debug && console.log(`Player receives card on split hand ${handIndex}`);
splitAce = this.hasAce(playerHand);
playerHand.push(deck.pop());
debug && console.log(playerHand);
hasSplit = true;
}
while (!splitAce && this.shouldSplit(matrix, playerHand, dealerHand) && playerHands.length < maxSplit) {
debug && console.log('Player splits.');
// Add it to the hand stack
playerHands.push([playerHand.pop()]);
debug && console.log(`Player receives card on split hand ${handIndex}`);
// Add another card to see if we can split again
splitAce = this.hasAce(playerHand);
playerHand.push(deck.pop());
hasSplit = true;
}
// Record if we split an Ace on this hand
splitAces[handIndex] = splitAce;
if (splitAce) {
debug && console.log(`Player turn complete after splitting Aces`);
handIndex++;
continue;
}
// Hit it until we no longer can
let iterationResult;
let iterationCount = 0;
while ((iterationResult = this.playerIteration(matrix, playerHand, dealerHand)) !== 'STAND') {
debug && console.log(`[${handIndex}] Player does:`, iterationResult);
playerHand.push(deck.pop());
debug && console.log(playerHand);
if (!iterationCount) {
// If first play
if (iterationResult === 'DD') {
debug && console.log(`[${handIndex}] Player doubles down!`);
bets[handIndex] += bet;
bankroll -= bet;
} else if (!hasSplit && allowSurrender && iterationResult === 'SURRENDER') {
// If we allow surrender, player surrenders, otherwise keeps
// card they just hit with and continues
debug && console.log(`[${handIndex}] Player surrenders.`);
playerHand.splice(0, playerHand.length);
bets[handIndex] = bet / 2;
bankroll += bet / 2;
break;
}
}
iterationCount++;
}
debug && console.log(`[${handIndex}] Player stands`);
handIndex++;
} while (handIndex < playerHands.length);
dealerHand = this.dealerPhase(deck, dealerHand, debug);
debug && console.log('Dealer stands');
// Reduction step over all player hands to see net outcome
return playerHands.reduce((sum, playerHand, i) => {
// Only verify we did correct thing if we didn't split aces
if (!splitAces[i] && playerHand.length && !this.verifyHand(matrix, playerHand, dealerHand)) {
// Should never happen, footgun prevention when altering play calculations
console.log(`[${i}] Dealer :: `, dealerHand, this.dealerIteration(dealerHand));
console.log(`[${i}] Player :: `, playerHand, this.playerIteration(matrix, playerHand, dealerHand));
throw new Error(`[${i}] Invalid hand result`);
}
return sum + this.handResult(playerHand, dealerHand, bets[i], i, debug);
}, 0);
}
/**
* Creates an initial player and dealer hands in order from deck: P, D, P, D
* @param {array} deck The deck to draw from
* @returns {object} {player: [..], dealer: [..]}
*/
static setupHands (deck) {
let playerHand = [deck.pop()];
let dealerHand = [deck.pop()];
playerHand.push(deck.pop());
dealerHand.push(deck.pop());
return {
player: playerHand,
dealer: dealerHand
};
}
/**
* Determines if a player should split a hand. If no information is present,
* they will follow basic strategy instead (i.e. treat 5,5 as a 10)
* @param {object} matrix The strategy matrix to use
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @returns {boolean}
*/
static shouldSplit (matrix, playerHand, dealerHand) {
let playerSum = this.handSum(playerHand);
let dealerShow = this.dealerShow(dealerHand);
let shouldSplit = matrix.PAIR[playerSum] && matrix.PAIR[playerSum][dealerShow] === 'SPLIT'
return (
this.canSplit(playerHand) &&
matrix.PAIR[playerSum] &&
matrix.PAIR[playerSum][dealerShow] === 'SPLIT'
);
}
/**
* Determines if a player can split a hand.
* @param {array} playerHand The hand of the player
* @returns {boolean}
*/
static canSplit (playerHand) {
return (
playerHand.length === 2 &&
playerHand[0].substr(1) === playerHand[1].substr(1)
);
}
/**
* Checks whether there's a blackjack in either player or dealer hand,
* used to immediately jump to conclusion of a hand before play commences
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @returns {boolean}
*/
static checkBlackjack (playerHand, dealerHand) {
return this.isBlackjack(playerHand) || this.isBlackjack(dealerHand);
}
/**
* Returns financial result of a blackjack hand
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @param {number} bet The amount wagered
* @param {number} blackjackPayout Typically 3/2 or 6/5
* @param {boolean} debug Enable to see logging
* @returns {number} +'ve win, -'ve loss, 0 push
*/
static blackjackResult (playerHand, dealerHand, bet, blackjackPayout, debug) {
let playerBlackjack = this.isBlackjack(playerHand);
let dealerBlackjack = this.isBlackjack(dealerHand);
if (playerBlackjack && dealerBlackjack) {
debug && console.log('Blackjack PUSH.');
return 0;
} else if (playerBlackjack) {
debug && console.log('Player Blackjack, player wins!');
return bet * blackjackPayout;
} else if (dealerBlackjack) {
debug && console.log('Dealer Blackjack, dealer wins.');
return -bet;
} else {
// Should never happen
console.log('Dealer :: ', dealerHand);
console.log('Player :: ', playerHand);
throw new Error('Invalid blackjack result, don\'t call .blackjackResult without a blackjack');
}
}
/**
* Automatically iterates through dealer phase of a Blackjack hand
* @param {array} deck The deck to draw from
* @param {array} dealerHand The dealer's hand
* @param {boolean} debug Enable to see logging
* @param {}
*/
static dealerPhase (deck, dealerHand, debug) {
let iterationResult;
while ((iterationResult = this.dealerIteration(dealerHand)) !== 'STAND') {
debug && console.log('Dealer does:', iterationResult);
dealerHand.push(deck.pop());
debug && console.log(dealerHand);
}
return dealerHand;
}
/**
* Peace of mind verification that a hand is at logical conclusion based on
* provided strategy tables.
* @param {object} matrix The strategy matrix to use
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @returns {boolean}
*/
static verifyHand (matrix, playerHand, dealerHand) {
let playerResult = this.playerIteration(matrix, playerHand, dealerHand.slice(0, 2));
let dealerResult = this.dealerIteration(dealerHand);
return (playerResult === 'STAND' || playerResult === 'SURRENDER') &&
dealerResult === 'STAND';
}
/**
* A more descriptive form of .verifyHand, used to provide feedback on optimal
* strategy if using library with a human player
* @param {object} matrix The strategy matrix to use
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @returns {object}
*/
static assessPlay (matrix, playerHand, dealerHand, action) {
let playerResult = this.playerIteration(matrix, playerHand, dealerHand.slice(0, 2));
if (playerResult !== action) {
return {assessment: false, suggested: playerResult, actual: action};
} else {
return {assessment: true, suggested: playerResult, actual: action};
}
}
/**
* Calculates the financial result of a hand
* @param {array} playerHand The hand of the player
* @param {array} dealerHand The hand of the dealer
* @param {number} bet The amount wagered on the hand
* @param {number} index The index of the hand in the case of splits
* @param {boolean} verify Whether or not to verify perfect play (simulation footgun prevention)
* @param {boolean} debug Enable to view logging
* @returns {number} +'ve win, -'ve loss, 0 push
*/
static handResult (playerHand, dealerHand, bet, index = 0, debug = false) {
// If player has no cards, it means they surrendered.
// bet should be adjusted to 1/2 bet beforehand
if (!playerHand.length) {
return -bet; // Surrender
}
let playerSum = this.handSum(playerHand);
let dealerSum = this.handSum(dealerHand);
if (this.hasAce(playerHand) && (playerSum + 10 <= 21)) {
playerSum += 10;
}
if (this.hasAce(dealerHand) >= 0 && (dealerSum + 10 <= 21)) {
dealerSum += 10;
}
debug && console.log(`[${index}] Dealer sum`, dealerSum);
debug && console.log(`[${index}] Player sum`, playerSum);
if (playerSum > 21) {
debug && console.log(`[${index}] Player bust, dealer wins.`);
return -bet;
} else if (dealerSum > 21) {
debug && console.log(`[${index}] Dealer bust, player wins!`);
return bet;
} else if (playerSum > dealerSum) {
debug && console.log(`[${index}] Player wins!`);
return bet;
} else if (dealerSum > playerSum) {
debug && console.log(`[${index}] Dealer wins.`);
return -bet;
} else if (dealerSum === playerSum) {
debug && console.log(`[${index}] PUSH`);
return 0;
} else {
// Should never happen, footgun prevention
console.log(`[${index}] Dealer :: `, dealerHand);
console.log(`[${index}] Player :: `, playerHand);
throw new Error(`[${index}] Invalid Blackjack event`);
}
}
}
/**
* Simulates Blackjack table sessions with custom rulesets
* @class
*/
Blackjack.Table = class Table {
/**
* Creates a Table session simulator with specified options
* @param {object} options An object of options
* @param {object} options.matrix The simulation strategy matrix to use
* @param {number} options.deckCount The number of decks to use
* @param {number} options.initialBankroll Initial bankroll when seated
* @param {number} options.targetBankroll Target bankroll
* (with stategy 'underTarget')
* @param {number} options.minimumBet Minimum betting unit
* @param {number} options.strategy Progressive betting strategy
* = ['minimum', 'double', 'linear']
* @param {number} options.maxHands Target hand count
* (with strategy 'underMaxHands')
* @param {number} options.playRequirement Simulated play style -
* under which conditions does player remain at table?
* = ['underTarget', 'underMaxHands', 'underTargetAndMaxHands']
* @param {number} options.blackjackPayout Typically 3 / 2 or 6 /5
* @param {number} options.allowSurrender Can player surrender?
* @param {number} options.maxSplit Number of times player can split, 2 - 4
* @param {number} options.debug Enable logging for play
* @returns Blackjack.Table
*/
constructor (options = {}) {
options.matrix = options.matrix || BASIC_STRATEGY_MATRIX;
options.deckCount = options.deckCount || 6;
options.initialBankroll = options.initialBankroll || 100;
options.targetBankroll = options.targetBankroll || 200;
options.minimumBet = options.minimumBet || 5;
options.strategy = this.constructor.strategies[options.strategy] ?
options.strategy : this.constructor.defaultStrategy;
options.maxHands = options.maxHands || null;
options.playRequirement = this.constructor.playRequirements[options.playRequirement] ?
options.playRequirement : this.constructor.defaultPlayRequirement;
options.blackjackPayout = options.blackjackPayout || 3 / 2;
options.allowSurrender = !!options.allowSurrender || true;
options.maxSplit = parseInt(options.maxSplit) || 4;
options.maxSplit = Math.max(Math.min(options.maxSplit, 4), 1);
options.debug = !!options.debug || false;
this.options = options;
}
/**
* Runs a table simulation and provides statistics of session based on provided
* options during instantiation
* @returns {object}
*/
simulate () {
let debug = this.options.debug;
let matrix = this.options.matrix;
let deckCount = this.options.deckCount;
let initialBankroll = this.options.initialBankroll;
let targetBankroll = this.options.targetBankroll;
let minimumBet = this.options.minimumBet;
let bettingStrategy = this.options.strategy;
let maxHands = this.options.maxHands;
let fnStrategy = this.constructor.strategies[this.options.strategy];
let fnPlayRequirement = this.constructor.playRequirements[this.options.playRequirement];
let blackjackPayout = this.options.blackjackPayout;
let allowSurrender = this.options.allowSurrender;
let maxSplit = this.options.maxSplit;
let bankroll = initialBankroll;
let bet = minimumBet;
let handCount = 0;
let winCount = 0;
let lossCount = 0;
let drawCount = 0;
let deck = Blackjack.shuffle(deckCount);
while (
bankroll >= minimumBet &&
fnPlayRequirement(handCount, maxHands, bankroll, initialBankroll, targetBankroll)
) {
let result = Blackjack.playHand(matrix, deck, bankroll, bet, blackjackPayout, allowSurrender, maxSplit, debug);
if (result < 0) {
lossCount++;
} else if (result > 0) {
winCount++;
} else {
drawCount++;
}
handCount++;
bankroll += result;
bet = Math.min(fnStrategy(result > 0, bet, minimumBet), bankroll);
if (deck.length < 26) {
deck = Blackjack.shuffle(deckCount);
}
}
debug && console.log(`Played ${handCount} hands.`);
return {
bankroll: bankroll,
hands: handCount,
wins: winCount,
losses: lossCount,
draws: drawCount,
minTime: handCount / this.constructor.maxHandsPerHour,
maxTime: handCount / this.constructor.minHandsPerHour,
bankrupt: bankroll < minimumBet,
houseTake: (initialBankroll - bankroll),
amountBet: (initialBankroll)
};
}
};
/**
* A list of strategies for progressive betting: what is my next bet based on
* my previous bet and outcome of the hand?
*/
Blackjack.Table.strategies = {
minimum: (win, lastBet, minimumBet) => minimumBet,
double: (win, lastBet, minimumBet) => win ? minimumBet : lastBet * 2,
linear: (win, lastBet, minimumBet) => win ? minimumBet : lastBet + minimumBet
};
/**
* Table simulation play requirements: under which circumstances do I continue
* to play hands? (Have not lost bankroll is a given)
*/
Blackjack.Table.playRequirements = {
underTarget: (handCount, maxHands, bankroll, initialBankroll, targetBankroll) => {
return bankroll < targetBankroll;
},
underTargetAndMaxHands: (handCount, maxHands, bankroll, initialBankroll, targetBankroll) => {
return bankroll < targetBankroll && handCount < maxHands;
},
underMaxHands: (handCount, maxHands, bankroll, initialBankroll, targetBankroll) => {
return handCount < maxHands;
}
};
/**
* Defaults and statistics calculations
*/
Blackjack.Table.defaultStrategy = 'minimum';
Blackjack.Table.defaultPlayRequirement = 'underTarget';
Blackjack.Table.minHandsPerHour = 60;
Blackjack.Table.maxHandsPerHour = 120;
module.exports = Blackjack;