better split ace logic; more args

This commit is contained in:
Nick Pegg 2025-07-10 15:57:45 -07:00
parent 1106f1484f
commit b2115a4a7e

View file

@ -13,8 +13,8 @@ fn main() -> anyhow::Result<()> {
// TODO: Use anyhow for error handling, get rid of unwraps // TODO: Use anyhow for error handling, get rid of unwraps
let args = Args::parse(); let args = Args::parse();
match args.mode { match args.mode {
Mode::Interactive => interactive_play(args.show_count), Mode::Interactive => interactive_play(args),
Mode::OldMan => old_man(), Mode::OldMan => old_man(args),
} }
} }
@ -32,19 +32,23 @@ struct Args {
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
show_count: bool, show_count: bool,
#[arg(long, default_value_t = 6)]
decks: u8,
} }
fn interactive_play(show_count: bool) -> anyhow::Result<()> { fn interactive_play(args: Args) -> anyhow::Result<()> {
// TODO: Make a way to reset bank // TODO: Make a way to reset bank
let term = Term::stdout(); let term = Term::stdout();
let mut bank: u32 = load_bank()?.unwrap_or(1_000); let mut bank: u32 = load_bank()?.unwrap_or(1_000);
let mut table = Table::new(6, bank).with_hit_on_soft_17(); let mut table = Table::new(args.decks, bank).with_hit_on_soft_17();
let mut last_bet: Option<u32> = None; let mut last_bet: Option<u32> = None;
loop { loop {
println!("\nMoney in the bank: ${bank}"); println!("\nMoney in the bank: ${bank}");
if show_count { // TODO: show normalized card count
if args.show_count {
println!("Card count: {}", table.count()); println!("Card count: {}", table.count());
println!("Cards in shoe: {}", table.shoe_count()); println!("Cards in shoe: {}", table.shoe_count());
} }
@ -84,7 +88,7 @@ fn interactive_play(show_count: bool) -> anyhow::Result<()> {
println!(); println!();
let mut turn = table.deal_hand(bet); let mut turn = table.deal_hand(bet);
let split_turn = interactive_play_turn(&mut turn, &mut table, show_count)?; let split_turn = interactive_play_turn(&mut turn, &mut table, args.show_count)?;
if split_turn.is_none() { if split_turn.is_none() {
table.dealers_turn()?; table.dealers_turn()?;
let result = table.results(turn)?; let result = table.results(turn)?;
@ -92,7 +96,7 @@ fn interactive_play(show_count: bool) -> anyhow::Result<()> {
print_result(&result); print_result(&result);
} else { } else {
let mut split_turn = split_turn.unwrap(); let mut split_turn = split_turn.unwrap();
interactive_play_turn(&mut split_turn, &mut table, show_count)?; interactive_play_turn(&mut split_turn, &mut table, args.show_count)?;
table.dealers_turn()?; table.dealers_turn()?;
let result = table.results(turn)?; let result = table.results(turn)?;
let split_result = table.results(split_turn)?; let split_result = table.results(split_turn)?;
@ -215,7 +219,6 @@ fn print_result(result: &EndState) {
println!("You got: ${}", result.returns); println!("You got: ${}", result.returns);
} }
// TODO: Make this use a lookup table, this if logic is gnarly
fn basic_strategy( fn basic_strategy(
hand: &Hand, hand: &Hand,
dealer_showing: &Card, dealer_showing: &Card,
@ -229,9 +232,8 @@ fn basic_strategy(
// Got a pair, maybe should split // Got a pair, maybe should split
// We check can_split right at the get-go, because if we can't we just follow the Hard // We check can_split right at the get-go, because if we can't we just follow the Hard
// Total table. // Total table.
let aces = hand.cards()[0].value == "A";
match hand.value() { match hand.value() {
12 if aces => PlayerChoice::Split, 12 if hand.has_ace() => PlayerChoice::Split,
20 => PlayerChoice::Stand, 20 => PlayerChoice::Stand,
18 => match dv { 18 => match dv {
2..=6 => PlayerChoice::Split, 2..=6 => PlayerChoice::Split,
@ -353,7 +355,7 @@ fn basic_strategy_play_turn(
/// addition to points/comps which he would use to eat brunch for free. /// addition to points/comps which he would use to eat brunch for free.
/// ///
/// Does this actually work, or was the driver full of it? /// Does this actually work, or was the driver full of it?
fn old_man() -> anyhow::Result<()> { fn old_man(args: Args) -> anyhow::Result<()> {
const START: u32 = 100_000; const START: u32 = 100_000;
const PER_DAY: u32 = 400; const PER_DAY: u32 = 400;
const MIN_BET: u32 = 15; const MIN_BET: u32 = 15;
@ -361,7 +363,7 @@ fn old_man() -> anyhow::Result<()> {
// Walk away when we're up by this much // Walk away when we're up by this much
const MAX_WIN: u32 = 100; const MAX_WIN: u32 = 100;
// Walk away when we're down by this much // Walk away when we're down by this much
const MAX_LOSS: u32 = MAX_WIN; const MAX_LOSS: u32 = 100;
// Run sim for this many days // Run sim for this many days
const DAYS: u16 = 30; const DAYS: u16 = 30;
@ -372,7 +374,7 @@ fn old_man() -> anyhow::Result<()> {
for day in 1..=DAYS { for day in 1..=DAYS {
bank -= PER_DAY; bank -= PER_DAY;
let mut table = Table::new(6, PER_DAY).with_hit_on_soft_17(); let mut table = Table::new(args.decks, PER_DAY).with_hit_on_soft_17();
let mut rounds = 0; let mut rounds = 0;
while table.player_chips() > MIN_BET while table.player_chips() > MIN_BET
&& table.player_chips() < (PER_DAY + MAX_WIN) && table.player_chips() < (PER_DAY + MAX_WIN)
@ -465,6 +467,17 @@ mod tests {
true, true,
), ),
PlayerChoice::Stand, PlayerChoice::Stand,
) );
// Always split aces
assert_eq!(
basic_strategy(
&Hand::from([("", "A"), ("", "A")].to_vec()),
&Card::from(("", "2")),
true,
true,
),
PlayerChoice::Split,
);
} }
} }