0

I'm a new python developer and having trouble running a function on an object in a loop cleanly. I have an object class called Hand with a function hit.

def hit(self, Deck):
        card = Deck.all_cards.pop(0)
        self.card_list.append(card)
        self.faces.append(str(card))
        self.value = 0
        for card in self.card_list:
            self.value += card.value
        # Ace Logic
        aces = [card for card in self.card_list if card.rank == 'Ace']
        for card in aces:
            if self.value > 21:
                self.value -= 10

This works as expected when called on a "one-off" basis.

I store the Hand object (inside a list) as a dictionary value.

I would then like to iterate through the dictionary and run the Hit function on each Hand object.

 for plyr in game_dict:
    game_dict[plyr][0].hit(deck)

The loop adds one card to the first Hand object but adds two cards to the second hand object. The second hand object is a copy of the first with an additional card.

Obviously I would like each Hand object to receive one card. I know I've fallen into some common OOP trap but I haven't been able to figure it out. Thanks.

Please see code:

#Global Vars
import random
suits = ('Hearts', 'Diamonds', 'Spades', 'Clubs')
ranks = ('Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten',
         'Jack', 'Queen', 'King', 'Ace')
values = {'Two':2, 'Three':3, 'Four':4, 'Five':5, 'Six':6, 'Seven':7, 'Eight':8,
          'Nine':9, 'Ten':10, 'Jack':10, 'Queen':10, 'King':10, 'Ace':11}

class Card:
    '''Card object '''
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank
        self.value = values[rank]

    def __str__(self):
        return self.rank + ' of ' + self.suit


class Deck:
    '''52 Card with four suits'''

    def __init__(self):
        self.all_cards = []
        for suit in suits:
            for rank in ranks:
                # if values[rank] == 10: #split testing
                # if rank in ['Ace']: #Ace logic testing
                self.all_cards.append(Card(suit, rank))

    def shuffle(self):
        random.shuffle(self.all_cards)

    def __str__(self):
        return f'This deck has {len(self.all_cards)} cards.'

class Player:
    '''
    Player will be dealt cards and have following actions:
    1.Hit (Recieve another card from deck)
    2.Double down (Hit w/ bet x 2)
    3.Stand (No change to hand)
    4.Split (Turn one hand into two hands with SAME bet, respectively)

    Attributes:
    Name- Player Specified
    Bet - Player Specified
    Pot - Contains player's remaining betting capital
    kwarg(s) - Hand objects

    '''

    def __init__(self, name, bet, pot=100, npc= True, **kwargs):
        self.name = name
        self.pot = pot
        self.bet = bet
        self.npc = npc
        self.__dict__.update(kwargs)

    def __str__(self):
        return f'{self.name} has ${self.pot} to play.'

class Hand:
    '''
    Black jack hand used to track in-game attributes of hand

    Attributes:
    value - Integer of Black jack count
    ordinal - Integer of hand creation order
    face - list of card rank object
    active - Boolean determination of in-game status

    '''

    def __init__(self, value=0, ordinal=1, card_list=[], faces=[], active=True):
        self.value = value
        self.ordinal = ordinal
        self.card_list = card_list
        self.faces = faces
        self.active = active

    # In game hit
    def hit(self, Deck):
        card = Deck.all_cards.pop(0)
        self.card_list.append(card)
        self.faces.append(str(card))
        self.value = 0
        for card in self.card_list:
            self.value += card.value
        # Ace Logic
        aces = [card for card in self.card_list if card.rank == 'Ace']
        for card in aces:
            if self.value > 21:
                self.value -= 10

    def __str__(self):
        cards = ','.join(self.faces)
        if self.active == True:
            status = 'Active'
        else:
            status = 'Inactive'
        return f'This hand contains {cards} and is {status}.'
#Deck
deck = Deck()
deck.shuffle()

game_dict = {}

#Players
gambler = Player(name='Thomas', bet=25, pot=100, npc=False)
game_dict[gambler] = [Hand()]

npc_1 = Player(name='Phil', bet =25, pot=175)
game_dict[npc_1] = [Hand()]

for plyr in game_dict:
    print(f'Player string: {plyr}')
    print(f'Hand string: {game_dict[plyr][0]}')
    game_dict[plyr][0].hit(deck)
    print(f'Hand string: {game_dict[plyr][0]}')
thomas398
  • 59
  • 1
  • 1
  • 5
  • 1
    Please try to provide a [mre] (and avoid images of code). Your code can't be run, and what you show us lacks very important parts, like how `card_list` is defined. Might it be a class attribute, for example, or did you initialize it in `__init__`? – Thierry Lathuille Oct 01 '20 at 16:52
  • _The second hand object is a copy of the first with an additional card._ Why? Shouldn't each player have its own hand? And shouldn't the `Player` class own the `Hand`, anyway? – 001 Oct 01 '20 at 17:01
  • @thomas398 Please share the rest of your `Hand` class. – Paul M. Oct 01 '20 at 17:02
  • @JohnnyMopp According to the content of `game_dict` as shown in the image, the `Hand` objects are different (they have different ids). It looks like these instances share a common `card_list`. It might be because it is a class attribute, or because it is a default parameter in `__init__`, or whatever. Anyway, we can't know, as the OP didn't share this part of the code and provide a mre... – Thierry Lathuille Oct 01 '20 at 17:09
  • @JohnnyMopp Agreed. I'm trying to fix the second hand being a copy of the first with an additional hand. I was tracking "ownership" with the dictionary. This is blackjack so there are cases where a player will have two hands and each will need a card which brings me back to this situation. – thomas398 Oct 01 '20 at 17:11
  • I know it is frowned on at SO, but maybe add all the code. Or at least the `Deck`, `Hand`, and `Player` classes. – 001 Oct 01 '20 at 17:19
  • @JohnnyMopp Yes, it is really frowned upon, for many good reasons. That's why we ask for a [mre]. That helps the OP to concentrate on the essential part of the problem, makes it easier for him to get an answer, and makes the question potentially useful for others. – Thierry Lathuille Oct 01 '20 at 17:22

2 Answers2

1

You define card_list as an empty list as a default argument in __init__, as well as faces.

You should definitely avoid mutable default arguments, see “Least Astonishment” and the Mutable Default Argument for more details.

What happens is that the default parameter for card_list is evaluated once as an empty list when you define the __init__ method.

It is then given an additional name as self.card_list each time you create a new instance using this default parameter, but all these names refer to the same, unique list, which is common to all instances.

The usual way to avoid this is to do:

def __init__(self, value=0, ordinal=1, card_list=None, faces=None, active=True):
    self.value = value
    self.ordinal = ordinal
    self.card_list = card_list
    if self.card_list is None:
        self.card_list = []
    ...

This way, you get a new empty list for each instance.

Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50
  • Thank you for this solution and explanation! I'm coming from a SQL background and I did not know what the python equivalent of NULL. – thomas398 Oct 01 '20 at 19:05
-1

Try the below:

    plyr[0].hit(deck)

If this works, it's because in each loop, playr is equal to the actual item in game_dict, not the index.