This is a Rust library to assist in creating your own blackjack trainer or game. For example, I was able to recreate some basic evidence evidence that card counting works:
This is with a $100k bankroll for both players and a bet spread from $1-$160 dollars for the AP. You can check out the example code for more detail on the exact betting.
Below is a minimal example just to get you started with manipulating blackjack hands:
use shoo::{Card, Hand, Rank, Suit};
#[test]
fn test_hand() {
let mut hand = Hand::default();
hand.insert(Card::new(Suit::Spade, Rank::Ace));
hand.insert(Card::new(Suit::Heart, Rank::King));
assert!(hand.value().is_some());
assert!(hand.blackjack());
}With the Strategy trait and Simulator struct, you can test and compare how strategies perform at the blackjack table. Let's try two different strategies: AlwaysStand and SometimesHit
use shoo::strategy::{Decision, Strategy};
use shoo::{Bet, Card, Hand, Rank, table::Outcome};
pub struct AlwaysStand;
impl Strategy for AlwaysStand {
fn bet(&self) -> Vec<Bet> {
vec![Bet::from(shoo::Chip::Five); self.max_hands()]
}
fn decide(&self, _hand: &Hand, _upcard: Card) -> Decision {
Decision::Stand
}
fn process(&mut self, _hands: &[Hand], _dealer: &Hand, _outcomes: &[Outcome]) {}
fn shoe_shuffled(&mut self) {}
fn max_hands(&self) -> usize {
2
}
fn name(&self) -> String {
"AlwaysStand".to_string()
}
}A better strategy would use hitting, splitting, doubling of course. Here is something that is vaguely close to basic strategy without counting:
pub struct SometimesHit;
impl Strategy for SometimesHit {
fn bet(&self) -> Vec<Bet> {
vec![Bet::from(shoo::Chip::Five); self.max_hands()]
}
fn decide(&self, hand: &Hand, upcard: Card) -> Decision {
match hand.value() {
Some(11) | Some(10) if !matches!(upcard.rank(), Rank::Ace | Rank::Ten) => {
Decision::Double
}
Some(v) if v <= 16 && hand.is_pair() && upcard.rank() <= Rank::Six => {
Decision::Split
}
Some(v) if v <= 11 => Decision::Hit,
Some(v) if v <= 16 && upcard.rank() >= Rank::Seven => Decision::Hit,
_ => Decision::Stand,
}
}
fn process(&mut self, _hands: &[Hand], _dealer: &Hand, _outcomes: &[Outcome]) {}
fn shoe_shuffled(&mut self) {}
fn max_hands(&self) -> usize {
2
}
fn name(&self) -> String {
"SometimesHit".to_string()
}
}If you want to simulate advantage play, a.k.a. card counting, you should add that within the process() hook. After every hand, the strategy can inspect the winning/losing hands and the dealer hand. Here you would consume the individual cards and add them to the running count. It is as simple as adding a card counter member field. You can even vary how many hands you want to play in bet(). I will leave those strategy implementations as an exercise to the reader. Lastly, you can simulate these strategies at a table:
use shoo::strategy::Simulator;
// Bankroll of 10k units
let bankroll: Bet = Bet::from(10000);
// Create a simulator with a strategy, bankroll, number decks, and deck penetration
let mut simulator = Simulator::new(AlwaysStand, bankroll.clone(), 6, 0.5);
// Add a second strategy with its own bankroll
sim.insert(SometimesHitTwo, bankroll);
// Run a simulation with max number of games
let res = sim.simulate(50000);
// res[t][i] is the bankroll state of strategy `i` at iteration `t`Some features of Blackjack rulesets are not modeled by this application. Two things that come to mind are insurance bets, resplitting aces, and hit or stand on soft 17. This library was programmed with Pennsylvania Blackjack in mind, but rule differences and insurance bets should eventually be modeled.