paulnsn
paulnsn

Reputation: 107

Manipulating the Value of Ace in Blackjack (Python)

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

Answers (3)

blueenvelope
blueenvelope

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.

Don't use getters in Python

You might want to check the deck is not empty in the deal function before poping otherwise it will error.

Upvotes: 0

wkl
wkl

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

Adam Smith
Adam Smith

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

Related Questions