Blackjack is the most widely played casino gambling game in the world. Blackjack is played against the dealer, not other players, and in this regard is quite different from other games.
First, we are going to model the game’s cards in a file named “card.py”:
class Card:
def __init__(self, name, suit, image, imageBack):
# A standard 52-card deck comprises 13 ranks in each of the four French suits: clubs (♣), diamonds (♦), hearts (♥) and spades (♠).
self.name = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'][name]
# Each card is worth the number value attached to it — 2 is worth 2, 3 is worth 3, 4 is worth 4, etc., and face cards (Jack, Queen, King) are worth 10. Aces can be worth either 1 or 11.
self.value = [11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10][name]
self.suit = '♥♦♣♠'[suit]
self.image = image
self.imageBack = imageBack
Next, let’s create a class that represents a deck of cards. The class is located in a file called deck.py:
import random
from card import Card
import pygame, configparser
class Deck:
def __init__(self):
self.cards = []
We will use the ConfigParser module to manage user-editable configuration files for our application. It provides a structure similar to what’s found in Microsoft Windows INI files. Our config file resources/blackjack.ini is quite simple:
[BASIC] # The file consists of sections, each of which contains keys with values. WIN_WIDTH = 600
WIN_HEIGHT = 500
CARD_HEIGHT = 150
CARD_WIDTH = 100
DEFAULT_X = 30
DEFAULT_OFFSET = 30
DEFAULT_Y_DEALER = 60
self.config = configparser.ConfigParser() # We create an instance of the main configuration parser.
self.config.read("resources/blackjack.ini") # We read the configuration file.
self.CARD_HEIGHT = int(self.config['BASIC']['CARD_HEIGHT']) # We access the CARD_HEIGHT and the CARD_WIDTH values from the BASIC section.
self.CARD_WIDTH = int(self.config['BASIC']['CARD_WIDTH'])
# It generates our deck of cards.
def generate(self):
# A standard 52-card deck comprises 13 ranks in each of the four French suits.
for i in range(13):
for j in range(4):
# The card images are located in a resources folder. Format: suit-name.png, e.g., 1-1.png (A♥), 1-2.png (2♥)....
image = "resources/" + str(i+1) + "-" + str(j+1) + ".png"
self.cards.append(Card(i, j, pygame.transform.scale(pygame.image.load(image), (self.CARD_WIDTH, self.CARD_HEIGHT)), pygame.transform.scale(pygame.image.load("resources/blue_back.png"), (self.CARD_WIDTH, self.CARD_HEIGHT))))
# It draws numCards from the deck.
def draw(self, numCards):
cards = []
for i in range(numCards):
# It randomly selects a card from the deck.
card = random.choice(self.cards)
# And removes it from the deck.
self.cards.remove(card)
cards.append(card)
return cards
def count(self):
return len(self.cards)
Now, we are going to create a class that represents our players.
import configparser
PLAYER = ["Human", "AI", "Dealer"]
class Player:
def __init__(self, typePlayer, deck):
self.cards = []
self.posx = []
self.posy = []
self.typePlayer = PLAYER[typePlayer]
self.deck = deck
self.score = 0
self.busted = False
self.config = configparser.ConfigParser()
self.config.read("resources/blackjack.ini") # We read the configuration file.
self.DEFAULT_OFFSET = int(self.config['BASIC']['DEFAULT_OFFSET'])
self.DEFAULT_Y_DEALER = int(self.config['BASIC']['DEFAULT_Y_DEALER'])
self.WIN_HEIGHT = int(self.config['BASIC']['WIN_HEIGHT'])
self.WIN_WIDTH = int(self.config['BASIC']['WIN_WIDTH'])
self.CARD_HEIGHT = int(self.config['BASIC']['CARD_HEIGHT'])
self.DEFAULT_Y = self.WIN_HEIGHT-self.CARD_HEIGHT-30
self.DEFAULT_X = int(self.config['BASIC']['DEFAULT_X'])
# Each player looks at his cards and has the option to ‘hit’ – which means going for another card.
def hit(self):
# Firstly, we draw a card from the deck.
self.cards.extend(self.deck.draw(1))
# Secondly, we check our score.
self.check_score()
# Thirdly, we position the card in its place (the x-position of the player's previous card plus an offset)
self.posx.append(self.posx[len(self.posx)-1] + self.DEFAULT_OFFSET)
if self.typePlayer == 'Dealer':
self.posy.append(self.DEFAULT_Y_DEALER)
else:
self.posy.append(self.DEFAULT_Y)
# Finally, the aim of the game is for the sum of the cards to be as close to 21 as possible, but never going over, as that means the player will go ‘bust.’
if self.score > 21:
self.busted = True
return True
return False
# At the start of the game, the dealer deals two cards to each player.
def deal(self):
# Firstly, we draw two cards from the deck.
self.cards.extend(self.deck.draw(2))
# Secondly, we position the cards in their places.
if self.typePlayer == "Human":
self.posx.append(self.DEFAULT_X)
self.posx.append(self.posx[0] + self.DEFAULT_OFFSET)
self.posy.append(self.DEFAULT_Y)
self.posy.append(self.DEFAULT_Y)
elif self.typePlayer == "AI":
self.posx.append(self.DEFAULT_X + self.WIN_WIDTH/2)
self.posx.append(self.posx[0] + self.DEFAULT_OFFSET)
self.posy.append(self.DEFAULT_Y)
self.posy.append(self.DEFAULT_Y)
elif self.typePlayer == "Dealer":
self.posx.append(self.DEFAULT_X)
self.posx.append(self.posx[0] + self.DEFAULT_OFFSET)
self.posy.append(self.DEFAULT_Y_DEALER)
self.posy.append(self.DEFAULT_Y_DEALER)
# Thirdly, we check the score.
self.check_score()
# We cannot get busted, but we can get a blackjack.
if self.score == 21:
return True
return False
# Check our score.
def check_score(self):
aces_counter = 0 # Number of aces.
self.score = 0
for card in self.cards:
if card.value == 11:
aces_counter += 1
self.score += card.value
# An Ace will have a value of 11 unless that would give a player, AI, or the dealer a score in excess of 21. If this is the case, then it has a value of 1.
while ace_counter != 0 and self.score > 21: # If we have some aces and get busted...
ace_counter -= 1
self.score -= 10 # let's remove 10 from our score, so instead of counting our ace as 11, we will count it as 1.
return self.score
# Hit or Stand? It is based on the article Learn Blackjack Strategy.
def should_hit(self, cardDealer):
a_counter = 0
for card in self.cards:
if card.value == 11:
a_counter += 1
score = self.check_score()
if score>18:
return False
elif score==18:
if a_counter>0 and cardDealer>8:
return True
else:
return False
elif score==17:
if a_counter>0 and cardDealer>6:
return True
else:
return False
elif score<17 and score>12:
if a_counter==0 and cardDealer>6:
return True
elif a_counter>0:
return True
else:
return False
else:
return True
# Whether you should hit or stand always depends on the dealer’s blackjack hand. This is an auxiliary method for the should_hit() method.
def dealer_card(self):
return self.cards[1].value
# It shows the player's cards.
def show(self, win, endGame=False):
index = 0
for i in self.cards:
# All players' cards are faced-up. One dealer's card is also faced-up and can be seen by players. In other words, if the player is the dealer, it is his first card, and it is not the end of the Game, we will not show the image of his first card.
if index==0 and self.typePlayer=='Dealer' and not endGame:
win.blit(i.imageBack, (self.posx[index],self.posy[index]))
# Otherwise, we will show the card's image.
else:
win.blit(i.image, (self.posx[index],self.posy[index]))
index += 1
Finally, we are going to code the game itself.
import pygame, sys, configparser
from deck import Deck
from player import Player
# Define colors in RGB values
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
class Game: # It represents the game itself
def __init__(self):
self.config = configparser.ConfigParser() # We create an instance of the main configuration parser.
self.config.read("resources/blackjack.ini") # We read the configuration file.
self.WIN_WIDTH = int(self.config['BASIC']['WIN_WIDTH'])
self.WIN_HEIGHT = int(self.config['BASIC']['WIN_HEIGHT'])
self.CARD_HEIGHT = int(self.config['BASIC']['CARD_HEIGHT'])
self.win = pygame.display.set_mode((self.WIN_WIDTH, self.WIN_HEIGHT))
pygame.display.set_caption("BlackJack") # It sets the current window caption.
pygame.init() # It initializes all imported pygame modules
self.bg = pygame.image.load("resources/blackjack.jpg") # It loads a new image from a file.
self.initialize()
# It initializes our game.
def initialize(self):
self.running = True # The game is obvioulsy running.
self.deck = Deck() # It creates an instance of our class Deck.
self.deck.generate() # It generates our deck of cards.
self.player = Player(0, self.deck) # We create a human player, an AI player, and the dealer
self.ai = Player(1, self.deck)
self.dealer = Player(2, self.deck)
self.player.deal() # At the start of the game, the dealer deals two cards to each player.
self.ai.deal()
self.dealer.deal()
self.K_SPACE = self.K_RETURN = False
self.finished = False # The player's turn is not finished, it has not even started.
self.end_hand = False
self.mytext = self.mytext2 = ""
self.updateInterface()
# It updates the interface.
def updateInterface(self):
self.win.blit(self.bg, (0, 0)) # It draws the background.
self.player.show(self.win) # It draws the human player's cards.
self.ai.show(self.win) # It draws the AI player's cards.
if self.end_hand==False: # It draws only one of the dealer's cards (end_hand=False) or all its cards.
self.dealer.show(self.win)
else:
self.dealer.show(self.win, self.end_hand)
GUI_font = pygame.font.SysFont(None, 32) # There are two ways to use fonts in pygame: pygame.font.Font() and pygame.font.SysFont(). pygame.font.SysFont() expects a string with the name of the font.
TEXT_font = pygame.font.SysFont(None, 30)
INSTRUCTIONS_font = pygame.font.SysFont("monospace", 14)
hand_value_text = GUI_font.render('HAND VALUE: '+ str(self.player.check_score()),True, black) # We show the human, AI, and dealer's hands and scores. When all the players have finished, the dealer plays his hand and it shows all its cards and its score.
player_hand_text = GUI_font.render("PLAYER HAND:",True,black)
ai_hand_text = GUI_font.render("AI HAND:",True, black)
ai_value_text = GUI_font.render('AI VALUE: '+ str(self.ai.check_score()),True,black)
dealer_hand_text = GUI_font.render("DEALER HAND:",True,black)
if self.end_hand==False:
dealer_value_text = GUI_font.render('DEALER VALUE:',True,white)
else:
dealer_value_text = GUI_font.render('DEALER VALUE: ' + str(self.dealer.check_score()),True,black)
instructions = INSTRUCTIONS_font.render("Spacebar: Hit. INTRO: Stand. R: New Game.", True, white) # It gives instructions to our game's user.
result_text = TEXT_font.render(self.mytext, True, red) # It is used to show the game's result.
result_text2 = TEXT_font.render(self.mytext2, True, red)
self.win.blit(hand_value_text, (15, self.WIN_HEIGHT-self.CARD_HEIGHT-85))
self.win.blit(player_hand_text, (15, self.WIN_HEIGHT-self.CARD_HEIGHT-60))
self.win.blit(ai_hand_text, (315, self.WIN_HEIGHT-self.CARD_HEIGHT-60))
self.win.blit(ai_value_text, (315, self.WIN_HEIGHT-self.CARD_HEIGHT-85))
self.win.blit(dealer_hand_text, (15, 35))
self.win.blit(dealer_value_text, (15, 10))
self.win.blit(instructions, (2*self.WIN_WIDTH/5, 20))
self.win.blit(result_text, (2*self.WIN_WIDTH/5, 50))
self.win.blit(result_text2, (2*self.WIN_WIDTH/5, 80))
pygame.display.flip() # We were drawing on the surface object. This method updates the full display Surface to the screen.
pygame.time.delay(1000)
def gameLoop(self): # A game loop runs continuously during our game.
while self.running:
pygame.time.delay(1000)
self.check_events() # It checks all the events.
if self.K_SPACE and not self.finished: # If the player's turn is not finished and the player hits the spacebar, he wants to "hit" (go for another card)
if self.player.hit(): # If the player gets busted, self.player.hit() returns True.
self.finished = True
elif self.K_RETURN and not self.finished: # If the player's turn is not finished and the player hits the enter Key, he wants to "stand" (he is happy with his or her cards)
self.finished = True
if self.finished: # The player's turn is finished.
while self.ai.should_hit(self.dealer.dealer_card()) and not self.ai.busted:
self.ai.hit()
self.updateInterface()
self.end_hand = True # It is the dealer's turn.
if self.ai.busted and self.player.busted: # The big advantage the casino has is that players can get bust and lose before the dealer even plays its hand.
self.mytext = "Casino wins."
self.mytext2 = "Everyone else loses."
else:
while self.dealer.check_score()<17 and not self.dealer.busted: # If the players' hands are not busted, then the dealer needs to hit his hand until the value of his hand is 17 or more.
self.dealer.hit()
self.updateInterface()
self.endgame() # We check all the end game's conditions.
self.updateInterface()
self.reset_keys() # We reset the keys as we have already processed the key's associated event.
def check_events(self): # Pygame handles all its event messaging through an event queue. This method checks all these events.
for event in pygame.event.get(): # It gets events from the queue.
if event.type == pygame.QUIT: # The user has clicked the windows's X button.
self.running = False # The game is over.
sys.exit() # We call sys.exit() to shutdown the program and exits the game's loop.
if event.type == pygame.KEYDOWN: # A key is physically pressed on.
if event.key == pygame.K_SPACE: # The key is the space bar.
self.K_SPACE = True
elif event.key == pygame.K_RETURN: # The key is the ENTER key.
self.K_RETURN = True
elif event.key == pygame.K_r: # The key is the "r".
self.initialize()
def reset_keys(self): # Reset all the keys
self.K_SPACE = self.K_RETURN = False
def endgame(self):
"""It checks all the end game's conditions and shows the appropriate messages."""
if self.dealer.busted:
if self.ai.busted:
self.mytext = "Dealer and AI busted!"
self.mytext2 = "Player wins!"
elif self.player.busted:
self.mytext = "Dealer and Player busted!"
self.mytext2 = "AI wins!"
else:
self.mytext = "Dealer busted!"
self.mytext2 = "AI and Player win!"
elif not self.ai.busted and not self.player.busted:
if self.dealer.check_score()>self.ai.check_score():
self.mytext = "Dealer wins. AI loses."
elif self.dealer.check_score()<self.ai.check_score():
self.mytext = "AI wins. Dealer loses."
elif self.dealer.check_score() == self.ai.check_score():
self.mytext = "Tie between AI and dealer."
if self.dealer.check_score()>self.player.check_score():
self.mytext2 = "Dealer wins. Player loses."
elif self.dealer.check_score()<self.player.check_score():
self.mytext2 = "Dealer loses. Player wins."
elif self.dealer.check_score() == self.player.check_score():
self.mytext2 = "Tie between Player and dealer."
elif not self.player.busted:
if self.dealer.check_score()>self.player.check_score():
self.mytext = "Dealer wins."
self.mytext2 = "Player loses. AI busted."
elif self.dealer.check_score()<self.player.check_score():
self.mytext = "Dealer loses."
self.mytext2 = "Player wins. AI busted."
elif self.dealer.check_score() == self.player.check_score():
self.mytext = "Tie between Player and dealer."
self.mytext2 = "AI busted."
else:
if self.dealer.check_score()>self.ai.check_score():
self.mytext = "Dealer wins."
self.mytext2 = "AI loses. Player busted."
elif self.dealer.check_score()<self.ai.check_score():
self.mytext = "AI wins. Dealer loses."
self.mytext2 = "Player busted."
elif self.dealer.check_score() == self.ai.check_score():
self.mytext = "Tie between AI and dealer."
self.mytext2 = "Player busted."
if __name__ == '__main__':
game = Game() # The main function is quite simple. We create an instance of our Game class.
while game.running: # While the state of the game is running
game.gameLoop() # We call the game's gameLoop function.