Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dealer should not be drawing cards if the player busts. #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 143 additions & 46 deletions BlackJack.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import sys
import os
from random import shuffle

import numpy as np
import scipy.stats as stats
import pylab as pl
import matplotlib.pyplot as plt
import pandas as pd

from importer.StrategyImporter import StrategyImporter


scriptDirectory = os.path.dirname(os.path.realpath(__file__))
GAMES = 20000
SHOE_SIZE = 6
SHOE_PENETRATION = 0.25
BET_SPREAD = 20.0

DECK_SIZE = 52.0
CARDS = {"Ace": 11, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "Six": 6, "Seven": 7, "Eight": 8, "Nine": 9, "Ten": 10, "Jack": 10, "Queen": 10, "King": 10}
BASIC_OMEGA_II = {"Ace": 0, "Two": 1, "Three": 1, "Four": 2, "Five": 2, "Six": 2, "Seven": 1, "Eight": 0, "Nine": -1, "Ten": -2, "Jack": -2, "Queen": -2, "King": -2}
CARDS = {
"Ace": 11, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "Six": 6,
"Seven": 7, "Eight": 8, "Nine": 9, "Ten": 10, "Jack": 10, "Queen": 10,
"King": 10
}
BASIC_OMEGA_II = {
"Ace": 0, "Two": 1, "Three": 1, "Four": 2, "Five": 2, "Six": 2, "Seven": 1,
"Eight": 0, "Nine": -1, "Ten": -2, "Jack": -2, "Queen": -2, "King": -2
}

BLACKJACK_RULES = {
'triple7': False, # Count 3x7 as a blackjack
Expand All @@ -38,6 +48,10 @@ def __init__(self, name, value):
def __str__(self):
return "%s" % self.name

@property
def count(self):
return BASIC_OMEGA_II[self.name]


class Shoe(object):
"""
Expand Down Expand Up @@ -76,16 +90,17 @@ def init_cards(self):

def init_count(self):
"""
Keep track of the number of occurrences for each card in the shoe in the course over the game. ideal_count
is a dictionary containing (card name - number of occurrences in shoe) pairs
Keep track of the number of occurrences for each card in the shoe in
the course over the game. ideal_count is a dictionary containing (card
name - number of occurrences in shoe) pairs
"""
for card in CARDS:
self.ideal_count[card] = 4 * SHOE_SIZE

def deal(self):
"""
Returns: The next card off the shoe. If the shoe penetration is reached,
the shoe gets reshuffled.
Returns: The next card off the shoe. If the shoe penetration is
reached, the shoe gets reshuffled.
"""
if self.shoe_penetration() < SHOE_PENETRATION:
self.reshuffle = True
Expand All @@ -112,7 +127,8 @@ def truecount(self):

def shoe_penetration(self):
"""
Returns: Ratio of cards that are still in the shoe to all initial cards.
Returns: Ratio of cards that are still in the shoe to all initial
cards.
"""
return len(self.cards) / (DECK_SIZE * self.decks)

Expand All @@ -134,13 +150,14 @@ def __init__(self, cards):
def __str__(self):
h = ""
for c in self.cards:
h += "%s " % c
h += "%s[%s] " % (c, c.count)
return h

@property
def value(self):
"""
Returns: The current value of the hand (aces are either counted as 1 or 11).
Returns: The current value of the hand (aces are either counted as 1 or
11).
"""
self._value = 0
for c in self.cards:
Expand Down Expand Up @@ -180,7 +197,8 @@ def aces_soft(self):

def soft(self):
"""
Determines whether the current hand is soft (soft means that it consists of aces valued at 11).
Determines whether the current hand is soft (soft means that it
consists of aces valued at 11).
"""
if self.aces_soft > 0:
return True
Expand All @@ -189,7 +207,7 @@ def soft(self):

def splitable(self):
"""
Determines if the current hand can be splitted.
Determines if the current hand can be split.
"""
if self.length() == 2 and self.cards[0].name == self.cards[1].name:
return True
Expand All @@ -198,10 +216,12 @@ def splitable(self):

def blackjack(self):
"""
Check a hand for a blackjack, taking the defined BLACKJACK_RULES into account.
Check a hand for a blackjack, taking the defined BLACKJACK_RULES into
account.
"""
if not self.splithand and self.value == 21:
if all(c.value == 7 for c in self.cards) and BLACKJACK_RULES['triple7']:
if (all(c.value == 7 for c in self.cards) and
BLACKJACK_RULES['triple7']):
return True
elif self.length() == 2:
return True
Expand Down Expand Up @@ -243,13 +263,43 @@ def length(self):
return len(self.cards)


class Log(object):
"""
Represents a history of hands and associated actions.
"""
def __init__(self):
try:
self.hands = pd.read_pickle(scriptDirectory+'/player_history')
except FileNotFoundError:
self.hands = None

def __str__(self):
print(self.hands)

def add_hand(self, action, hand, dealer_hand, shoe):
d = {'hand': [hand.value], 'soft': [hand.soft()],
'splitable': [hand.splitable()],
'dealer': [dealer_hand.cards[0].value],
'truecount': [shoe.truecount()], 'action': [action.upper()]
}
if self.hands is None:
self.hands = pd.DataFrame(data=d)
else:
self.hands = self.hands.append(pd.DataFrame(data=d))

def save(self):
self.hands.to_pickle(scriptDirectory+'/player_history')


class Player(object):
"""
Represent a player
"""
def __init__(self, hand=None, dealer_hand=None):
self.hands = [hand]
self.dealer_hand = dealer_hand
self.autoplay = True
self.history = Log()

def set_hands(self, new_hand, new_dealer_hand):
self.hands = [new_hand]
Expand All @@ -267,14 +317,30 @@ def play_hand(self, hand, shoe):
self.hit(hand, shoe)

while not hand.busted() and not hand.blackjack():
if hand.soft():
flag = SOFT_STRATEGY[hand.value][self.dealer_hand.cards[0].name]
elif hand.splitable():
flag = PAIR_STRATEGY[hand.value][self.dealer_hand.cards[0].name]
if self.autoplay:
if hand.soft():
flag = SOFT_STRATEGY[hand.value][
self.dealer_hand.cards[0].name]
elif hand.splitable():
flag = PAIR_STRATEGY[hand.value][
self.dealer_hand.cards[0].name]
else:
flag = HARD_STRATEGY[hand.value][
self.dealer_hand.cards[0].name]
else:
flag = HARD_STRATEGY[hand.value][self.dealer_hand.cards[0].name]

if flag == 'D':
print("Dealer Hand: %s (%d)" %
(self.dealer_hand, self.dealer_hand.value))
print("Player Hand: %s (%d)" %
(self.hands[0], self.hands[0].value))
print("Count=%s, Penetration=%s\n" %
("{0:.2f}".format(shoe.count),
"{0:.2f}".format(shoe.shoe_penetration())))
flag = input("Action (H=Hit, S=Stand, D=Double, P=Split, "
"Sr=Surrender, Q=Quit): ")
if flag != 'Q':
self.history.add_hand(flag, hand, self.dealer_hand, shoe)

if flag.upper() == 'D':
if hand.length() == 2:
# print "Double Down"
hand.doubled = True
Expand All @@ -283,23 +349,26 @@ def play_hand(self, hand, shoe):
else:
flag = 'H'

if flag == 'Sr':
if flag.upper() == 'SR':
if hand.length() == 2:
# print "Surrender"
hand.surrender = True
break
else:
flag = 'H'

if flag == 'H':
if flag.upper() == 'H':
self.hit(hand, shoe)

if flag == 'P':
if flag.upper() == 'P':
self.split(hand, shoe)

if flag == 'S':
if flag.upper() == 'S':
break

if flag.upper() == 'Q':
exit()

def hit(self, hand, shoe):
c = shoe.deal()
hand.add_card(c)
Expand Down Expand Up @@ -330,13 +399,16 @@ def hit(self, shoe):
self.hand.add_card(c)
# print "Dealer hitted: %s" %c

# Returns an array of 6 numbers representing the probability that the final score of the dealer is
# Returns an array of 6 numbers representing the probability that the final
# score of the dealer is
# [17, 18, 19, 20, 21, Busted] '''
# TODO Differentiate 21 and BJ
# TODO make an actual tree, this is false AF
def get_probabilities(self) :
def get_probabilities(self):
start_value = self.hand.value
# We'll draw 5 cards no matter what an count how often we got 17, 18, 19, 20, 21, Busted
# We'll draw 5 cards no matter what an count how often we got 17, 18,
# 19, 20, 21, Busted


class Tree(object):
"""
Expand All @@ -353,16 +425,16 @@ def __init__(self, start=[]):
def add_a_statistical_card(self, stat_card):
# New set of leaves in the tree
leaves = []
for p in self.tree[-1] :
for v in stat_card :
for p in self.tree[-1]:
for v in stat_card:
new_value = v + p
proba = self.tree[-1][p]*stat_card[v]
if (new_value > 21) :
proba = self.tree[-1][p] * stat_card[v]
if (new_value > 21):
# All busted values are 22
new_value = 22
if (new_value in leaves) :
if (new_value in leaves):
leaves[new_value] = leaves[new_value] + proba
else :
else:
leaves[new_value] = proba


Expand Down Expand Up @@ -421,10 +493,18 @@ def get_hand_winnings(self, hand):
return win, bet

def play_round(self):
if self.shoe.truecount() > 6:
self.stake = BET_SPREAD
if self.player.autoplay:
if self.shoe.truecount() > 6:
self.stake = BET_SPREAD
else:
self.stake = 1.0
else:
self.stake = 1.0
raw_stake = input("Bet (%s): " % self.stake)
if raw_stake != "":
try:
self.stake = float(raw_stake)
except ValueError:
print("Invalid bet, using default.")

player_hand = Hand([self.shoe.deal(), self.shoe.deal()])
dealer_hand = Hand([self.shoe.deal()])
Expand All @@ -434,17 +514,17 @@ def play_round(self):
# print "Player Hand: %s\n" % self.player.hands[0]

self.player.play(self.shoe)
self.dealer.play(self.shoe)
if not self.player.hands[0].busted():
self.dealer.play(self.shoe)
else:
dealer_hand.add_card(self.shoe.deal())

# print ""

for hand in self.player.hands:
win, bet = self.get_hand_winnings(hand)
self.money += win
self.bet += bet
# print "Player Hand: %s %s (Value: %d, Busted: %r, BlackJack: %r, Splithand: %r, Soft: %r, Surrender: %r, Doubled: %r)" % (hand, status, hand.value, hand.busted(), hand.blackjack(), hand.splithand, hand.soft(), hand.surrender, hand.doubled)

# print "Dealer Hand: %s (%d)" % (self.dealer.hand, self.dealer.hand.value)

def get_money(self):
return self.money
Expand All @@ -455,38 +535,55 @@ def get_bet(self):

if __name__ == "__main__":
importer = StrategyImporter(sys.argv[1])
HARD_STRATEGY, SOFT_STRATEGY, PAIR_STRATEGY = importer.import_player_strategy()
HARD_STRATEGY, SOFT_STRATEGY, PAIR_STRATEGY = (
importer.import_player_strategy())

moneys = []
bets = []
countings = []
nb_hands = 0
GAMES = int(input("How many games? "))
for g in range(GAMES):
game = Game()
autoplay = input("Autoplay? (y/n): ")
if autoplay == 'n':
game.player.autoplay = False
while not game.shoe.reshuffle:
# print '%s GAME no. %d %s' % (20 * '#', i + 1, 20 * '#')
game.play_round()
print("Dealer Hand: %s (%d)" %
(game.dealer.hand, game.dealer.hand.value))
print("Player Hand: %s (%d)\n" %
(game.player.hands[0], game.player.hands[0].value))

nb_hands += 1

moneys.append(game.get_money())
bets.append(game.get_bet())
countings += game.shoe.count_history

print("WIN for Game no. %d: %s (%s bet)" % (g + 1, "{0:.2f}".format(game.get_money()), "{0:.2f}".format(game.get_bet())))
print("WIN for Game no. %d: %s (%s bet)" %
(g + 1, "{0:.2f}".format(game.get_money()),
"{0:.2f}".format(game.get_bet())))

if game.player.autoplay is False:
game.player.history.save()
sume = 0.0
total_bet = 0.0
for value in moneys:
sume += value
for value in bets:
total_bet += value

print "\n%d hands overall, %0.2f hands per game on average" % (nb_hands, float(nb_hands) / GAMES)
print "%0.2f total bet" % total_bet
print("Overall winnings: {} (edge = {} %)".format("{0:.2f}".format(sume), "{0:.3f}".format(100.0*sume/total_bet)))
print("\n%d hands overall, %0.2f hands per game on average" %
(nb_hands, float(nb_hands) / GAMES))
print("%0.2f total bet" % total_bet)
print("Overall winnings: {} (edge = {} %)".format(
"{0:.2f}".format(sume), "{0:.3f}".format(100.0 * sume / total_bet))
)

moneys = sorted(moneys)
fit = stats.norm.pdf(moneys, np.mean(moneys), np.std(moneys)) # this is a fitting indeed
fit = stats.norm.pdf(moneys, np.mean(moneys), np.std(moneys))
pl.plot(moneys, fit, '-o')
pl.hist(moneys, normed=True)
pl.show()
Expand Down