https://github.com/mhluska/blackjack-simulator
🃏 Realistic blackjack simulator (practice card counting using Hi-Lo and calculate EV for any table conditions)
https://github.com/mhluska/blackjack-simulator
blackjack blackjack-game blackjack-simulator cli javascript multiprocessing nodejs simulator typescript
Last synced: about 2 months ago
JSON representation
🃏 Realistic blackjack simulator (practice card counting using Hi-Lo and calculate EV for any table conditions)
- Host: GitHub
- URL: https://github.com/mhluska/blackjack-simulator
- Owner: mhluska
- License: mit
- Created: 2020-06-08T16:38:22.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2026-04-18T18:21:58.000Z (about 2 months ago)
- Last Synced: 2026-04-18T20:26:23.072Z (about 2 months ago)
- Topics: blackjack, blackjack-game, blackjack-simulator, cli, javascript, multiprocessing, nodejs, simulator, typescript
- Language: TypeScript
- Homepage: https://blackjacktrainer.app
- Size: 2.34 MB
- Stars: 66
- Watchers: 1
- Forks: 21
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Blackjack Simulator
> Practice card counting using Hi-Lo and calculate EV for any table conditions
## Features
- Simulator mode for computing EV given any table conditions
- [Game mode](https://blackjacktrainer.app) 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
## Usage
### Simulator mode
```sh
npm install -g @blackjacktrainer/blackjack-simulator
```
```sh
blackjack-simulator simulate --help
```
Override the number of CPU cores used:
```sh
CORES=1 blackjack-simulator simulate
```
### Game mode
```sh
blackjack-simulator game --help
```
See it live as a web app [here](https://blackjacktrainer.app).
### Build and run locally
```sh
npm install
nvm use --install
NODE_ENV=development npm run build
./bin/cli.js
```
### Use as a library (simulator mode)
```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, 1000, 4000, 7000, 10000],
playerSpots: [1, 1, 1, 1, 1],
playerTablePosition: 1,
playerBankroll: 1000 * 10 ** 7,
playerWongOutTrueCount: -Infinity,
// 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 simulator = new Simulator(settings);
const result = simulator.run();
```
Result contains the following data:
```
{
amountEarned: number;
amountWagered: number;
bankrollMean: number;
bankrollVariance: number;
handsLost: number;
handsPlayed: number;
handsPushed: number;
handsWon: number;
hoursPlayed: number;
riskOfRuin: number;
timeElapsed: number;
}
```
### Use as a library (game mode)
```js
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.stringify(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:
```jsx
{
game.state.step === GameStep.WaitingForPlayInput && (
<>
Stand (S)
Hit (H)
Double (D)
Surrender (R)
Split (P)
>
);
}
{
game.state.step === GameStep.WaitingForInsuranceInput && (
<>
No (N)
Yes (Y)
>
);
}
{
game.state.step === GameStep.WaitingForNewGameInput && (
<>
Deal (press any key)
>
);
}
```
## Performance
Tested on Apple M2 Max, Node v24.3, default settings (2-deck H17, I18 + Fab 4 deviations, bet spread).
| Cores | Hands | Time | Hands/sec |
|---|---|---|---|
| 1 | 100M | ~53s | ~1.9M |
| 4 | 1B | ~134s | ~7.5M |
| 12 | 1B | ~65s | ~15M |