Best Move Analysis

Command line options

-c CARDS, --cards CARDS
                      The cards the player has. (examples: A,10 or 2,8,4)
-d DEALER_CARD, --dealer-card DEALER_CARD
                      The up card of the dealer (examples: A or 3)
--splits SPLITS       How many times the player can split (min: 1=fastest, fairly accurate; max: 3=slowest, super
                      accurate; default: 1)
--decks DECKS         How many decks the shoe starts with. (default: 6)
--shoe SHOE           The cards in the shoe before the cards were dealt. Overrides --decks. (format:
                      2,6,5,8,6,2,A,10,9,...)
--stand17             Dealer should stand on soft 17. (default: true)
--hit17               Dealer should hit on soft 17. (default: false)
--das                 Allow double after split. (default: true)
--no-das              Don't allow double after split. (default: false)
--peek                Dealer peeks for blackjack. (default: true)
--no-peek             Dealer doesn't peek for blackjack. (default: false)
--surrender           Allow surrendering. (default: true)
--no-surrender        Don't allow surrendering. (default: false)

See the help by running python best_move.py -h.

Calculate the best action

best_move.perfect_mover_cache(cards, dealer_up_card, cards_not_seen, can_double=True, can_insure=True, can_surrender=True, max_splits=3, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True, return_all_profits=False, print_profits=False, plot_profits=False)

Increase cache hits in perfect_mover. Use this instead of perfect_mover for faster results.

Parameters:
  • cards (Iterable[int]) – The cards in the player’s hand.

  • dealer_up_card (int) – The dealer’s up card.

  • cards_not_seen (Iterable[int]) – The cards the player hasn’t already seen.

  • can_double (bool) – Whether the player can double.

  • can_insure (bool) – Whether the player can take insurance.

  • can_surrender (bool) – Whether the player can surrender.

  • max_splits (int) – How many times the player can split their hand.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

  • return_all_profits (bool) – Whether we should return the expected returns of all 6 possible actions, or only the best expected return, the action that gets this return, and the expected profit of taking insurance.

  • print_profits (bool) – Whether we should print the expected returns of all the actions.

  • plot_profits (bool) – Whether we should create a table showing the expected returns of all possible actions, with the best move highlighted.

Returns:

The expected returns of all 6 possible actions, or only the best expected return, the action that gets this return, and the expected profit of taking insurance.

Return type:

tuple[float, …] | tuple[float, str, float]

Example:

from best_move import perfect_mover_cache
from utils import DECK

shoe = DECK * 6
for card in (3, 5, 11):  # Remove the cards the player and the dealer have.
    shoe.remove(card)

expected_return, best_action, insurance_return = perfect_mover_cache(cards=(3, 5), dealer_up_card=11, cards_not_seen=shoe,
                                                                     can_double=True, can_insure=True, can_surrender=True,
                                                                     max_splits=3, dealer_peeks_for_blackjack=False, das=False,
                                                                     dealer_stands_soft_17=False, return_all_profits=False,
                                                                     print_profits=True, plot_profits=True)
best_move.perfect_mover(cards, dealer_up_card, cards_not_seen, can_double=True, can_insure=True, can_surrender=True, max_splits=3, dealer_peeks_for_blackjack=True, das=True, dealer_stands_soft_17=True)

Get the best move to play by taking into account even the cards that we have already seen.

Parameters:
  • cards (tuple[int, ...]) – The cards in the player’s hand.

  • dealer_up_card (int) – The dealer’s up card.

  • cards_not_seen (tuple[int, ...]) – The cards the player hasn’t already seen.

  • can_double (bool) – Whether the player can double.

  • can_insure (bool) – Whether the player can take insurance.

  • can_surrender (bool) – Whether the player can surrender.

  • max_splits (int) – How many times the player can split their hand.

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • das (bool) – Whether we can double after splitting.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

Returns:

The expected returns of all 6 possible actions (stand, hit, double, split, surrender, and insurance, in that order).

Return type:

tuple[float, …]

Calculate our chances of beating the dealer

best_move.chances_of_beating_dealer(hand_value, dealer_value, dealer_has_ace, counts, dealer_more_than_1_card, dealer_peeks_for_blackjack, dealer_stands_soft_17)

Assumes that if the player got 21, it wasn’t a blackjack. If it was a blackjack, then we don’t call this function.

If dealer_peeks_for_blackjack is true, then the dealer can’t have blackjack.

Parameters:
  • hand_value (int) – The player’s final hand value.

  • dealer_value (int) – The dealer’s hand value at the moment.

  • dealer_has_ace (bool) – Whether the dealer has an ace that is counted as 11.

  • counts (tuple[int, ...]) – How many times each card is in the deck. (e.g. (0, 0, 12, 11, 15, …))

  • dealer_more_than_1_card (bool) – Whether the dealer has more than 1 card (not including the down card).

  • dealer_peeks_for_blackjack (bool) – Whether the dealer peeks for blackjack.

  • dealer_stands_soft_17 (bool) – Whether the dealer stands on soft 17.

Returns:

The chances of beating the dealer.

Return type:

float

Adjust the probabilities if the dealer peeks for blackjack

best_move.probabilities_if_dealer_peeks_for_blackjack(counts, dealer_up_card)

Account for the fact that if the dealer has an ace and doesn’t have blackjack, then we know they don’t have a 10.

As such, our chances of getting a 10 are slightly higher and out chances of getting any other card are slightly lower. Similarly, if the dealer has a 10 and doesn’t have blackjack, then we know they don’t have an ace.

Parameters:
  • counts (dict[int, int]) – How many times each card is in the deck. (e.g. {2: 12, 3: 11, 4: 15, …})

  • dealer_up_card (int) – The dealer’s up card.

Returns:

The probabilities of getting each card if the dealer peeks for blackjack.

Return type:

dict[int, float]

Utilities

best_move.create_deck_from_counts_cache(counts)

Create a deck from the counts of every card.

Parameters:

counts (dict[int, int]) – How many times each card is in the deck. (e.g. {2: 12, 3: 11, 4: 15, …})

Returns:

A shoe with these cards.

Return type:

tuple[int, …]

best_move.create_deck_from_counts(counts)

Create a deck from the counts of every card.

Parameters:

counts (tuple[int, ...]) – How many times each card is in the deck. (e.g. (0, 0, 12, 11, 15, …))

Returns:

A shoe with these cards.

Return type:

tuple[int, …]

best_move.argmax(*profits)

Return the maximum profit and the action that gets you that profit.

Parameters:

profits (float) – The profits generated by each action.

Returns:

The best profit, and the best action.

Return type:

tuple[float, str]

best_move.dict_to_tuple(counts)

Convert the dict of the counts of every card to a tuple, so it can be used with @cache.

Parameters:

counts (dict[int, int]) – How many times each card is in the deck. (e.g. {2: 12, 3: 11, 4: 15, …})

Returns:

A tuple containing the number of times each card is in the shoe, but the keys are now the indices. (e.g. {2: 12, 3: 11, 4: 15, …} becomes (0, 0, 12, 11, 15, …))

Return type:

tuple[int, …]

best_move.can_never_split(cards)

Get if the player can’t split.

Parameters:

cards (tuple[int, ...]) – The player’s hand.

Returns:

Whether the player will not be able to split their hand now and in the future.

Return type:

bool

best_move.tuple_sort(cards)

Sort a tuple. Used to increase cache hits.

Parameters:

cards (Iterable[int]) – The cards in the shoe.

Returns:

The shoe with its cards sorted.

Return type:

tuple[int, …]

class best_move.HandPlayer(cards)

Hold information about the player’s hand.

Calculate the value of the hand the number of aces it has.

Parameters:

cards (tuple[int, ...]) – The cards the hand has at the moment.

class best_move.HandDealer(value, aces)

Hold information about the dealer’s hand.

Calculate the value of the hand the number of aces it has.

Parameters:
  • value (int) – The hand’s value (can be over 21).

  • aces (int) – How many aces that are counted as 11 the hand has.