Tuesday, October 1, 2013

Python Scripting and the Blackjack "In Bet"ween Bet

I recently spent an hour or so at the local casino (Delaware Park) playing blackjack with my father-in-law. The table we chose had a side bet called “IN BETween”, which compares the player’s two cards to the dealer’s up card. If all three cards match, the player is paid a 30-1 return. If the dealer’s card is in between the two player cards (hence the name of the game), the player is paid based on a pay table. At Delaware Park, the current pay table is:

Result Pay Ratio Example Winning Hand
All Cards Match 30-1 7-7-7
One Card Spread 10-1 3-4-5
Two Card Spread 6-1 8-T-J
Three Card Spread 4-1 2-5-6
All Other Spreads 1-1 3-7-T
For comparison purposes, Aces are the highest possible card.

After watching this side bet for awhile, I began to wonder what the house odds were for this game. Sure, I can just look it up (h/t to the State of Washington - http://www.wsgc.wa.gov/docs/game_rules/in_between.pdf), but this seemed like a perfect excuse to spend a few minutes with Python. So I dusted off my old Poker python script and modified it to simulate this game. No one actually starts a Python script with an empty notepad file, right? Smile

If you are interested in playing with this script, it takes two parameters. The first is the number of decks used. The number of decks is an important factor in this wager, as the majority of the value in the bet is due to the frequency of 30-1 payouts. Seeing three matching cards on a random draw from a single deck only happens .235% of the time (3/51 * 2/50), while the same result from eight decks happens .541% of the time, more than twice as often (31/415 * 30/414).

The second parameter is the number of iterations. Monte Carlo simulations benefit from many iterations. I’ve found that 1,000,000 iterations convergences on the mathematical results that the State of Washington has in their reference document.

Without further explanation, here is the script. If you notice any errors or anything I’ve done that is wildly inefficient please let me know; I always like improving my programming skills. If you want to improve this one suggestion would be to add the other pay tables listed in the State of Washington document. My local casino only seems to use the payouts I have listed, and since I rarely go to a casino (even the local one) these are the only payouts I was interested in.

#
# inbetween.py - Runs Monte Carlo simulation of In BETween bet
#       with user-specified number of decks and iterations
#
# Reference URL - http://www.wsgc.wa.gov/docs/game_rules/in_between.pdf
#
#
import sys
import random
def inbetween(cards):
#
# Takes array of three cards
# Returns win multiple based on standard pay table
#   return value includes original wager, if successful
#
  if cards[0][0] > cards[2][0]:
    cards[0], cards[2] = cards[2], cards[0]
  if cards[0][0] == cards[1][0] == cards[2][0]: return 30+1
  if cards[0][0] < cards[1][0] < cards[2][0]:
    if cards[2][0] - cards[0][0] == 2: return 10+1
    elif cards[2][0] - cards[0][0] == 3: return 6+1
    elif cards[2][0] - cards[0][0] == 4: return 4+1
    return 1+1
  return 0

def card_gen(num_decks):
#
# Takes number of decks (1 - 8)
# Returns three cards in array
# cards[0] = Player Card 1
# cards[1] = Dealer card 1
# cards[2] = Player Card 2
#
  card1 = []
  card2 = []
  card3 = []
  card1 = [random.randrange(0,13), random.randrange(0,4), random.randrange(0, num_decks)]
  card2 = [random.randrange(0,13), random.randrange(0,4), random.randrange(0, num_decks)]
  while card2 == card1:
#    print "Collision! " + str(card1) + " " + str(card2)
    card2 = [random.randrange(0,13), random.randrange(0,4), random.randrange(0, num_decks)]
  card3 = [random.randrange(0,13), random.randrange(0,4), random.randrange(0, num_decks)]
  while (card3 == card1) or (card3 == card2):
#    print "Collision! " + str(card1) + " " + str(card2) + " " + str(card3)
    card3 = [random.randrange(0,13), random.randrange(0,4), random.randrange(0, num_decks)]
  cards = []
  cards.append(card1)
  cards.append(card2)
  cards.append(card3)
  return cards

def readable_hand(cards):
#
# Returns a readable version of a set of cards
#
  rank_refstring = "X23456789TJQKA"
  suit_refstring = "xcdhs"
  string = ""
  for i, v in enumerate(cards):
    string += rank_refstring[v[0]+1] + suit_refstring[v[1]+1] + str(v[2]+1)
  return string
#
# Main Program Body
#
#
# Initialization
#
iterations = 0
num_decks = 0
cards = []
total_won = 0
result = 0
#
# Process command-line arguments
#
if (len(sys.argv) < 3) or (sys.argv[1] in ("-h", "--help")):
        sys.exit("\n\
First input is number of decks to be used (1 - 8)\n\
Second input is number of iterations to run the Monte Carlo simulation\n\n\
--help: This message\n")
else:
    num_decks = int(sys.argv[1])
    iterations = int(sys.argv[2])
    if iterations < 1: iterations = 1
for n in range(1, iterations+1):
  cards = card_gen(num_decks)
  result = inbetween(cards)
  total_won += result
#  print "Result[" + str(n) + "]: $" + str(result) + " Hand = " + readable_hand(cards)
print "Total Wagered = $" + str(iterations)
print "Total Returned = $" + str(total_won)
print "Total Profit = $" + str(total_won - iterations)
print "Win / Loss Percentage = %.2f" % \
  (100*float(total_won - iterations) / iterations) + "%"