6
\$\begingroup\$

[Edited: added V2 link] BlackJack V2 - Github This is my first post, and I was actually going to post this on Stackoverflow when I was rather suggested posting the code here, as it may be better suited.

Here is the github repository, if that is allowed here, as it seems easy to receive suggestions.

BlackJack

I am looking for criticism as everything I have learned has been self taught, and mainly in this manner. Self-teach, practice, build, post, refine, move on.

The first bit of code would be the inherited code.

import time, itertools, random



def countyDots():
    for i in range(5):
            line = "." * i
            print(line, end="")
            print("\r", end="")
            time.sleep(0.5)



class Card(object):

    def __init__(self, rank, suit):

        self.rank = rank
        self.suit = suit

        self.valueChart = {
            "2": 2,
            "3": 3,
            "4": 4,
            "5": 5,
            "6": 6,
            "7": 7,
            "8": 8,
            "9": 9,
            "10": 10,
            "J": 10,
            "Q": 10,
            "K": 10,
            "A": 11,
        }


    def getValues(self):

        cardValues = self.valueChart
        returnedValue = 0

        for rank, value in cardValues.items():

            if self.rank == rank:
                returnedValue = value

        return returnedValue  
        



class Player(object):

    def __init__(self):
        self.name = self.defineName()

        #initialize player bank to $200
        self.balance = 200
        print("Beginning Balance: $200.00 USD\n")

        ##initialize player hand
        self.hand = []
        

        #initialize score and currentBet to 0
        self.score = 0


        self.bet = 0


    def placeBet(self):
        
        currentBet = input(f"BALANCE: ${self.balance}\nHow much would you like to bet? (1, 2, 5, 10, 25):  ")
        
        def is_number(n):
            try:
                int(n)# Type-casting the string to `float`.
                        # If string is not a valid `float`, 
                        # it'll raise `ValueError` exception
            except ValueError:
                return False
            return True

        if is_number(currentBet):
            currentBet = int(currentBet)
            if currentBet in (1, 2, 5, 10, 25):
                self.balance -= currentBet
                self.bet = currentBet
                print(f"\nCurrent Bet: {currentBet}\nBALANCE: {self.balance}\n")
            else:
                self.placeBet()
        else:
            self.placeBet()


        
    def defineName(self):

        #Ask for name from user
        newName = input("What is your Name?:  ")

        #confirm new name with user
        confirmation = input(f"Your name is {newName}, correct? (Y/N):   ")
        
        #convert confirmation to all lowercase and eliminate whitespace
        confirmation = confirmation.lower().strip()

        while confirmation != "y":

            newName = input("Sorry. What is your Name?:  ")
            countyDots()
            confirmation = input(f"So you prefer to be called {newName}?  (Y/N):   ")
            confirmation = confirmation.lower().strip()

        if confirmation == "y":
            return newName
            

    def showHand(self):

        print(f"{self.name}'s HAND")

        for card in self.hand:
            rank = card[0]
            suit = card[1]
            print(f"{rank} of {suit}")



    def getAction(self):

        action = input("Would you like to HIT or STAND?:  ")

        action = action.lower().strip()

        return action


    def calculateScore(self):

        self.score = 0

        for card in self.hand:
            rank = card[0]
            suit = card[1]
            card = Card(rank, suit)
            

            value = card.getValues()
            self.score += value
        
        print(f"{self.name}'s SCORE: {self.score}\n")
        return self.score

    def showBalance(self):

        print(f"BALANCE: {self.balance}")






class Dealer(object):

    def __init__(self):
        self.name = "Dealer"

        #initialize a dealer score to 0
        self.score = 0

        #initialize a dealer hand
        self.hand = []

        #initialize a dealer bank of 1,000,000
        self.bank = 1000000


    def showHand(self):

        print(f"{self.name}'s HAND")

        for card in self.hand:
            rank = card[0]
            suit = card[1]
            print(f"{rank} of {suit}")

        


    def calculateScore(self):

        self.score = 0

        for card in self.hand:
            rank = card[0]
            suit = card[1]
            card = Card(rank, suit)
            

            value = card.getValues()
            self.score += value
        
        print(f"{self.name}'s SCORE: {self.score}\n")
        return self.score

    






class Deck(object):


    def __init__(self):

        self.deck = []
        self.ranks = (
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "10",
            "J",
            "Q",
            "K",
            "A",
        )



    def buildDeck(self):

        suits = ("Spades ♠", "Clubs ♣", "Hearts ♥", "Diamonds ♦")
        cards = list(itertools.product(self.ranks, suits))
        random.shuffle(cards)

        for card in cards:
            self.deck.append(card)

        return self.deck


class Shoe(Deck):

    def __init__(self):

        self.shoe = []

    
    def buildShoe(self):

        for i in range(5):


            newDeck = Deck()
            newDeck.buildDeck()
            for card in newDeck.deck:
                self.shoe.append(card)

    
    def dealCard(self):

        gameDeck = self.shoe
        dealtCard = self.shoe[0]
        self.shoe.pop(0)

        return dealtCard

The Second bit of code is the gameLogic file which executes at the bottom.





class GameLogic(object):

    def __init__(self, player, dealer, shoe):

        self.player = player
        self.dealer = dealer
        self.gameShoe = shoe

    
    def beginGame(self):

        self.dealer.hand = []
        self.player.hand = []

        #initalize the players first bet
        self.player.placeBet()

        #initialize the shoe of cards (5 decks of cards)
        self.gameShoe.buildShoe()
        

        self.player.hand.append(self.gameShoe.dealCard())
        self.dealer.hand.append(self.gameShoe.dealCard())
        self.player.hand.append(self.gameShoe.dealCard())
        self.dealer.hand.append(self.gameShoe.dealCard())

        self.player.showHand()
        self.player.calculateScore()
        countyDots()
        self.dealer.showHand()              #These need to be changed so that they reflect one face up card
        self.dealer.calculateScore()        #and one face down card
        countyDots()

        self.playerAction(self.player.getAction())

    def keepPlaying(self):

        userResponse = input("\nPRESS ENTER FOR NEXT HAND\nType 'EXIT' to quit game\n")
        userResponse = userResponse.lower().strip()

        if userResponse == "":
            self.beginGame()

        elif userResponse == "exit":
            exit()

        else:
            self.keepPlaying()
    
    def playerAction(self, action):

        

        if action == "hit":
            print("You chose to Hit")

            #player takes on an additional card
            self.player.hand.append(self.gameShoe.dealCard())
            self.player.showHand()
            self.player.calculateScore()


            if self.player.score <= 21:
                newAction = self.player.getAction()
                self.playerAction(newAction)

            elif self.player.score > 21:
                print("BUSTED!!! YOU LOSE")
                #Proceed to 
                self.dealer.showHand()

                #set currentbet to 0
                self.player.bet = 0

                self.keepPlaying()
                    #show dealers hand for good faith
                    #set player bet to 0
                    #set forth with "would you like to play another round"

            #if player total score isnt higher than 21, we ask the same question
                #if the player total score is higher than 21, we end the game immediately and proceed to ask endgame()

        elif action == "stand":
            print("You chose to STAND\n")

            #if action is to stand then we need to proceed forward with the game logic for the dealer.
            self.dealer.showHand()
            self.dealer.calculateScore()

            while self.dealer.score < 16:
                countyDots()
                self.dealer.hand.append(self.gameShoe.dealCard())
                self.dealer.showHand()
                self.dealer.calculateScore()
                


            if self.dealer.score > 21:
                print("The dealer has busted!")
                
                #pay the player the player 2times their bet
                payout = self.player.bet * 2
                self.player.balance += payout
                print(f"{self.player.name} WON {self.player.bet}")
                self.player.showBalance()

                #reset bet to 0 for the next round
                self.player.bet = 0
                self.keepPlaying()

            
            elif self.dealer.score <= 21 and self.dealer.score >= 16:
                
                if self.player.score > self.dealer.score:
                    print("The player WINS!")
                    #pay the player the player 2times their bet
                    payout = self.player.bet * 2
                    self.player.balance += payout
                    print(f"{self.player.name} WON {self.player.bet}")
                    self.player.showBalance()

                    #reset bet to 0 for the next round
                    self.player.bet = 0
                    self.keepPlaying()


                elif self.player.score <= self.dealer.score:
                    print("The PLAYER LOSES!")
                    print(f"{self.player.name} LOST {self.player.bet}")
                    self.player.showBalance()

                    #reset bet to 0 for the next round
                    self.player.bet = 0
                    self.keepPlaying()    

        else:
            print("Some Real Bad shit happened herrrrrr")


    















#initialize a dealer
dealer1 = Dealer()

#initialize a player
player1 = Player()

#initialize a game shoe
gameShoe = Shoe()

#Initialize GamePlay
game = GameLogic(player1, dealer1, gameShoe)

#Begin Game Play
game.beginGame()



\$\endgroup\$
2
  • 3
    \$\begingroup\$ As the first review doesn't mention it: Follow the Style Guide for Python Code. \$\endgroup\$
    – greybeard
    Commented Oct 4, 2022 at 7:28
  • \$\begingroup\$ Followup question posted here \$\endgroup\$
    – AJNeufeld
    Commented Oct 7, 2022 at 16:11

4 Answers 4

2
\$\begingroup\$
  • Looks like you are from different language background. To name methods, you should use underscores rather than camelcase. For example the first method should be named based on that county_dots

  • Consider using typing hints, they will increase some basic readability of your code - for example it's not completely obvious, what date type is the card rank - if it is the number or string as per your valueChart variable.

  • I expect the Card attribute valueChart is basically constant. It doesn't have to be instance variable, move this outside of the class or declare it on the class level.

  • I would rename getValues to just values (Java background? :) )

  • Rather to see comments, I'd sometimes just name the functions and parameters better so that the function call has the same information value as the comment and therefore you can remove the comment. For example:

      #initialize the shoe of cards (5 decks of cards)
      self.gameShoe.buildShoe()
    

Perhaps name the function better and even pass decks=5 as named parameter to make it clear, it is about 5 decks of cards:

    self.gameShoe.initialize(decks=5)

Another example, where I see the comment pointless even like this, just reducing readability. I would also consider creating "reset_bet" method instead so that your code shows actions rather than modifying data:

            #set currentbet to 0
            self.player.bet = 0
  • Make smaller methods, extract for better readability and extensibility.

Example:

if self.player.score <= 21:

Can be improved to:

if self.player.still_in_play():

(name is questionable, maybe you will think of a better one).

  • I would rename all showXX methods to print_XX.

  • I am not sure what is a type of architecture you are trying to do, but if it is OOP with DDD, etc., there is way too much code in your GameLogic class. Some of it should be closer the the classes themselves. Your approach seems a bit more functional, having classes mostly as data containers so maybe that's what you want. Then I would consider @dataclass annotation and remove all logic from those completely (also some of my previous comments wouldn't make sense). But I don't think that is the way you probably wanted. Edit: Just noticed the title, ha! My bad :)

\$\endgroup\$
3
  • 1
    \$\begingroup\$ My man, I appreciate this so much. I do not have to ability to have a formal mentor for coding. I sold my business and picked programming back up a year ago, beginning with Harvard's CS50 online course. Since then I have been in and out of speaking spaces (which usually become toxic or heavily filled with web security, so it still gets toxic.) My cousin is high up in SONY but I cant get 30 mins a week with my dude, so I am left to self teaching and hoping the 4k Im paying General assembly to teach me javascript is all worth it. The best advice I was given was just BUILD STUFF and learn. \$\endgroup\$ Commented Oct 4, 2022 at 16:59
  • 1
    \$\begingroup\$ Cool! So far I found this community very welcoming and always getting good feedback :) \$\endgroup\$
    – K.H.
    Commented Oct 4, 2022 at 18:10
  • \$\begingroup\$ V2 added above! \$\endgroup\$ Commented Oct 7, 2022 at 17:23
2
\$\begingroup\$

Python 3 Classes

class Card(object): is deprecated Python 2 syntax. With Python 3, the (object) base class is implied and just unnecessary clutter. Just write class Card:.

One Source of Truth

Each Card has a dictionary converting the card rank to a value. With 52 cards in a deck, you'll have 52 identical dictionaries. With 5 decks in the shoe, you'll have 260 identical dictionaries!

Additionally, each Deck has a tuple of ranks. 5 decks yields 5 identical tuples of card ranks.

(Again, additionally, a Shoe inherits from Deck (brokenly, more later), so even more copies???)

You should specify the card ranks exactly once in your program, in one global structure. An Enum would be a good choice for this:

from enum import Enum

class Rank(Enum):
    TWO = ('2', 2)
    THREE = ('3', 3)
    FOUR = ('4', 4)
    FIVE = ('5', 5)
    SIX = ('6', 6)
    SEVEN = ('7', 7)
    EIGHT = ('8', 8)
    NINE = ('9', 9)
    TEN = ('10', 10)
    JACK = ('J', 10)
    QUEEN = ('Q', 10)
    KING = ('K', 10)
    ACE = ('A', 11)

    @property
    def symbol(self):
        return self.value[0]

    @property
    def points(self):
        return self.value[1]

Similarly, you could use an Enum for suits:

class Suit(Enum):
    CLUBS = ('Clubs', '♣')
    DIAMONDS = ('Diamonds', '♦')
    HEART = ('Hearts', '♥')
    SPADES = ('Spades', '♠')

    @property
    def suit_name(self):
        return self.value[0]

    @property
    def symbol(self):
        return self.value[1]

Card

A card is pair of values: rank & suit. But more than that, it has a string which describes it, like "Queen of Clubs ♣". You use f"{rank} of {suit}" in two different places to convert the a card (actually a tuple) into a suitable string. Since you have a Card class, it should be able to convert itself into a suitable string.

A card has a point value, which (in the case of blackjack) is determined solely by the card's rank. But we shouldn't ask the card for its rank and then convert that into a point value, rather we should just ask the card for its point value, and let it determine its value from its rank & suit.

Cards are immutable. If you have a Card, you shouldn't be able to change it to another suit or rank with a statement like card.rank = 'A'. We can use a dataclass to make our Card object resistant to malicious changes:

from dataclasses import dataclass

...

@dataclass(frozen=True)
class Card:
    rank: Rank
    suit: Suit

    def point_value(self):
        return self.rank.points

    def __str__(self):
        return f"{self.rank.symbol} of {self.suit.suit_name} {self.suit.symbol}"
>>> card = Card(Rank.ACE, Suit.SPADES)
>>> print(card)
A of Spades ♠
>>> print(card.point_value())
11

Deck

class Deck(object):
    def __init__(self):
        self.deck = []

Strange. If I create a deck of cards, deck = Deck(), it doesn't have any cards it in!

The constructor for a deck of cards should actually create a deck of cards:

class Deck(object):
    def __init__(self):
        self.deck = [Card(rank, suit) for rank, suit in itertools.product(Rank, Suit)]

Note the above actually creating a list of Card objects here, not simply tuples of rank and suit values.

Your Deck class seems rather limited in its functionality. You can't return cards to the deck, you can't shuffle the remaining cards in the deck, and so on.

Shoe

class Shoe(Deck):
    def __init__(self):
        self.shoe = []

As written, a Shoe is a Deck, but without any of the functionality of a Deck. Although it inherits from Deck, since super().__init__() is never called, this inheritance is broken. Show().buildDeck() will give an AttributeError.

A Shoe is not be a Deck; don't inherit from it.

The shoe is constructed from 5 decks, sequentially! Imagine you have 5 decks, with red, yellow, green, blue, and orange backs. You're shuffling each of those decks individually, and then stacking them one on top of the other in the shoe without shuffling them together. This makes it impossible (instead of just unlikely) to draw three "Ace of Spaces ♠" sequentially from the shoe.

You should add cards from the 5 decks into the shoe, and THEN shuffle the shoe afterwards.

\$\endgroup\$
5
  • \$\begingroup\$ Enumerations are something that I am not as comprehensively involved with yet. As mentioned above, I have noted every suggestion and criticism here. I am reviewing more documentation and will be rewriting the game with these thoughts/criticisms in mind. My code did/does naturally feel unnecessarily verbose. I appreciate every word here and will be posting a, hopefully, better version. \$\endgroup\$ Commented Oct 4, 2022 at 20:35
  • \$\begingroup\$ I cant express enough again how much I appreciate comments like this. The amount of direction and knowledge you imparted on me is empowering. Thank you. \$\endgroup\$ Commented Oct 5, 2022 at 17:10
  • \$\begingroup\$ When you are "posting a, hopefully, better version", ensure you add a link in this question post to the new post, and a link from the new post back to this one. See Posting a new question for details. \$\endgroup\$
    – AJNeufeld
    Commented Oct 6, 2022 at 16:02
  • \$\begingroup\$ Thank you My friend. I appreciate everything. I am looking forward to the day I can join a team and continually be surrounded by more and more experienced people. \$\endgroup\$ Commented Oct 6, 2022 at 22:30
  • \$\begingroup\$ V2 added above! \$\endgroup\$ Commented Oct 7, 2022 at 17:23
2
\$\begingroup\$

Unwanted recursion

Several of your methods contain unwanted recursions, i.e. these methods contain calls to themselves. I noticed that in (as in Player.placeBet(), GameLogic.keepPlaying(), and GameLogic.playerAction(), but there may be more. This is very dangerous – it basically means that if you play long enough, your game will run out of memory, and will crash with a RecursionError.

This means that you will have to revise these sections, probably by making use of appropriate while loops. Here's a revision of placeBet() – note the use of the while True loop, which is a very popular Python idiom. You may want to adopt for the other problematic methods as well.

    def place_bet(self):
        while True:
            response = input(f"BALANCE: ${self.balance}\n"
                             "How much would you like to bet? (1, 2, 5, 10, 25):  ")
            try:
                bet = int(response)
            except ValueError:
                print("Please enter a number!")
            else:
                if bet not in (1, 2, 5, 10, 25):
                    print("Only bets of 1, 2, 5, 10, or 25 are possible.")
                else:
                    self.balance -= bet
                    self.bet = bet
                    print(f"\nCurrent Bet: {currentBet}\nBALANCE: {self.balance}\n")                        
                    break

Too many local variables

Many of your methods contain unnecessary local variables. For instance, Card.getValues() creates cardValues (a copy of valueChart), as well as the variable returnedValues. You iterate through cardValues by creating two more loop variables rank and value, which are also unneeded: basically, all you need here is this:

def getValues(self):
    return self.valueChart[self.rank]

Given that, you may consider getting rid of getValues() in the first place as it could be replaced by a simple dictionary look-up.

Overpowered method

The method GameLogic.playerAction() is overpowered – it does way too much, much more than you'd expect. Not only does it handle the selected action, it also seems to be responsible for the dealer's reaction e.g. when the player loses, it's responsible for the cashout, and it resets for the next round. You really need to break up the logic here into smaller bits.

Wrong, missing, or dangerous game behavior

Your code currently behaves somewhat unexpectedly at times. Things that I've noticed:

  • It's possible to bet more than you currently have
  • Shouldn't you insta-win if your starting hand is a blackjack?
  • Your game is very unforgiving when it comes to typing mistake (e.g. hir instead of hit). Mistakes like this should be caught e.g. by another while loop.
\$\endgroup\$
5
  • 1
    \$\begingroup\$ I appreciate all of this constructive criticism. I am running through some more information and documentation now, and I will start rewriting the game with all of this advice in mind. I really do appreciate every word that I received here. This is how I have learned to best self-teach. \$\endgroup\$ Commented Oct 4, 2022 at 20:29
  • 1
    \$\begingroup\$ I am currently rebuilding the program and when I came to use the try/except, in the exact same manner you have mentioned above, the error I get is a ValueError instead of a TypeError. I say this not to point out any potential error, but because I do not understand WHY it is a ValueError versus a TypeError ValueError: invalid literal for int() with base 10: \$\endgroup\$ Commented Oct 5, 2022 at 20:37
  • \$\begingroup\$ @InfiniteGrasp: Good catch! You can stop trying to understand why your code doesn't behave like mine – I've made a mistake: Trying to convert an invalid string to an integer does indeed throw a ValueError and not a TypeError. I've fixed my answer in response to your comment. \$\endgroup\$
    – Schmuddi
    Commented Oct 5, 2022 at 22:12
  • \$\begingroup\$ I actually submitted the question to stackoverflow because I am genuinely curious. I moved past and am not trying to actually correctly build the deck of cards, which is easier than I thought now that I am getting the gist of enumerations. To the above though, it seems like int() itself accepts all types, but only will accept values of base 10...This is the best I could come up with as to why this was the correct reasoning. Is this in the ball park? \$\endgroup\$ Commented Oct 5, 2022 at 22:16
  • \$\begingroup\$ V2 added above! \$\endgroup\$ Commented Oct 7, 2022 at 17:23
2
\$\begingroup\$

A lot have been said by the others and I feel that it may be unnecessary to repeat them. I also have another take on the problem you presented:

import random
import time

class Card:

    cards_values = {
        "2": 2,
        "3": 3,
        "4": 4,
        "5": 5,
        "6": 6,
        "7": 7,
        "8": 8,
        "9": 9,
        "10": 10,
        "J": 10,
        "Q": 10,
        "K": 10,
        "A": 11,
    }

    allowed_suits = {
        "Spades ♠", 
        "Clubs ♣", 
        "Hearts ♥", 
        "Diamonds ♦"
    }

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    @property
    def value(self):
        return self.cards_values[self.rank]

class Participant:

    def __init__(self, name, balance):
        self.name = name
        self.balance = balance
        self.clear_hand()
    
    def receive_card(self, card):
        self.hand.append(card)
        self.score += card.value

    def clear_hand(self):
        self.hand = []
        self.score = 0

    def wins(self, amount):
        self.balance += amount

    def loses(self, amount):
        self.balance -= min(amount, self.balance)
    
    @staticmethod
    def ask_user_for_choice(question, choices, please_give_good_answer):
        not_chosen = True
        while not_chosen:
            answer = input(question).upper().strip()
            if not_chosen := (answer not in {x.upper().strip() for x in choices}):
                print(please_give_good_answer)
        return answer   

    def show_hand_and_score(self):
        # Show dots
        self.show_dots()

        hand = "\n".join(
            (
                f"{self.name}'s hand:",
                '\n'.join(
                    f"{current_card.rank} of {current_card.suit}" 
                    for current_card in self.hand
                )
            )
        )
        print(hand)
        print(f"{self.name}'s score: {self.score}")

    @staticmethod
    def show_dots():
        for nb_dots in range(5):
            print("." * nb_dots, "\r", end='')
            time.sleep(0.5)
        print()

    def show_balance(self):
        print(f"{self.name}'s BALANCE: {self.balance}$")

    def has_busted(self):
        if has_busted := self.score > 21:
            print(f"{self.name} has busted!")
 
        return has_busted

class Player(Participant):

    def ask_bet(self):
        self.show_balance()
        current_bet = int(
            self.ask_user_for_choice(
                f"How much would you like to bet? (1, 2, 5, 10, 25): ",
                ("1", "2", "5", "10", "25"),
                "Please only choose your bet as one of these choices: 1, 2, 5, 10 or 25"
            )
        )

        if current_bet > self.balance:
            raise ValueError(f'Balance (${self.balance})is not big enough for bet (${current_bet})')
        
        return current_bet

    def ask_if_hit_is_next_move(self):
        player_action = self.ask_user_for_choice(
            "Would you like to HIT or STAND? ",
            ('HIT', 'STAND'),
            'Please only answer "HIT" or "STAND"'
        )
        return player_action == 'HIT'
    
    def has_busted_after_hitting(self, card_received):
        self.receive_card(card_received)
        self.show_hand_and_score()
         
        return self.has_busted ()

    def has_won(self, dealers_score):
        return self.score > dealers_score

    def wins(self, current_bet):
        super().wins(current_bet)
        print(f"{self.name} WON {current_bet}$")

    def loses(self, current_bet):
        super().loses(current_bet)
        print(f"{self.name} LOST {current_bet}$")

    def ask_if_we_continue(self):
        player_answer = self.ask_user_for_choice(
            "\nPRESS ENTER FOR NEXT HAND\nType 'EXIT' to quit game\n",
            ('', 'EXIT'),
           'Please only press enter for next hand or type "EXIT" to quit the game'
        )
        return player_answer == 'EXIT'

class Dealer(Participant):
    
    def __init__(self, name, balance, nb_decks):
        super().__init__(name, balance)
        self.nb_decks = nb_decks
        self.shoe = None

    def deal_card(self):
        if not self.shoe:
            self.create_shoe()

        return next(self.shoe)

    def create_shoe(self):

        def create_deck():

            cards = [
                Card(current_rank, current_suit) 
                for current_rank in Card.cards_values 
                for current_suit in Card.allowed_suits
            ]
            random.shuffle(cards)

            return cards

        self.shoe = (
            current_card 
            for _ in range(self.nb_decks)
            for current_card in create_deck()
        )

    def has_busted_after_getting_cards(self):
        self.show_hand_and_score()

        while self.score < 16:
            self.receive_card(self.deal_card())
            self.show_hand_and_score()

        return self.has_busted()

    def has_enough_funds_for_bet(self, current_bet):
        return self.balance >= current_bet

    def already_won(self):
        return self.score == 21

    def show_hand_and_score(self, hidden=False):
        if hidden:
            self.show_dots()
            hand  = "\n".join(
                (
                    f"{self.name}'s hand:",
                    f"{self.hand[0].rank} of {self.hand[0].suit}", 
                    "(hidden card)"
                )
            )
            print(hand)
        else:
            super().show_hand_and_score()

class Blackjack_table:

    def __init__(self, bank=1000000, nb_decks=5, default_player_starting_bablance=200):

        name_not_confirmed = True
        while name_not_confirmed:
            player_name = input("What name do you want to use? ")
            name_not_confirmed = Player.ask_user_for_choice(
                f"Is your name {player_name} (Y/N)? ",
                'YN',
                'Please answer only "Y" or "N"'
            ) == 'N'

        self.player = Player(player_name, default_player_starting_bablance)
        self.dealer = Dealer("Dealer", bank, nb_decks)

    def play_rounds(self):

        finished_playing = False
        while not finished_playing:

            if self.player.balance <= 0:
                print("You don't have any funds left")
                break

            if self.dealer.balance <= 0:
                print("The bank has lost all their funds.")
                break

            self.player.clear_hand()
            self.dealer.clear_hand()

            try:
                self.current_bet = self.player.ask_bet()
            except ValueError as bet_error:
                print(bet_error.args[0])
                continue

            if not self.dealer.has_enough_funds_for_bet(self.current_bet):
                print("Sorry, the dealer doesn't have enough funds for this bet")
                self.dealer.show_balance()
                finished_playing = self.player.ask_if_we_continue()
                continue
            
            for _ in range(2):
                self.player.receive_card(self.dealer.deal_card())
                self.dealer.receive_card(self.dealer.deal_card())
                
            self.player.show_hand_and_score()
            self.dealer.show_hand_and_score(hidden=True)
            
            if self.player.has_busted():
                self.dealer_wins()
                finished_playing = self.player.ask_if_we_continue()
                continue
            if self.dealer.has_busted():
                self.player_wins()
                finished_playing = self.player.ask_if_we_continue()
                continue

            round_is_not_finished = True
            while round_is_not_finished:
                if self.player.ask_if_hit_is_next_move():
                    if self.player.has_busted_after_hitting(self.dealer.deal_card()):
                        self.dealer.show_hand_and_score()
                        self.dealer_wins()
                        round_is_not_finished = False
                    # round_is_not_finished stays True otherwise
                else: # player is standing
                    if self.dealer.has_busted_after_getting_cards():
                        self.player_wins()
                    else:
                        if self.player.has_won(self.dealer.score):
                            self.player_wins()
                        else:
                            self.dealer_wins()
                    round_is_not_finished = False
                    
            finished_playing = self.player.ask_if_we_continue()

        print("Gave over. Hope to see you sson!")

    def player_wins(self):
        print("The player WINS!")
        self.player.wins(self.current_bet)
        self.dealer.loses(self.current_bet)
        self.player.show_balance()
        self.dealer.show_balance()

    def dealer_wins(self):
        print("The player LOSES!")
        self.dealer.wins(self.current_bet)
        self.player.loses(self.current_bet)
        self.player.show_balance()
        self.dealer.show_balance()

if __name__ == '__main__':
    table = Blackjack_table(100)
    table.play_rounds()

In addition to what the others have already said I would add that you should look into:

For most part, I try to :

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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