Practice card counting using Hi-Lo and calculate EV for any table conditions
- Simulator mode for computing EV given some table conditions (10M hands / second)
- Game mode for practicing basic strategy and card counting with hints
- No package dependencies
- Runs in any JS environment (CLI, browser, React Native app etc)
- Multi-core support in Node
npm install -g @blackjacktrainer/blackjack-simulator
blackjack-simulator simulate --help
Override the number of CPU cores used:
CORES=1 blackjack-simulator simulate
blackjack-simulator game --help
See it live as a web app here.
npm install
nvm use --install
NODE_ENV=development npm run build
./bin/cli.js
import { Simulator } from '@blackjacktrainer/blackjack-simulator';
// Default settings:
const settings = {
debug: false,
// Simulator-only settings:
hands: 10 ** 7,
// Can be one of:
// 'basic-strategy': play perfect basic strategy
// 'basic-strategy-i18': play perfect basic strategy plus illustrious 18
// 'basic-strategy-i18-fab4': play perfect basic strategy plus illustrious 18 + fab 4
playerStrategy: 'basic-strategy-i18-fab4',
playerBetSpread: [1000, 2000, 4000, 8000, 16000],
playerSpots: [1, 1, 1, 1, 1],
playerTablePosition: 1,
playerBankroll: 1000 * 10 ** 7,
playerWongOutTrueCount: null,
// Table rules
allowDoubleAfterSplit: true,
allowLateSurrender: true,
allowResplitAces: false,
blackjackPayout: '3:2',
deckCount: 2,
hitSoft17: true,
maxHandsAllowed: 4,
maximumBet: 1000 * 100,
minimumBet: 1000,
playerCount: 1,
penetration: 0.75
};
const simulator = new Simulator(settings);
const result = simulator.run();
Result contains the following data:
{
amountEarned: number;
amountWagered: number;
bankrollMean: number;
bankrollRqd: number;
bankrollVariance: number;
handsLost: number;
handsPlayed: number;
handsPushed: number;
handsWon: number;
hoursPlayed: number;
riskOfRuin: number;
timeElapsed: number;
}
import {
Event,
Game,
GameStep,
PlayerInputReader
} from '@blackjacktrainer/blackjack-simulator';
// Default settings:
const settings = {
autoDeclineInsurance: false,
disableEvents: false,
checkDeviations: false,
// Can be one of 'default', 'pairs', 'uncommon', 'deviations'. If the mode is set to 'deviations', `checkDeviations`
// will be forced to true.
mode: 'default',
debug: false,
playerBankroll: 1000 * 10 ** 7,
playerTablePosition: 1,
playerStrategyOverride: {},
// Table rules
allowDoubleAfterSplit: true,
allowLateSurrender: false,
allowResplitAces: false,
blackjackPayout: '3:2',
deckCount: 2,
hitSoft17: true,
maxHandsAllowed: 4,
maximumBet: 1000 * 100,
minimumBet: 1000,
playerCount: 1,
penetration: 0.75
};
const game = new Game(settings);
// In a real app, this will likely be a React-redux store or a Vuex store.
const state = {};
// Called when any game state changes. `name` will be one of the following:
//
// - focusedHand
// - sessionMovesCorrect
// - sessionMovesTotal
// - playCorrection
// - step
// - shoe
// - discardTray
// - dealer
// - player
// - handWinner
game.on(Event.Change, (name, value) => {
state[name] = value;
});
game.on(Event.Shuffle, () => {
console.log('End of shoe, cards shuffled!');
});
// Emitted when the game wants to save optional game statistics.
// `entityName` can be one of `hand-result` or `move`.
// `data` is a plain object with values to save to the backend.
game.on(Event.CreateRecord, (entityName, data) => {
fetch(`/api/v1/${entityName}`, {
method: 'POST',
body: JSON.serialize(data),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
});
function stepGame(game, playerInputReader, input) {
const step = game.step(input);
if (
![
GameStep.WaitingForPlayInput,
GameStep.WaitingForInsuranceInput,
GameStep.WaitingForNewGameInput
].includes(step)
) {
return Promise.resolve();
}
return new Promise((resolve) => playerInputReader.readInput(resolve));
}
async function runGame(game) {
game.betAmount = 10 * 100;
const playerInputReader = new PlayerInputReader();
let input;
while (true) {
input = await stepGame(game, playerInputReader, input);
}
}
runGame(game);
PlayerInputReader.readInput
listens for a click
or keypress
event on document.body
. Your DOM just has to declare the following buttons
somewhere for user interaction:
{
game.state.step === GameStep.WaitingForPlayInput && (
<>
<button data-action="s">Stand (S)</button>
<button data-action="h">Hit (H)</button>
<button data-action="d">Double (D)</button>
<button data-action="r">Surrender (R)</button>
<button data-action="p">Split (P)</button>
</>
);
}
{
game.state.step === GameStep.WaitingForInsuranceInput && (
<>
<button data-action="n">No (N)</button>
<button data-action="y">Yes (Y)</button>
</>
);
}
{
game.state.step === GameStep.WaitingForNewGameInput && (
<>
<button data-action="d">Deal (press any key)</button>
</>
);
}