3
\$\begingroup\$

I have reached a point in my course that I feel I could use some feedback as to how my code looks and hopefully some direction on some best practice methodology going forward. I know I have a long way to go and there are problem a million better ways of doing what I did but I did use every concept I was taught up to this point.

It’s a simple blackjack game but I learned a lot making it

art.py

logo = """
.------.            _     _            _    _            _    
|A_  _ |.          | |   | |          | |  (_)          | |   
|( \/ ).-----.     | |__ | | __ _  ___| | ___  __ _  ___| | __
| \  /|K /\  |     | '_ \| |/ _` |/ __| |/ / |/ _` |/ __| |/ /
|  \/ | /  \ |     | |_) | | (_| | (__|   <| | (_| | (__|   < 
`-----| \  / |     |_.__/|_|\__,_|\___|_|\_\ |\__,_|\___|_|\_\\
      |  \/ K|                            _/ |                
      `------'                           |__/           
"""

Blackjack.py

############### Our Blackjack House Rules #####################

## The deck is unlimited in size. 
## There are no jokers. 
## The Jack/Queen/King all count as 10.
## The the Ace can count as 11 or 1.
## Use the following list as the deck of cards:
## cards = [11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10]
## The cards in the list have equal probability of being drawn.
## Cards are not removed from the deck as they are drawn.
## The computer is the dealer.

# required imports
import random
from art import logo
import os

#global Variables#################################################
cards = [11, 2, 3, 4, 5, 6, 7, 8, 9, 10,"J","Q","K"]
exit = False

##################################################################
#Clear terminal screen
def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

#####################################################################
#deal cards##########################################################
def deal_cards():
  hands = {
    "player" : [],
    "computer" : []
  }
  counter = 0
  while counter <= 1:
    hands["player"].append(cards[random.randint(0,12)])
    hands["computer"].append(cards[random.randint(0,12)])
    counter += 1 
  return hands # returns the hands as dealt by the computer

######################################################################
#get card total ######################################################
def card_total(player,hands):

  ace_list=[]
  low_cards=[]
  total = 0
  # process hand to remove aces and creat two lists
  for card in hands[player]:
        if card == 11:
            ace_list.append(card) 
        else:
            low_cards.append(card)
    
  # sum cards for subtotal
  for card in low_cards: 
        if card == "J" or card == "Q" or card == "K":
            card = 10
        total += card

  # process aces
  for ace in ace_list:  #decides which value to use for each ace
    if total + ace + len(ace_list) -1 > 21:
        total += 1 
    else:
        total += ace
  return total

########################################################################
#deal another card #####################################################
def get_card(player,hands):
  hands[player].append(cards[random.randint(0,12)])
  
####################################################
#player Processing################################
def player_turn(hands): 
  choice = ""
  while choice == "":
    print_cards(hands,False)
    choice = input("\n\nHit 'ENTER' to accept another card -- Type 'HOLD' to keep hand\n").lower()
    if choice == "":
      get_card("player",hands)
      if card_total("player",hands) > 21:
        return hands
    elif choice == "hold":
      return hands
    else:
        choice = ""

  return hands


##################################################### 
#computer play#######################################
def comp_turn(hands):
  while card_total("computer",hands) < card_total("player",hands):
    get_card("computer",hands)
  return hands


#####################################################
#create print pattern################################
def print_cards(hands,reveal):
    #clear()
    print(logo)
    print ("=" * 60)
    card_art = {
        "line1":[],
        "line2":[],
        "line3":[],
        "line4":[],
        "line5":[],
        "line6":[],
    }

##############################################
####player card pattern##########
    for card in hands["player"]:
        card_art["line1"].append(".------.")
        if card == 11:
            card = "A"

        if card != 10:
            id_line = ("| {}/\  |".format(card))

        elif card == 10:
            id_line = ("|10/\  |")

        card_art["line2"].append(id_line)
        card_art["line3"].append("| /  \ |")
        card_art["line4"].append("| \  / |")

        if card != 10:
            id_line = ("|  \/{} |").format(card)
        elif card == 10:
            id_line = ("|  \/10|")

        card_art["line5"].append(id_line)
        card_art["line6"].append("`------'") 

    #Card spacer  
    card_art["line1"].append("     < P | D >     ")
    card_art["line2"].append("     < L | E >     ")
    card_art["line3"].append("     < A | A >     ")
    card_art["line4"].append("     < Y | L >     ")
    card_art["line5"].append("     < E | E >     ")
    card_art["line6"].append("     < R | R >     ")

    #computer card pattern
    for card in hands["computer"]:
        if reveal == True:
            card_art["line1"].append(".------.")
            if card == 11:
                card = "A"

            if card != 10:
                id_line = ("| {}/\  |".format(card))

            elif card == 10:
                id_line = ("|10/\  |")

            card_art["line2"].append(id_line)
            card_art["line3"].append("| /  \ |")
            card_art["line4"].append("| \  / |")

            if card != 10:
                id_line = ("|  \/{} |").format(card)
            elif card == 10:
                id_line = ("|  \/10|")

            card_art["line5"].append(id_line)
            card_art["line6"].append("`------'")
        # Face card pattern        
        else :
            card_art["line1"].append(".------.")
            card_art["line2"].append("|  /\  |")
            card_art["line3"].append("| /  \ |")
            card_art["line4"].append("| \  / |")
            card_art["line5"].append("|  \/  |")
            card_art["line6"].append("`------'")
            reveal = True
    
    #print cards
    for line in card_art["line1"]:
        print (line,end ="") 
    print()                
    for line in card_art["line2"]:
        print (line,end="")
    print()
    for line in card_art["line3"]:
        print (line,end="")
    print()
    for line in card_art["line4"]:
        print (line,end="")
    print()
    for line in card_art["line5"]:
        print (line,end="")
    print()
    for line in card_art["line6"]:
        print (line,end="")
    print()

    
############################################################# 
############### Main Program ################################

while exit == False:
    player_win = "pending"
    cur_hands = deal_cards()
    cur_hands = player_turn(cur_hands)
    if card_total("player",cur_hands) > 21:
        player_win = False
    else:
        cur_hands = comp_turn(cur_hands)
        if card_total("computer",cur_hands) > 21:
            player_win = True

    if player_win == "pending":
        if card_total("player",cur_hands) > card_total("computer",cur_hands):
            player_win = True
        else:
            player_win = False

    if player_win == True:
            if card_total("computer",cur_hands) <= 21:
                print_cards(cur_hands,True)
                print ("You win !!!")
            else:
                print_cards(cur_hands,True)
                print("Computer busted You Win !!")
    

    elif player_win == False:
        if card_total("player",cur_hands) <= 21:
            print_cards(cur_hands,True)
            print("You Lose !!")
        else:
             print_cards(cur_hands,True)
             print("You busted You lose !!!")

    else:
        print("ERROR")
    choice = input("Hit enter to play again or type 'exit' to quit")
    if choice.lower()=="exit":
        exit = True
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

The game was fun to play (and I loved the graphics), which means you have achieved the number one goal in creating a game. Bravo!

I do have a few suggestions:

Perhaps the Game Should Recognize When the Player Is Dealt Blackjack

I was dealt the following hand:

.------..------.     < P | D >     .------..------.
| A/\  || J/\  |     < L | E >     |  /\  || Q/\  |
| /  \ || /  \ |     < A | A >     | /  \ || /  \ |
| \  / || \  / |     < Y | L >     | \  / || \  / |
|  \/A ||  \/J |     < E | E >     |  \/  ||  \/Q |
`------'`------'     < R | R >     `------'`------'

I was then asked if I wanted another card or not. I think we can assume that a player is smart enough to recognize when they have blackjack and would never take another card. Perhaps in this situation instead of prompting the player if they want another card the game should just declare the player the winner with blackjack. Moreover, I believe if the player is dealt a blackjack, then they immediately win unless the dealer also has blackjack.

Update

I just looked up the rules concerning being dealt a blackjack:

The Deal and "Blackjack"

At the start of a blackjack game, the players and the dealer receive two cards each. The players' cards are normally dealt face up, while the dealer has one face down (called the hole card) and one face up. The best possible blackjack hand is an opening deal of an ace with any ten-point card. This is called a "blackjack", or a natural 21, and the player holding this automatically wins unless the dealer also has a blackjack. If a player and the dealer each have a blackjack, the result is a push for that player.

If the dealer has a blackjack, all players not holding a blackjack lose.

Test For Erroneous Input In All Places

You have:

    ...
    choice = input("Hit enter to play again or type 'exit' to quit")
    if choice.lower()=="exit":
        exit = True

This should be:

    ...
    while True:
        choice = input("Hit enter to play again or type 'exit' to quit").lower()
        if choice in ("", "exit"):
            break
    exit = choice == "exit"

If your version of Python supports the walrus operator, then this can be simplified to:

    ...
    while (choice := input("Hit enter to play again or type 'exit' to quit").lower()) not in ("", "exit"):
        pass
    exit = choice == "exit"

I will have a couple of other comments in the next two sections related to this code concerning the text of the prompt and the use of a variable named exit.

User Interface

You have:

choice = input("\n\nHit 'ENTER' to accept another card -- Type 'HOLD' to keep hand\n").lower()

I would suggest you use terms known to Blackjack players, which are 'hit' to ask for another card and 'stay' to keep the current hand. I was also initially under the erroneous impression until I looked at the code that I had to enter 'HOLD' using all capital letters. Having to hold the shift key when entering letters is an unnecessary extra burden for the player. So I would use instead:

choice = input("\n\nEnter 'h' to hit or 's' to stay: ").lower()

Of course, you would have to modify function player_turn to check for 'h' and 's' instead. Note also that the user now enters the command on the same line as the prompt:

Enter 'h' to hit or 's' to stay: h

Similarly, I would change the following:

#choice = input("Hit enter to play again or type 'exit' to quit")
choice = input("Hit enter to play again or type 'q' to quit: ")

Do Not Redefine Built-in Functions

You have, for example:

exit = False
############### Main Program ################################

while exit == False:
    ...
    if choice.lower()=="exit":  # Or test for 'q' as I suggested
        exit = True

But exit is a built-in function. So prefer:

exit_game = False
...
############### Main Program ################################

while not exit_game:
    ...
    choice = input("Hit the Enter key to play again or type 'q' to quit: ")
    if choice.lower() == "q":
        exit_game = True

But if you want, you can get rid of the exit_game variable altogether:

############### Main Program ################################

while True:
    ...
    if choice.lower() == "q":
        break

Make Your Game "Importable"

I would also take all the code you have commented as the "Main Program" and place them in a function named, for example, play:

def play():
    #############################################################
    ############### Main Program ################################
    
    while True:
        ...

And then add a call to play if this Python file is being run as a script rather than being imported:

if __name__ == '__main__':
    play()

This allows another script to import and run blackjack from another script if that should ever become something you might want to do. For example, you might have a script that offers a menu of games to be played and depending on the selection imports different modules. For example:

some_other_script.py

...
if selection == 'Blackjack':
    import blackjack

    blackjack.play()
elif selection == 'Chess':
    import chess

    chess.play()
...

It's also just neater not to have so many global definitions if you can avoid it.

Follow PEP-8 Guidelines

Familiarize the PEP 8 – Style Guide for Python Code. For example, you are not following the guidelines for proper spacing. You have:

def card_total(player,hands):

  ace_list=[]
  ...

This should be:

def card_total(player, hands):
    ace_list = []
    ...

Use Docstrings and/or Type Hinting to Document Your Functions

For example:

#get card total ######################################################
def card_total(player,hands):
  ...

Should be:

from typing import Dict
...


def card_total(player: str, hands: Dict[str, int]) -> int:
    """Get the card total for the specified player.""
    ...

And freely use comments where it may not be clear what the code is doing.

Look For Efficiencies

Just one example:

You have in function print_cards a dictionary card_art with keys "line1", "line2" whose values are initially empty lists. You could have instead card_art a list of lists. So:

    card_art = {
        "line1":[],
        "line2":[],
        "line3":[],
        "line4":[],
        "line5":[],
        "line6":[],
    }
    ...
    for card in hands["player"]:
        card_art["line1"].append(".------.")

Becomes:

    card_art = [[] for _ in range(6)]  # A list of 6 lists
    line1, line2, line3, line4, line5, line6 = card_art  # Unpack
    ...
    for card in hands["player"]:
        line1.append(".------.")
        ...

Then after you have replaced an expression such as card_art["line1"] with just line1, we can take the following:

    ...
    #print cards
    for line in line1:
        print (line,end ="")
    print()
    for line in line2:
        print (line,end="")
    print()
    for line in line3:
        print (line,end="")
    print()
    for line in line4:
        print (line,end="")
    print()
    for line in line5:
        print (line,end="")
    print()
    for line in line6:
        print (line,end="")
    print()

And further simplify it to:

    #print cards
    for line in card_art:
        print(''.join(line))  # Combine the segments
\$\endgroup\$
5
  • \$\begingroup\$ I am going to. mplememt every one of those suggestions thank you very much that feedback is exactly what I needed \$\endgroup\$ Commented Jan 12 at 15:46
  • 1
    \$\begingroup\$ In the section about erroneous input, you have a colon at the end of the exit = choice == "exit": lines which should be removed. Also, about exit being a built-in name, quit is also a built-in function which is another alias for exiting. (The quit example also still assigns to exit.) But it's probably not as big of a deal to shadow either of them compared to most built-ins. \$\endgroup\$ Commented Jan 12 at 18:45
  • \$\begingroup\$ @duckboycool Yikes! Thanks for both piece of info. I will correct this! \$\endgroup\$
    – Booboo
    Commented Jan 12 at 19:39
  • \$\begingroup\$ Just a note: You don't need to import Dict from typing, just do dict[str, int]. \$\endgroup\$
    – Linny
    Commented Jan 12 at 20:33
  • \$\begingroup\$ @Linny I am running Python 3.8,5 and I do need to import Dict. If I try dict[str, int] I get TypeError: 'type' object is not subscriptable. \$\endgroup\$
    – Booboo
    Commented Jan 12 at 21:44
0
\$\begingroup\$

Card Values

I hate this:

cards = [11, 2, 3, 4, 5, 6, 7, 8, 9, 10,"J","Q","K"]

What is the type of cards? List[Union[int,str]] ... a list that contains one of two different types. Yuck!

Instead, use Dict[str, int] for the card-values, and perhaps a Tuple[str] for the possible cards.

CARD_VALUE = {"A": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
              "10": 10, "J": 10, "Q": 10, "K": 10}
CARDS = tuple(CARD_VALUE.keys())

The uppercase signifies these are global constants.

To choose a random card, random.choice(CARDS). No need to worry about whether it is random.randint(0,12) or random.randint(0,13) this way.

Note: I've defined Aces as having a value of only 1, instead of 11.

Total points

With the separation between cards and card values, we can easily compute the total points of a hand.

total = sum(CARD_VALUE[card] for card in hand)

Aces

At most one Ace will be valued as 11 points, otherwise two Aces would total 22 ... busting the hand. So instead of giving Aces a value of 11, I've given them a value of 1.

If you have a total of 11 or less, and one or more Aces, then you'd increase the value of only one of the Aces:

if total <= 11 and "A" in hand:
    total += 10

This makes the function a lot simpler.

def card_total(player: str, hands: Dict[str, List[str]]) -> int:
    """
    Total the points in the hand of the given player.
    Aces are normally worth 1 points, but the first Ace will be worth
    11 if it doesn't cause the hand to bust.
    """

    hand = hands[player]
    total = sum(CARD_VALUE[card] for card in hand)
    if 'A' in hand:
        total += 10
    return total
\$\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.