Reputation: 107
Can someone please take a look and my code and let me know why the Ace value isn't changing to 11 when the player's hand is less than 21? I am having difficulty with implementing the IF loop in the FOR loop under def checkvalue(self)
. Is this the best way to do this or is there a better way?
Thanks
import random
rank = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
suit = ['Diamonds', 'Clubs', 'Hearts', 'Spade']
card_val = {'2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '10':10, 'Jack':10, 'Queen':10, 'King':10, 'Ace':1}
class Card(object):
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
def __str__(self):
return str(self.rank) + ' of ' + str(self.suit)
def grab_suit(self):
return self.suit
def grab_rank(self):
return self.rank
def draw(self):
print(self.suit + self.rank)
class Deck(object):
def __init__(self):
self.cards = []
for i in rank:
for j in suit:
self.cards.append(Card(i,j))
def __str__(self):
return str([str(card) for card in self.cards])
def shuffle(self):
random.shuffle(self.cards)
def deal(self):
single_card = self.cards.pop()
return single_card
deck = Deck()
class Hand(object):
def __init__(self):
self.value = []
def hit(self):
self.value.append(deck.deal())
return self.value
def __str__(self):
return str([str(card) for card in self.value])
def checkvalue(self):
handvalue = 0
for card in self.value:
handvalue += card_val[card.grab_rank()]
if card.grab_rank() in self.value == 'Ace' and handvalue <= 11:
handvalue = handvalue + 10
return handvalue
playerhand = Hand()
Upvotes: 3
Views: 3651
Reputation: 699
You want the if inside of the for loop, otherwise you will only check one card (the last card in hand). We also only care if they have at least one ace because using more than one as 11 would bust.
Try:
def checkvalue(self):
handvalue = 0
has_ace = False
for card in self.value:
if card.grab_rank() == 'Ace':
has_ace = True
handvalue += card_val[card.grab_rank()]
if has_ace and handvalue <= 11:
handvalue += 10
return handvalue
Minor notes about the rest of your code:
Your if checks if self.value is equal to 'Ace' (False) and then if card.grab.rank() in False which would error.
You might want to check the deck is not empty in the deal function before poping otherwise it will error.
Upvotes: 0
Reputation: 80001
When you calculate the value of a player's hand, you only compare the last card
from iterating through self.value
to see if it's an Ace
.
def checkvalue(self):
handvalue = 0
for card in self.value:
handvalue += card_val[card.grab_rank()]
# Because of how Python scoping works, the card you use here
# is the last card that `for card in self.value` iterated on
# You're not actually comparing every card in `self.value` to 'Ace'
# This conditional is also odd - you probably meant it to be an iteration
# over self.value to see if any card was an Ace
if card.grab_rank() in self.value == 'Ace' and handvalue <= 11:
handvalue = handvalue + 10
return handvalue
So basically, you want to instead calculate the hand value while also determining if any card was an Ace
.
def checkvalue(self):
handvalue = 0
has_ace = False
for card in self.value:
handvalue += card_val[card.rank]
if card.rank == 'Ace':
has_ace = True
if has_ace and handvalue <= 11:
handvalue += 10
return handvalue
Upvotes: 1
Reputation: 54223
if card.grab_rank() in self.value == 'Ace'
is gibberish. Or rather, it is interpreted as
if (card.grab_rank() in self.value) == 'Ace'
card
, here, refers to the last card in the hand (since it's outside your for
loop, above), not any card in your hand. Even if it were, you'd have to remove that in self.value
check.
A minimal change to get your code working would be:
class Hand(object):
...
def checkvalue(self):
handvalue = 0
for card in self.value:
handvalue += card_val[card.grab_rank()]
if any(card.grab_rank() == 'Ace' for card in self.value) and \
handvalue <= 11:
handvalue += 10
return handvalue
Separately, get_rank
and get_value
are silly. Don't implement getters in Python, just use attribute access (card.rank
, and card.value
).
Note that you've got a lot going on here that could be cleaned up a bit. For one thing: cards should probably know their own value rather than having to look up a global table to find it.
class Card(object):
valuetable = {'Jack': 10, 'Queen': 10, 'King': 10, 'Ace': 1}
valuetable.update({str(i): i for i in range(2, 10)})
# maybe write that out for clarity.
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
self.value = self.valuetable[self.rank] # look up once and save
Then your checkvalue
function becomes
def checkvalue(self):
handvalue = sum(c.value for c in self.value)
if any(c.rank == 'Ace' for c in self.value) and handvalue <= 11:
handvalue += 10
return handvalue
This becomes even simpler if you refactor to an inheritance model and make checkvalue
into a property.
class Hand(list):
# in other languages, I might literally define this as a type alias of `[]Card`
# in Python, inheriting from list is fine.
@property
def value(self):
total = [c.value for c in self]
if any(c.rank == 'Ace' for c in self) and total <= 11:
total += 10
return total
Now you call hand.value
instead of hand.value()
which makes more sense anyway. value
isn't an action in the real world -- it's a noun not a verb. Treat it as an attribute (or a property, which is what the @property
decorator does) instead.
Note that I don't define __init__
here since that's taken care of when you inherit from list
, I don't define __str__
since it's more or less taken care of on its own (if you print
a list, it calls str
on all its members anyway), and I don't define hit
because a hand really shouldn't be hitting itself. It doesn't make sense from either an encapsulation point of view (it now relies upon an object named deck
that has a method deal
) and it doesn't make sense for "hitting" to be the hand's responsibility. Maybe consider...
class Player(object):
# has a self.hand attribute and a `self.game.deck` attribute
def hit(self):
self.hand.append(self.game.deck.deal(1))
The player hits, the hand doesn't.
Upvotes: 3