# Blackjack / 21 in Python3

I have created a blackjack game in Python3 but I am worried that it is not very efficient and also there are some problems with it.
Problem 1) The player almost always wins unless he goes bust as the program allows the player to take multiple cards but the computer only chooses 2 and I can't think of any solutions.
Problem 2) Because of how I have done the choosing of the suits it is entirely possible to get e.g. 2 Kings of spades which is impossible with a regular deck of cards. Since I am new to python any help would be much appreciated.

import random

cards = {
"Ace": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5,
"six": 6,
"seven": 7,
"eight": 8,
"nine": 9,
"ten": 10,
"Jack": 10,
"Queen": 10,
"King": 10
}

suits = [" of spades", " of clubs", " of hearts", " of diamonds", " of spades", " of clubs", " of hearts",
" of diamonds", " of spades", " of clubs", " of hearts", " of diamonds", " of spades", " of clubs",
" of hearts", " of diamonds", " of spades", " of clubs", " of hearts", " of diamonds", ]

again = "yes"

while again == "yes":

def firstcards():
global first_card
global second_card
global total
total = 0
first_card = random.choice(list(cards))
second_card = random.choice(list(cards))
total += cards.get(first_card)
total += cards.get(second_card)

firstcards()

def gameplay():
global total
another_card = "yes"
print("Your first card is the " + first_card + random.choice(suits))
print("Your second card is the " + second_card + random.choice(suits))
print("Your total is: " + str(total))

while another_card == "yes" and total < 21:
print("Would you like another card?")
another_card = input("Y/N:  ")
if another_card == "Y":
card = random.choice(list(cards))
total += cards.get(card)
print("Your next card is the " + card + random.choice(suits))
print("Your total is now: " + str(total))
another_card = "yes"
elif another_card == "N":
num1 = random.randint(1, 10)
num2 = random.randint(1, 10)
computerTotal = num1 + num2
print("The computer got a total of " + str(computerTotal))
if computerTotal > total:
print("The computer wins!")
else:
print("You win!")
another_card = "no"
else:
print("That is not a valid option.")
another_card = "yes"
if total > 21:
print("You have gone bust!")
print("The computer wins!")

def play_again():
global again
print("")
print("Would you like to play again?")
again = input("yes/no: ")
if again != "yes" and again != "no":
print("That is not a valid option")
play_again()
elif again == "yes":
print("")
print("")
elif again == "no":
again = "no"
play_again()

gameplay()

Once again any help would be much appreciated.

## 1 Answer

It's hard to make small tweaks to this code to make it do what you want, so I'm going to give some pointers on how to do a wholesale rewrite.

Before you can write a card game, you need to write a data model for a deck of cards. Picking the right data model makes everything else easy (part of why your program was hard to write and is hard to fix that the cards were modeled in a way that didn't make the rest of the program easy). Here's how I'd do it:

from enum import auto, Enum
from random import shuffle
from typing import List, NamedTuple

class Rank(Enum):
ACE = 1
TWO = 2
THREE = 3
FOUR = 4
FIVE = 5
SIX = 6
SEVEN = 7
EIGHT = 8
NINE = 9
TEN = 10
JACK = 10
QUEEN = 10
KING = 10

class Suit(Enum):
SPADES = auto()
CLUBS = auto()
DIAMONDS = auto()
HEARTS = auto()

class Card(NamedTuple):
rank: Rank
suit: Suit
def __repr__(self) -> str:
"""Pretty-print the name of the card, e.g. 'Queen of Hearts'"""
return f"{self.rank.name.title()} of {self.suit.name.title()}"

There's my Card class -- every card has a rank and suit. Enums have the convenient built-in functions name and value that I can use for pretty-printing and also for scoring (I set the value of my Rank enum to correspond to standard scoring numbers).

A NamedTuple is an easy way to build a class that contains multiple other values that never change; it automatically implements a constructor to set its member variables and in many cases you don't need to do anything other than declare what those variables are, but you can also add other methods to it. My Card implements a magic __repr__ function so that anywhere we print one (including inside a list) it will get formatted as something like Queen of Hearts rather than the default NamedTuple formatting of Card(rank=Rank.QUEEN, suit=Suit.HEARTS).

And here's a really quick example of how you could use this class to implement a game like blackjack. This isn't an actual blackjack game, I've just implemented the dealer part to demonstrate how building a deck, shuffling, and dealing would work, but hopefully you can play with this code and see how simple everything is when you have a nice data model. In particular something like the best_value function is going to be really important to a blackjack game (and it's a lot easier to define it as its own function and then be able to call it in a bunch of places).

deck = [Card(rank, suit) for suit in Suit for rank in Rank]
print("Here's our deck fresh from the factory: ", deck)
shuffle(deck)
print("Here it is all shuffled: ", deck)

def best_value(hand: List[Card]) -> int:
"""Get the best blackjack value for a hand of cards (highest without busting)"""
value = sum([card.rank.value for card in hand])
if value < 12 and any([card.rank == Rank.ACE for card in hand]):
# Count one of our aces as 11, adding 10 to the total.
value += 10
return value

print()
print("Deal me two!")
hand = [deck.pop(), deck.pop()]
print(f"My hand is {hand}, which is worth {best_value(hand)}")

while best_value(hand) < 17:
print("Hit me!")
card = deck.pop()
hand.append(card)
print("I got ", card)

if best_value(hand) > 21:
print("Bust!")
print(f"{hand} = {best_value(hand)}")

Run this code, read through it, understand how it works, then take another crack at writing a blackjack program and hopefully it'll be easier this time around. :)

• best_value([10, 10, Ace]) should rather be 21, not 31. – Roland Illig Jan 1 '20 at 15:00
• oops, good catch! fixed – Samwise Jan 1 '20 at 16:57