7
\$\begingroup\$

I am attempting to write a game of blackjack using classes in Python. Any advice on keeping it DRY?

I seem to be creating too many lists in lists. Should I name the keys in the dictionary or just use integers? How can I get the deck, shuffle and deal logic into a class that will link with the Player class?

import random
import itertools as it

number_players = 2 #input('Enter Number of Players here: ')
number_cards = 2 #input('Enter Number of Cards here: ')

# create the deck of cards
deck = list(it.product("♠♣♥♦", [str(x) for x in range(2, 11)] + list("JQKA")))

times_to_shuffle_deck = 5

for x in range(times_to_shuffle_deck):
    random.shuffle(deck)

# print(len(deck))
# print(deck)

# init the memo
memo = set()  
def set_shuffle(n):
    for i in range(n):
        k = random.choice(deck)
        if k not in memo:
            memo.add(k)
        else:
            set_shuffle(1)
    return memo #sorted(memo)

i = set_shuffle(52)

def deal(deck, number_players, number_cards):
    """ 
        Deals n amount of cards to n amount of players.
        """
    player_dict = {'Player' + str(k + 1): [i.pop() for x in range(number_cards)] for k in range(number_players)}

    dealer_hand = [i.pop() for x in range(number_cards)]
    player_dict['Dealer'] = dealer_hand

    return player_dict

players = deal(deck, number_players, number_cards)

def print_current_cards(player_dict):
    """ pretty prints the current hands """
    for k, v in player_dict.items():
        print('{} hand:'.format(k))
        for u in v:
            print(u[1], u[0])
        print('\n')
print_current_cards(players)

class Player:
    """Takes in a players cards for processing"""

    def __init__(self, cards, player):
        self.cards = cards
        self.player = player

    def pic_to_int(self, card):
        """takes in a card and converts it to an integer of 10 for JQK or 10 and 11 for A """

        card_list = ['J', 'Q', 'K', '10']

        if card in card_list:
            return 10
        elif card == 'A':
            return 11
        if card not in card_list and card != 'A':
            return int(card)

    def current_score(self):
        """ Evaluates the current list of cards and returns a total integer value"""
        total = 0

        if self.is_blackjack() == True:
            return
        else:
            for c in self.cards:
                for c1 in c:
                    for c2 in c1[1:]:
                        if c2.isdigit():
                            total += int(c2) 
                        elif 'A' == c2:
                            total += 11
                        else:
                            total += 10
        print('Current card total: ', total, '\n')
        return total

    def is_blackjack(self):
        """ Takes in 2 cards and returns True if the cards contain only 1x Ace and either 10, J, Q or K
        """
        if len(self.cards[0]) == 2:
            c1 = self.pic_to_int(self.cards[0][0][1])
            c2 = self.pic_to_int(self.cards[0][1][1])
            total = c1 + c2 if c1 or c2 else 0

            if total == 21:
                print('BLACKJACK!!!!')
                return 
            else:
                return False
        else:
            return False

    def check_if_bust(self):
        score = self.current_score()
        bust = False
        if score > 21:
            bust = True
            print("Bust!")
            return bust
        return bust

    def stick_or_twist(self, twist=False):
        """ Takes in current hand and processes a new card
            returns ok or bust
        """
        score = self.current_score()
        cards = self.cards
        if self.player == 'Dealer':
            if score < 17:
                cards[0].append(i.pop())
                self.check_if_bust()
            else:
                print('Dealer must stand on 17')

        elif self.player != 'Dealer':
            cards[0].append(i.pop())
            self.check_if_bust()

    def split_pair(self):
        """ splits a pair of the same cards into 2 games """
        pass


player1 = Player([v for k, v in players.items() if 'Player1' == k], 'Player1')
player2 = Player([v for k, v in players.items() if 'Player2' == k], 'Player2')
dealer = Player([v for k, v in players.items() if 'Dealer' == k], 'Dealer')

player_list = [player1, player2, dealer]

for p in player_list:
    print(p.player, ':')
    p.is_blackjack()
    p.current_score()
    p.check_if_bust()
    p.stick_or_twist(twist=True)

print(len(i), 'cards left')
print_current_cards(players)


# for x in range(3):

#     j = i.pop()
#     print(j)

# print(len(i), 'cards left')
\$\endgroup\$
2
\$\begingroup\$

In response to your question, a Deck class may look like this

class Deck():
    def __init__(self, times_to_shuffle_deck = 5):
        # create the deck of cards
        ...
        self.times_to_shuffle_deck = times_to_shuffle_deck

    def shuffle(self):
        # Here you can place code to shuffle

    def set_shuffle(self, n):
        # Here you can place function to set shuffle

    def deal(self, player, number_cards):
        # Here you could set a method to deal cards to a player
        # Probably less complex and easier to follow

Some minor recomendations:

-The class Player is doing a lot of work, you may consider extracting some methods outside, likepic_to_int or is_blackjack may go outside (not necessary in a class)

-The deal method I'd do shorter, since in that method you are creating something bigger than just dealing cards

  • When you pick this project in 3 months, and you want to create a player, you have to ask yourself which of this options is easier to remember:

    player1 = Player([v for k, v in players.items() if 'Player1' == k], 'Player1')
    
    player1 = Player('Player1')
    

So may be better to put fireworks with dict comprehensions inside the constructor

Regarding lists on lists and efficiency

In a blackjack game, roughly, you want to store

  • A list of all possible cards

  • A list of cards per player

  • A list of available cards (possible cards - player cards)

Then you can perform checks any way you want to discern player's points

You may want to try to design your app to have:

  • 1 possible_cards list + 1 available_cards list + n player_cards lists

If you think you're creating way too many lists, compare it with the minimum required and check where are you doing extra work

That was long.. Hope you spend some more time working on it

Don't hesitate to come back with more questions ;)

\$\endgroup\$
  • \$\begingroup\$ Thanks.. I have progressed somewhat and no doubt be back with more questions soon:) \$\endgroup\$ – johnashu Feb 18 '18 at 4:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.