2
\$\begingroup\$

This program is an implementation of the Blackjack which is similar to what is played in the casinos.

Here were the requirements:

  • I needed to create a simple text-based BlackJack game
  • The game needs to have one player versus an automated dealer
  • The player can stand or hit
  • The player must be able to pick their betting amount
  • Keep track of the player's total money
  • Alert the player of wins, losses, or busts, etc...

I need advice on the following topics:

  • My method and approach
  • Efficiency of my code and scope for cutting it short
  • Implementation of OOP
  • Suggestions for best practices
  • Styling approach
  • Readability
  • Comments and variable naming practices (Use of docstring)

Since I am new to CS and to Python and this is my first ever OOP program, please feel free to give me suggestions that could help me to better my programs in future.

import random # Random library is imported to enable us choose a random card

class Deck():
    """This is an abstract class. Player and  Dealer
    are meant to inherit its pick function for picking 
    a random card from the deck """

    deck_cards = ['A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2'] * 4
    #represents all the cards in the deck

    def pick(self, num):
        '''
        Picks the specified number of cards from the existing
        desk and returns it in a form of a list.
        '''

        picked = []
        for _ in range(num):

            choosen = random.randint(0, len(self.deck_cards) - 1)
            picked.append(self.deck_cards[choosen])

            #Removes the card that is picked up
            self.deck_cards.pop(choosen)

        return picked


class Player(Deck):
    """An object of this class keeps the track of players
    progress in the game including its cards, money, burst 
    situation. It also initiates the player with 500 units"""

    cards = []
    burst = False

    def __init__(self):
        self.money = 500


class Dealer(Deck):
    """An object of this class keeps the track of dealers
    progress in the game including its cards, and burst
    situation."""

    cards = []
    burst = False

#FUNCTIONS

def print_cards(cards):
    """prints the card value inside a box """

    for i in range(len(cards)):
        print("{0:^4}-----".format(''))
        print("{0:^4}|{1:^3}|".format('', cards[i]))
        print("{0:^4}-----".format(''))


def print_board(cards1, cards2):
    """Prints the complete board situation including both
    the players as well as the dealers cards"""

    print("\n"*50)
    print("*************")
    print("Player cards:")
    print("*************")
    print_cards(cards1)
    print("*************")
    print("Dealer cards:")
    print("*************")
    print_cards(cards2)
    print("*************")


def find_value(cards):
    """It finds the total value of the cards"""

    card_value = []
    total = 0
    ace = False
    value_dict = {'A': 11, 'K': 10, 'Q': 10, 'J': 10, '10': 10,
    '9': 9, '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2, }

    #makes a corrosponding car_value list
    for i in range(len(cards)):
        card_value.append(value_dict[cards[i]])
        if cards[i] == 'A':
            ace = True

    for i in range(len(card_value)):
        total += card_value[i]

    #Ace value can be 11 or 1 depending on the situation
    #makes sure that the 'Ace' value is taken in player's favour
    if total > 21 and ace:
        total -= 10

    return total


#GAMEPLAY

p1 = Player()
d1 = Dealer()

#Game stops only if the player wants to quit
#Or if he runs out of cash
while True:
    p1.cards = p1.pick(2)
    d1.cards = d1.pick(1)

    dealer_won = False

    print_board(p1.cards, d1.cards)

    #Asks for the bet amount till the player enter a valid amount
    while True:
            try:
                bet = int(input("Enter the amount of money you want to bet: "))
                if bet > p1.money:
                    print("You dont have sufficient funds!")
                    print("Your account balance is: {}".format(p1.money))
                    raise Exception
                break
            except Exception as e:
                print("Please enter a valid amount!")

    p1.money -= bet

    #Player turns continues till he passes or bursts
    while True:

        #Asks player choice for action till a valid choice is put
        while True:
            try:
                choice = input("Do you want to hit or pass? [h/p]: ")
                if choice.lower() not in ['h', 'p']:
                    raise Exception
                break
            except Exception as e:
                print("Oops! Please enter a valid choice")

        if choice.lower() == 'h':
            p1.cards += p1.pick(1)
            print_board(p1.cards, d1.cards)
        else:
            break

        if find_value(p1.cards) > 21:
            p1.burst = True
            dealer_won = True
            break

    #Dealer only plays if the player is not bursted yet
    if not dealer_won:
        while True:
            d1.cards += d1.pick(1)
            if find_value(d1.cards) > 21:
                d1.burst = True
                break

            if find_value(d1.cards) > find_value(p1.cards):
                dealer_won = True
                break

    print("\n" *50)
    print_board(p1.cards, d1.cards)

    #Winner determination and result printing
    if dealer_won:
        print("_"*50)
        print("Sorry you lost the game and the money.")
        if p1.burst:
            print("Your cards value exceeded 21. Be careful next time!")
            print("_"*50)
        else:
            print("Dealer got very lucky! Better luck next time! :)")
            print("_"*50)
    else:
        p1.money += 3*bet
        print("_"*67)
        print("Bingo! You won the game! Twice the bet is credited to your account.")
        if d1.burst:
            print("Dealer's total card value exceeded 21. You got lucky, well played!")
            print("_"*67)

    print("Your account balance is: {}".format(p1.money))

    #Asks if the player  wants to play again
    while True:
            try:
                play_again = input("Do you want to play again? [y/n]: ")
                if play_again.lower() not in ['y', 'n']:
                    raise Exception
                break
            except Exception as e:
                print("Oops! Please enter a valid choice")

    if play_again == 'n':
        break
    elif p1.money == 0:
        print("You did not have sufficient to continue playing!")
        break
\$\endgroup\$
1
\$\begingroup\$

Method and approach

Correctness

These one's I and not sure if it the intended behavior or not, so feel free to ignore these if they are actually correct.

In your function find_value, if the user had the hand Ace, Ace, Ten, your code would say they have the value of 22(1 + 11 + 10) and should bust, however, my understanding of blackjack is that all ace's can be a 1 or an 11, not just the first ace

Also, in blackjack, I am fairly sure the dealer is supposed to win in a tie

if find_value(d1.cards) > find_value(p1.cards):
    dealer_won = True

This indicates that the player wins in a tie to me.

Catching Exception is a bad idea

So, in python ample use of try except is encouraged, however, it is not a good idea to write except Exception, as this will catch more exceptions then you likely want. You can define your own exceptions. In this case I would do something like this

class BadInputException(Exception):
    pass

while True:
    try:
        choice = input("Do you want to hit or pass? [h/p]: ")
        if choice.lower() not in ['h', 'p']:
            raise BadInputException
        break
    except BadInputException:
        print("Oops! Please enter a valid choice")

And I would change all of your input handling sections to use this new exception

Use more functions

The last 100ish lines of code are kind of - no offense - a big blob of while loops. Lots of this could be extracted out into functions. You could have a getBet function for example instead of all these nested loops.

In fact, you already know that the big while loop at the end needs to be split up into several sections, it's clear by where you placed you comments you can see where which section starts and ends, each of those sections I would refactor into a function, then I would take all those function calls into one more function, so your code ends up being a bunch of function and class definitions, and then something like this.

while True:
    play_game()
    if not play_again():
         break

This will make your code easier to follow, as well it will be easier to test and maintain. Your could would benefit from being more modular.

Implementation of OOP

While I am not an expert on blackjack, it seems to me in most card games the player and the dealer would draw from the same deck. If this is what you intended, you will need to structure things a little differently.

You could have a general person class which has a pick method and has a deck field, and have both the player and the dealer inherit from that class, then the player and the dealer could both be passed the desired deck upon instantiation. This way when the dealer draws a card, the player can't draw the same one, or in you case, since there are no suits, both the player and the dealer could not be dealt 3 3's

Styling approach

For styling in python, it is recommended to follow pep8. There are lots of tools out there that will format your code to be pep8 compliant. Autopep8 is a good one, or if you are using an IDE, it likely has something built in to allow this.

Readability

This was pretty well done. Most variable names were descriptive, a few I would recommend changing are:

p1 = Player()
d1 = Dealer()

As well as cards1 and cards2 in the print_board function, as it it clear one is meant to be for the player, and the other is meant to be for the dealer

Your comments were alright for the most part, comments are to serve the purpose not of describing what your code is doing, but rather how and why it is doing that, this makes it easier when you look at this later to figure out what you did.

Your docstrings were all good one line descriptions, however, none of them describe what is returned or the values being passed in. Here is a good answer on how to format your docstrings.

\$\endgroup\$

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.