I began creating blackjack in python; however, I'm looking to simulate it, so I'm thinking I'm going to need to optimize it so that I don't just leave my computer running for three hours waiting for just 3000 games to be simulated. I'm very new to this stuff so feel free to be as hard on me as you want. I'm looking to understand the changes as well so that I can improve my code in the future.
import random
ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
suits = ['Spades', 'Hearts', 'Diamonds', 'Clubs']
global deck
global data
data = []
class Hand(object):
def __init__(self):
# start with two cards in hand.
self.cards = [(deck.deal_card()), (deck.deal_card())]
self.cardValue = 0
for c in self.cards:
if c.rank == 'A':
self.sοft = True
else:
self.soft = False
def value(self):
# setting two temporary variables and resetting them whenever the function is run.
temp = 0
self.aceCount = 0
anothertemp = False
for card in self.cards:
# if the card is an ace. This works because our value for aces is (1, 11), declared in our card class.
if card.value() == (1, 11):
# add an ace to the aceCount, aces needs to be accounted for at the end, because otherwise if you have an ace
# in the first few cards they will count as 11 even if that would cause a bust (hand value greater than 21).
self.aceCount += 1
self.soft = True
else:
# add the card if it's not an ace
temp += card.value()
# Now we add the aces to our hand value, one ace can count as 11 while the other counts as one, so this works just fine
for card in range(self.aceCount):
# checking if the ace would cause a bust
if temp + 11 > 21:
# if it does, its value is only 1
temp += 1
self.soft = False
else:
# if it doesn't, its value is 11
temp += 11
self.soft = True
anothertemp = True
# finally set the cardValue to the temp variable to correctly return our hand value.
# I don't think I actually need the 'temp' variable if I just set self.cardValue to 0 in the beginning.
self.soft = anothertemp
self.cardValue = temp
return str(self.cardValue)
# just makes it easier to draw cards
def draw(self):
c = deck.deal_card()
self.cards.append(c)
# set up a string for display and testing purposes
def __str__(self):
hand = []
for c in self.cards:
hand.append(str(c))
return str(hand)
class Player(object):
def __init__(self, strategy = 'none', name='Player', budget=100):
self.name = name
self.hand = Hand()
self.budget = budget
self.state = 'play'
self.handValue = self.hand.value()
self.handIsSoft = self.hand.soft
self.strategy = strategy
# strategy sheets
# Neither player has a soft hand
self.strategy1NoSoft = [
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'hit', 'stay'],
['hit', 'hit', 'hit', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay', 'stay']
]
# Only dealer has a soft hand
self.strategy1DealerSoft = [
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','stay'],
['hit','hit','stay','stay','stay'],
['hit','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay'],
['stay','stay','stay','stay','stay']
]
# only the player has a soft hand
self.strategy1SelfSoft = [
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['stay', 'stay', 'stay', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay', 'hit', 'hit', 'hit', 'hit', 'stay', 'hit', 'hit', 'hit', 'stay'],
['stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','hit','hit','stay'],
['stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','hit','stay'],
['stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay','stay']
]
# both have a soft hand
self.strategy1BothSoft = [
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','hit'],
['hit','hit','hit','hit','stay'],
['stay','stay','stay','stay','stay',],
['stay','stay','stay','stay','stay',],
['stay','stay','stay','stay','stay',]
]
# add a card to hand
def hit(self):
self.hand.draw()
self.handValue = self.hand.value()
self.handIsSoft = self.hand.soft
# change the state to stay to stop all functions revolving around 'play' state
def stay(self):
self.state = 'stay'
# Check if the player busted
def check_bust(self):
# if your hand is over 21 you bust
if int(self.handValue) > 21:
self.state = 'bust'
return True
else:
return False
# Check the budget
def check_broke(self):
if int(self.budget < 1):
self.state = 'broke'
return True
else:
return False
# bid an amount
def bid(self):
self.broke = self.check_broke()
if self.broke:
print('You are out of money and cannot bid anymore')
else:
self.bidAmount = int(input('How much?\n'))
self.budget -= self.bidAmount
# the strategy function, checks for each soft/not soft hand and responds according to the strategy sheet
def strategy_one(self, dealer):
try:
if not self.handIsSoft and not dealer.handIsSoft:
if self.strategy1NoSoft[int(self.handValue)][int(dealer.handValue)] == 'hit':
self.hit()
elif self.strategy1NoSoft[int(self.handValue)][int(dealer.handValue)] == 'stay':
self.stay()
elif not self.handIsSoft and dealer.handIsSoft and dealer.handValue >= 12 and dealer.handValue <= 16:
if self.strategy1DealerSoft[int(self.handValue)][int(dealer.handValue) - 12] == 'hit':
self.hit()
elif self.strategy1DealerSoft[int(self.handValue)][int(dealer.handValue) - 12] == 'stay':
self.stay()
elif self.handIsSoft and not dealer.handISSoft and dealer.handValue >= 4:
if self.strategy1SelfSoft[int(self.handValue)][int(dealer.handValue) - 4] == 'hit':
self.hit()
elif self.strategy1SelfSoft[int(self.handValue)][int(dealer.handValue) - 4] == 'stay':
self.stay()
elif self.handIsSoft and dealer.handIsSoft and self.handValue >= 13 and dealer.handValue >= 12 and dealer.handValue <= 16:
if self.strategy1BothSoft[int(self.handValue)-13][int(dealer.handValue) - 12] == 'hit':
self.hit()
elif self.strategy1BothSoft[int(self.handValue)-13][int(dealer.handValue) - 12] == 'stay':
self.stay()
except:
self.stay()
def __str__(self):
# player string
return str('Name: ' + str(self.name) + '; Hand: ' + str(self.hand) + '; Budget: ' + str(self.budget) +
'; Hand Value: ' + str(self.handValue) + '; Hand is soft: ' + str(self.handIsSoft) + '; Busted: ' + str(self.check_bust()) + '; State: ' + str(self.state))
class Dealer(Player):
# initialize the dealer as a player
def __init__(self, name='Dealer'):
super().__init__('None',name)
# automate the dealer strategy. His strategy is nice and simple, hit below 17 stay at 17 or higher if the hand is 17 and soft, hit.
def dealer_strategy(self):
if int(self.handValue) < 17 or int(self.handValue) == 17 and self.handIsSoft:
self.hit()
else:
self.stay()
class Game(object):
def __init__(self, strategy = 'none', name = 'Player'):
self.dealer = Dealer()
self.deck = Deck()
self.deck.shuffle()
self.player = Player(strategy, name)
# this will be used later for running a simulation
global gameCount
gameCount += 1
# this will later be used to save the data of the game
def log_game(self):
data.append((gameCount, self.player.name, self.player.budget))
# run the game
def run(self):
print(self.player)
print(self.dealer)
if self.dealer.handValue == 21 and self.dealer.hand.aceCount > 0:
self.player.state = 'stay'
# while the player is not staying, busted or out of budget
while not self.player.state in ['stay', 'bust']:
# if a strategy is already selected then automate the strategy
if self.player.strategy == 'strategy1':
self.player.strategy_one(self.dealer)
print(self.player.hand, self.player.handValue)
else:
# get the move that the player wants to do
playerMove = input("what would you like to do? \n1) Hit \n2) Stay \n3) Bid\n")
# execute that move
if playerMove.upper() in ['1', 'HIT']:
self.player.hit()
print(self.player.hand)
print(self.player.handValue)
elif playerMove.upper() in ['2', 'STAY']:
self.player.stay()
elif playerMove.upper() in ['3', 'BID']:
self.player.bid()
else:
print('Not a valid move')
# Check if the player busted
self.player.check_bust()
if self.player.state == 'Bust':
print('Sorry, you busted.')
# Run the dealer simulation
while not self.dealer.state in ['stay', 'bust']:
self.dealer.dealer_strategy()
print(self.dealer.hand, self.dealer.handValue)
# Check bust for both
self.dealer.check_bust()
self.player.check_bust()
# Check win and loss
if ((self.player.handValue > self.dealer.handValue) and self.player.state != 'bust') or self.dealer.state == 'bust':
if self.player.state == 'bust' and self.dealer.state == 'bust':
print('You lost this round.')
else:
print('You won this round!')
else:
print('You lost this round.')
self.log_game()
print(data)
class Deck(object):
def __init__(self):
self.cards = []
# Create a deck, suit first then rank. This makes the deck follow what a brand new pack of cards looks like.
for suit in suits:
for rank in ranks:
c = Card(rank, suit)
self.cards.append(c)
def shuffle(self):
# use the random library to shuffle the cards
random.shuffle(self.cards)
def deal_card(self):
# Check if there are no cards left in the deck
if not self.cards:
raise Exception('No more cards in deck!')
# As long as there are cards left in the deck, pop one from the top of the deck out.
draw = self.cards.pop(0)
# return the drawn card so it can be added to the hand
return draw
# Set up another string so that the deck can be displayed whenever needed
def __str__(self):
deck = []
for c in self.cards:
deck.append(str(c))
return str(deck)
class Card(object):
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
def value(self):
# if the rank is is a king, queen, or jack, return 10 as the value of the card.
if self.rank in ['J', 'Q', 'K']:
return 10
# if the rank is an ace return 1 and 11. This will later let us choose whether we want the ace to be a one or eleven
if self.rank == 'A':
return 1, 11
else:
# if it's nothing special just return the number.
return int(self.rank)
def suit(self):
return self.suit
def rank(self):
return self.rank
# another string declaration to make it easy to display
def __str__(self):
return self.rank + '-' + self.suit
# <------------- TESTS ------------->
def cardTest():
# manually create 4 cards to make sure all the special things work
card1 = Card('9', 'Spades')
card2 = Card('3', 'Hearts')
card3 = Card('K', 'Clubs')
card4 = Card('A', 'Diamonds')
print(card1, card2, card3, card4)
def deckTest():
# create a deck, display the deck to test the str then shuffle and re-display to test the shuffle method
deck = Deck()
print('<----------- Before Shuffle ----------->')
print(deck)
deck.shuffle()
print('<----------- After Shuffle ----------->')
print(deck)
def handTest():
# It's safe to use global because the deck variable will be global, make a new hand and print it and its value.
global deck
deck = Deck()
deck.shuffle()
hand = Hand()
print(hand)
print(hand.value())
print(hand.soft)
def playerTest():
# make sure the player class and each of there moves are working.
player1 = Player('Andrea', 200)
player2 = Player()
player3 = Player('Rob')
player4 = Player('Tara', 300)
print(player1, '\n' + str(player2), '\n' + str(player3), '\n' + str(player4))
player4.hit()
print(player4)
def dealerTest():
dealer = Dealer()
print(str(dealer))
while dealer.state not in ['stay', 'bust']:
dealer.dealer_strategy()
print(str(dealer))
def gameTest():
print('<--------------------------------------- GAME --------------------------------------->')
global gameCount
gameCount = 0
game = Game('None') # 'None', 'strategy1'
game.run()
cardTest()
deckTest()
handTest()
playerTest()
dealerTest()
gameTest()
2 Answers 2
This submission is about performance, but it does not include any profiling measurements.
language choice
If you're looking for raw speed, interpreted python usually wouldn't be the first choice. Maybe you'd be happier with Rust, zig, or C++.
main guard
cardTest()
deckTest()
handTest()
playerTest()
dealerTest()
gameTest()
Please enclose those within the customary
if __name__ == "__main__":
idiom, so
unit tests
can safely import
this module without
delay or side effects.
module-level global
global deck
global data
data = []
Excessive coupling leads to trouble.
Prefer self.foo
over global foo
.
python2
class Hand(object):
Please don't tell us Hand
inherits from object
.
Yes, we know that, everything inherits from object
.
As written this looks like python2 code which is
trying to distinguish between the old- and new-style
MRO.
Simply write class Hand:
Similarly for Player
and Game
.
docstrings
def __init__(self):
# start with two cards in hand.
...
def value(self):
# setting two temporary variables and resetting them whenever
These #
comments would be more helpful as """docstrings""".
Please get in the habit of putting one at the start of
each class or method.
identifiers
self.cardValue = 0
Pep-8
asked you nicely to spell it card_value
, ace_value
,
another_temp
, etc.
Writing in the style of some non-python language
is distracting and makes your code harder to read.
odd loop
for c in self.cards:
if c.rank == 'A':
self.sοft = True
else:
self.soft = False
That can't be right -- consider the case where just card zero is an Ace.
self.soft = False
for c in self.cards:
if c.rank == 'A':
self.sοft = True
Or use self.soft = any((c.rank == 'A' for c in self.cards))
.
ToC
def value(self):
...
self.aceCount = 0
Please don't spring that on me by surprise.
While not necessary, it is polite to
introduce all attributes up in the __init__
constructor,
even if only to assign None
,
as sort of a Table of Contents.
That way the Gentle Reader has a heads up on
what state matters to the Hand object.
Similarly for self.bid_amount
, and especially
for self.broke
which there's no need for storing as an attribute.
Also, if card.value() == (1, 11):
is unexpected
and weird, but fine, we'll roll with it.
Naming it plural card.values()
would have led to less surprise.
Prefer a [
]
list
for variable-length datastructures,
rather than the (
)
tuple
we see here.
throwaway name
This is clearly the wrong name:
if temp + 11 > 21:
You intended total
here.
Names really matter. They affect how we think about code. Choose them carefully.
stringly typed
class Player(object):
def __init__( ... ):
...
self.state = 'play'
Consider defining an Enum for that.
Also, not sure why you're replicating things here:
self.handValue = self.hand.value()
self.handIsSoft = self.hand.soft
Now we have to worry about them getting out of sync,
for example down in the hit()
method. Create a
@property
if you feel you need a convenience method.
cite your reference
# Neither player has a soft hand
self.strategy1NoSoft = [
['hit', 'hit', 'hit',
...
# Only dealer has a soft hand
self.strategy1DealerSoft = [
...
# only the player has a soft hand
self.strategy1SelfSoft = [
...
# both have a soft hand
self.strategy1BothSoft = [
These datastructures are really quite hard to read and interpret. If you copied them from a book or web page, please cite it. If there's some code which generated these, show us the code rather than its output.
As written, the Gentle Reader has no reason to believe that these are valid or winning strategies. You have not offered us the tools to reason about this datastructure or its correctness.
temporary expression
In strategy_one()
the repeated
int(self.handValue)
conversion is distracting.
Assign an integer hand_value
local variable, and use that.
There's no need for four self.hit()
calls.
Phrase it in this way:
if not self.handIsSoft and ...
strategy = self.strategy1NoSoft...
elif not self.handIsSoft and ...
strategy = self.strategy1DealerSoft...
elif self.handIsSoft and ...
strategy = self.strategy1SelfSoft...
elif self.handIsSoft and ...
strategy = self.strategy1BothSoft...
Now that you've assigned a strategy
, call it.
Ideally we'd have a function object already, rather than a string.
We could do what the OP was doing before:
if strategy == 'hit':
self.hit()
else:
self.stay()
Or we could use a lookup table:
str_to_fn = {
'hit': self.hit,
'stay': self.stay,
}
str_to_fn[strategy]()
bare except
except:
self.stay()
No, we don't do that, not ever. Prefer except Exception:
,
so you don't accidentally disable some important items
like CTRL/C handling.
Use ruff check or
pylint
to help you notice such things.
Also, it's a bit unsettling that we silently ignore
the error without logging it.
Writing something specific like except IndexError:
would be a boon to the Gentle Reader.
As written, a #
comment becomes necessary,
explaining why we anticipate an error
and why it's harmless to silently discard it.
f-string
def __str__(self): ...
return str('Name: ' + str(self.name) + '; Hand: ' + str(self.hand) + '; Budget: ' + ...
You compute a string expression and then apply str()
to it,
which is harmless but unnecessary.
Prefer the more compact format-string notation:
return f'Name: {self.name}; Hand: {self.hand}' ...
none
class Player(object):
def __init__(self, strategy = 'none', ... ):
...
class Dealer(Player): ...
def __init__(self, ... ):
super().__init__('None', name)
You told us the usual spelling is lowercase 'none'
,
then used titlecase 'None'
.
Idiomatic python would pass in a None
object rather than a str
.
Please use an Enum for this already!
Similar remarks for the 'bust'
/ 'Bust'
inconsistency.
namedtuple
This is very nice:
def log_game(self):
data.append((gameCount, self.player.name, self.player.budget))
I'm glad to see the use of a 3-tuple, rather than a list
.
Consider making it self-explanatory by naming the three fields:
from collections import namedtuple
...
LogRecord = namedtuple('LogRecord', 'game_count, name, budget')
overly broad error
raise Exception('No more cards in deck!')
Consider making this a ValueError. Or define your own app-specific error.
As written, a caller could not e.g. catch just an anticipated ZeroDivisionError.
join
Turning a deck into a str
works just fine:
deck = []
for c in self.cards:
deck.append(str(c))
return str(deck)
Consider using .join() there:
return '[' + ', '.join(map(str, self.cards)) + ']'
If you dislike map(), at least go for a list comprehension:
return str([str(c) for c in self.cards])
elide getters
def suit(self):
return self.suit
def rank(self):
return self.rank
This isn't java. We're all grownups here. Just let the caller access those attributes.
Oh, wait, you didn't say def get_suit
.
You overwrote it, yikes!
So in the class namespace suit
is a callable method,
and in the object namespace suit
is a str
.
That's not helpful.
Yeah, you really need to delete this pair of methods.
automated tests
I really like that you're thoroughly exercising the target code. But an automated test suite "knows the right answer", so it can display a Green bar.
def cardTest():
...
print(card1, card2, card3, card4)
Rather than manually eyeballing the results to see if they look right, prefer to create a TestCase, using e.g. self.assertEqual().
Overview
The code layout is good, and you made good use of classes, functions and comments.
Here are some suggestions to consider.
DRY
There is a lot of unnecessary repetition in lines like this:
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
['hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'hit', 'stay'],
Firstly, the 3 lines are identical (your code actually has 11 lines).
Secondly, the same string (hit
) appears many times.
Loops are one way to reduce the repetition. It also has the benefit of eliminating long lines of code.
Another example of repetition is in lines like the following:
if self.strategy1NoSoft[int(self.handValue)][int(dealer.handValue)] == 'hit':
self.hit()
elif self.strategy1NoSoft[int(self.handValue)][int(dealer.handValue)] == 'stay':
It seems like every handValue
is converted to int
. Perhaps handValue
could be an int
in the class, or your class could have another variable
which is an int
.
Simpler
You can eliminate the temporary variable. To simplify the code, change:
c = deck.deal_card()
self.cards.append(c)
to:
self.cards.append(deck.deal_card())
Documentation
Add docstrings for the classes and functions. These should describe the purpose of the code as well as inputs and return values.
Unicode
In the Hand
__init()__
function, the following line has
a Unicode character instead of o
:
self.soft = True
I recommend changing to an o
. It was a syntax error for me
when I ran the code.
Output
When I run the code, it generates a lot of output to the screen, some of which is very wide and wraps. This makes the output very hard to understand.
I recommend adding an option to print only necessary information by default. Let the user opt in for more verbose output.
Lint check
pylint identified a few issues. There are some long lines that can be shortened, especially the comments.
Refer to the PEP 8 style guide for function and variable names.
Explore related questions
See similar questions with these tags.