# Mini-project #6 - Blackjack
try:
import simplegui
except ModuleNotFoundError:
import simplequi as simplegui
import random
# load card sprite - 936x384 - source: jfitz.com
CARD_SIZE = (72, 96)
CARD_CENTER = (36, 48)
card_images = simplegui.load_image(
"http://storage.googleapis.com/codeskulptor-assets/cards_jfitz.png")
CARD_BACK_SIZE = (72, 96)
CARD_BACK_CENTER = (36, 48)
card_back = simplegui.load_image(
"http://storage.googleapis.com/codeskulptor-assets/card_jfitz_back.png")
# initialize some useful global variables
in_play = False
outcome = ""
score = 0
# define globals for cards
SUITS = ('C', 'S', 'H', 'D')
RANKS = ('A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K')
VALUES = {'A': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
'9': 9, 'T': 10, 'J': 10, 'Q': 10, 'K': 10}
# GUI constants
WIDTH = 600
HEIGHT = 600
DEALER_HAND_POS = (50, 150)
PLAYER_HAND_POS = (DEALER_HAND_POS[0], DEALER_HAND_POS[1] + 150)
TITLE_POS = (10, 30)
TITLE_FONT_SIZE = 26
FONT_COLOR = 'yellow'
FONT_FACE = 'monospace'
OUTCOME_POS = (10, 500)
OUTCOME_FONT_SIZE = TITLE_FONT_SIZE
SCORE_POS = (WIDTH - 150, TITLE_POS[1])
SCORE_FONT_SIZE = TITLE_FONT_SIZE
# define card class
class Card:
def __init__(self, suit, rank):
if (suit in SUITS) and (rank in RANKS):
self.suit = suit
self.rank = rank
else:
self.suit = None
self.rank = None
print("Invalid card: ", suit, rank)
def __str__(self):
return self.suit + self.rank
def get_suit(self):
return self.suit
def get_rank(self):
return self.rank
def draw(self, canvas, pos):
card_loc = (CARD_CENTER[0] + CARD_SIZE[0] * RANKS.index(self.rank),
CARD_CENTER[1] + CARD_SIZE[1] * SUITS.index(self.suit))
canvas.draw_image(
card_images, card_loc, CARD_SIZE,
[pos[0] + CARD_CENTER[0], pos[1] + CARD_CENTER[1]], CARD_SIZE)
# define hand class
class Hand:
def __init__(self):
# create Hand object
self.cards = []
def __str__(self):
# return a string representation of a hand
cards_string = 'Hand contains'
for card in self.cards:
cards_string += ' ' + str(card)
return cards_string
def add_card(self, card):
# add a card object to a hand
self.cards.append(card)
def get_value(self):
# count aces as 1, if the hand has an ace, then add 10 to hand value if
# it doesn't bust
value = 0
for card in self.cards:
value += VALUES[card.rank]
# we can add 10 only once
if 'A' in [card.rank for card in self.cards] and not value + 10 > 21:
value += 10
return value
def draw(self, canvas, pos):
# draw a hand on the canvas, use the draw method for cards
for i, card in enumerate(self.cards):
card.draw(canvas, [pos[0] + i * CARD_SIZE[0], pos[1]])
# define deck class
class Deck:
def __init__(self):
# create a Deck object
# NOTE Deck does not consist of strings but Card objects! Do not
# as follows:
# self.cards = [suit+rank for suit in SUITS for rank in RANKS]
# Use the Card class:
self.cards = [Card(suit, rank) for suit in SUITS for rank in RANKS]
def shuffle(self):
# shuffle the deck
random.shuffle(self.cards)
def deal_card(self):
# deal a card object from the deck
return self.cards.pop()
def __str__(self):
# return a string representing the deck
cards_string = 'Deck contains'
for card in self.cards:
cards_string += ' ' + str(card)
return cards_string
# define event handlers for buttons
def deal():
global outcome, in_play, deck, player_hand, dealer_hand, score
deck = Deck()
deck.shuffle()
player_hand = Hand()
player_hand.add_card(deck.deal_card())
player_hand.add_card(deck.deal_card())
dealer_hand = Hand()
dealer_hand.add_card(deck.deal_card())
dealer_hand.add_card(deck.deal_card())
if in_play:
score -= 1
outcome = 'Player lost last round. Hit or stand?'
else:
outcome = 'Hit or stand?'
in_play = True
def hit():
global in_play, score, outcome
if in_play:
# if the hand is in play, hit the player
if player_hand.get_value() <= 21:
player_hand.add_card(deck.deal_card())
# if busted, assign a message to outcome, update in_play and score
if player_hand.get_value() > 21:
in_play = False
score -= 1
outcome = 'You have busted. New deal?'
else:
outcome = 'Cannot hit. New deal?'
# print(f'player: {player_hand} : {player_hand.get_value()}')
# print(f'dealer: {dealer_hand} : {dealer_hand.get_value()}')
def stand():
global outcome, score, in_play
# print('before stand')
# print(f'player: {player_hand} : {player_hand.get_value()}')
# print(f'dealer: {dealer_hand} : {dealer_hand.get_value()}')
if player_hand.get_value() > 21:
outcome = 'Cannot stand, you\'ve busted.'
elif in_play:
# if hand is in play, repeatedly hit dealer until his hand has value 17
# or more
while dealer_hand.get_value() < 17:
dealer_hand.add_card(deck.deal_card())
# assign a message to outcome, update in_play and score
if dealer_hand.get_value() > 21:
outcome = 'Dealer has busted.'
score += 1
elif dealer_hand.get_value() < player_hand.get_value():
outcome = 'Player wins.'
score += 1
else:
outcome = 'Dealer wins.'
score -= 1
else:
outcome = 'Cannot stand.'
outcome += ' New deal?'
in_play = False
# print('after stand')
# print(f'player: {player_hand} : {player_hand.get_value()}')
# print(f'dealer: {dealer_hand} : {dealer_hand.get_value()}')
# draw handler
def draw(canvas: simplegui.Canvas):
canvas.draw_text(
'Blackjack', TITLE_POS, TITLE_FONT_SIZE, FONT_COLOR, FONT_FACE)
dealer_hand.draw(canvas, DEALER_HAND_POS)
player_hand.draw(canvas, PLAYER_HAND_POS)
canvas.draw_text(
outcome, OUTCOME_POS, OUTCOME_FONT_SIZE, FONT_COLOR, FONT_FACE)
if in_play:
# draw over the hole card
canvas.draw_image(
card_back,
CARD_BACK_CENTER,
CARD_BACK_SIZE,
(DEALER_HAND_POS[0] + CARD_BACK_SIZE[0]/2,
DEALER_HAND_POS[1] + CARD_BACK_SIZE[1]/2),
CARD_BACK_SIZE)
# score
canvas.draw_text(
f'Score: {score:2}', SCORE_POS, SCORE_FONT_SIZE, FONT_COLOR, FONT_FACE)
# initialization frame
frame = simplegui.create_frame("Blackjack", WIDTH, HEIGHT)
frame.set_canvas_background("Green")
# create buttons and canvas callback
frame.add_button("Deal", deal, 200)
frame.add_button("Hit", hit, 200)
frame.add_button("Stand", stand, 200)
frame.set_draw_handler(draw)
# get things rolling
deal()
frame.start()