# Simple Blackjack game with card art

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

########################################################################
#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


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


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.

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

• I am going to. mplememt every one of those suggestions thank you very much that feedback is exactly what I needed Commented Jan 12 at 15:46
• 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. Commented Jan 12 at 18:45
• @duckboycool Yikes! Thanks for both piece of info. I will correct this! Commented Jan 12 at 19:39
• Just a note: You don't need to import Dict from typing, just do dict[str, int]. Commented Jan 12 at 20:33
• @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. Commented Jan 12 at 21:44

# 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
`