I upgraded a terminal Blackjack game in python

Hello Campers!

After watching one of fCC’s python tutorials on youtube (This one) I did the follow along project for the terminal based blackjack game, and while the game was fun and the project good for showing off what the course taught, I felt like there could be some improvements made to the game itself that would make it far more enjoyable to actually play.

So after a few hours of tinkering that let my stretch my baby dev legs a bit more, I came up with this:

import random
import os

rank_value = {"A" : 11, "2" : 2, "3" : 3, "4" : 4, "5" : 5, "6" : 6, "7" : 7, "8" : 8, "9" : 9, "10" : 10, "J" : 10, "Q" : 10, "K" : 10}


class Card:
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank

    def front_face(self) -> list:
        if self.rank != "10":
            front_face = [
                f" _____ ",
                f"|{self.rank}    |",
                f"|  {self.suit}  |",
                f"|    {self.rank}|",
                f" ‾‾‾‾‾ "
                ]
        else:
            front_face = [
                f" _____ ",
                f"|{self.rank}   |",
                f"|  {self.suit}  |",
                f"|   {self.rank}|",
                f" ‾‾‾‾‾ "
                ]

        return front_face
    
    def back_face(self) -> list:
        back_face = [
        " _____ ",
        "|  p  |",
        "| pod |",
        "|  d  |",
        " ‾‾‾‾‾ "
        ]

        return back_face

    def __str__(self):
        return f"{self.rank} of {self.suit}"

class Deck:
    def __init__(self):
    
        self.cards = []
        suits = ["♠", "♥", "♣", "♦"]
        ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
        
        for suit in suits:
            for rank in ranks:
                self.cards.append(Card(suit, rank))

    def shuffle(self):
        if len(self.cards) > 1:
            random.shuffle(self.cards)

    def deal(self, number: int):
        cards_delt = []
        for i in range(number):
            if len(self.cards) > 0:
                cards_delt.append(self.cards.pop())
        return cards_delt

class Hand:
    def __init__(self, dealer = False):
        self.cards = []
        self.value = 0
        self.dealer = dealer

    def add_card(self, card_list: list):
        self.cards.extend(card_list)

    def calculate_value(self):
        self.value = 0
        ace_count = 0

        for card in self.cards:
            card_value = int(rank_value[card.rank])
            self.value += card_value
            if card.rank == "A":
                ace_count += 1

        while ace_count > 0:
            if self.value > 21:
                self.value -= 10
            ace_count -= 1

    def get_value(self):
        self.calculate_value()
        return self.value
    
    def is_blackjack(self):
        return self.get_value() == 21
    
    def display(self, show_facedown_cards = False):
        hand_matrix = []
        hand_string = ""
        print(f'''{"Dealer's" if self.dealer else "Your"} hand:''')
        for index, card in enumerate(self.cards):
            if self.dealer and index == 0 and not show_facedown_cards and not self.is_blackjack():
                hand_matrix.append(card.back_face())
            else:
                hand_matrix.append(card.front_face())

        for i in range(5):
            for e in range(len(hand_matrix)):
                hand_string += hand_matrix[e][i]
            hand_string += "\n"

        print(hand_string)

        if not self.dealer:
            print("Value:", self.get_value())

        print()

class Game:

    def __init__(self):
        self.wins = 0
        self.losses = 0
        self.ties = 0
        self.games = 0
        self.score = 0
        self.high_score = 0
        self.low_score = 0
        self.play_response = ""

    def start_screen(self):
        print("-" * 50)
        print(" " * 11 + "Welcome to BlackJack.py!")
        print("-" * 50)
        print()
        self.play_response = input("Would you like to be dealt in? [Y/N]: ").lower()

        while self.play_response not in ["yes", "y", "no", "n"]:
            print("Invalid input!\n")
            self.play_response = input("Would you like to be dealt in? [Please enter 'Yes', 'Y', 'No', or 'N']: ").lower()

        if self.play_response in ["yes", "y"]:
            clear_terminal()
            self.play()
        else:
            clear_terminal()
            print("Good bye!")

    def display_header(self):
        print("-" * 50)
        print(f"Game: {self.games}  Wins: {self.wins}  Losses: {self.losses}  Ties: {self.ties}  Score: {self.score}")
        print("-" * 50)

    def display_board(self, dealer_hand: Hand, player_hand: Hand):
        dealer_hand.display()
        print("-" * 50 + "\n")
        player_hand.display()

    def display_dealer_board(self, dealer_hand: Hand, player_hand: Hand):
        dealer_hand.display(show_facedown_cards=True)
        print(f"Dealer's Value: {dealer_hand.get_value()}")
        print("-" * 50 + "\n")
        player_hand.display()


    def play(self):    

        while self.play_response in ["y", "yes"]:
            
            self.games += 1

            deck = Deck()
            deck.shuffle()

            player_hand = Hand()
            dealer_hand = Hand(dealer=True)

            for i in range(2):
                player_hand.add_card(deck.deal(1))
                dealer_hand.add_card(deck.deal(1))

            clear_terminal()
            self.display_header()

            self.display_board(dealer_hand, player_hand)
            
            choice = ""
            while player_hand.get_value() < 21 and choice not in ["s", "stand"]:
                choice = input("'Hit' or 'Stand'?: ").lower()
                print()
                while choice not in ["h", "s", "hit", "stand"]:
                    print("Invalid input!")
                    choice = input("Please enter 'Hit' or 'H' OR 'Stand' or 'S' as valid input: ").lower()
                    print()
                if choice in ["hit", "h"]:
                    player_hand.add_card(deck.deal(1))
                    #player_hand.display()
                    clear_terminal()
                    self.display_header()
                    self.display_board(dealer_hand, player_hand)

            dealer_hand_value = dealer_hand.get_value()

            if player_hand.get_value() <= 21 and player_hand.get_value() > dealer_hand_value:
                while dealer_hand_value < 17:
                    dealer_hand.add_card(deck.deal(1))
                    dealer_hand_value = dealer_hand.get_value()

            
            clear_terminal()
            self.display_header()
            self.display_dealer_board(dealer_hand, player_hand)
            

            self.check_winner(player_hand, dealer_hand)
            
            if self.score > self.high_score:
                self.high_score = self.score

            if self.score < self.low_score:
                self.low_score = self.score

            self.play_response = input("Would you like to play another game? [Y/N]: ").lower()
            while self.play_response not in ["yes", "y", "no", "n"]:
                print("Invalid input!\n")
                self.play_response = input("Would you like to play another game? [Please enter 'Yes', 'Y', 'No', or 'N']: ").lower()

        clear_terminal()
        print("Thanks for playing!\n")
        print(f"Overall:\nGames: {self.games}\nWins: {self.wins}\nLosses: {self.losses}\nTies: {self.ties}\n")
        print(f"End score: {self.score}\nLargest Stack: {self.high_score}\nDeepest in the hole: {self.low_score}\n")

    def check_winner(self, player_hand: Hand, dealer_hand: Hand):
            if player_hand.is_blackjack() and dealer_hand.is_blackjack():
                print("Dealer and Player both Blackjack! It's a tie.")
                self.ties += 1
            elif player_hand.is_blackjack():
                print("You got blackjack! You win!")
                self.wins += 1
                self.score += 3
            elif dealer_hand.is_blackjack():
                print("Dealer has blackjack! Dealer wins!")
                self.losses += 1
                self.score -= 3
            elif player_hand.get_value() > 21:
                print("You busted. Dealer wins!")
                self.losses += 1
                self.score -= 2
            elif dealer_hand.get_value() > 21:
                print("Dealer busted! You win!")
                self.wins += 1
                self.score += 2
            elif player_hand.get_value() > dealer_hand.get_value():
                self.wins += 1
                self.score += 1
                print("You win!")
            elif player_hand.get_value() == dealer_hand.get_value():
                self.ties += 1
                print("It's a tie.")
            else:
                self.losses += 1
                self.score -= 1
                print("Dealer wins!")
    
def clear_terminal():
    os.system('cls' if os.name == 'nt' else 'clear')

if __name__ == "__main__":
    game = Game()
    game.start_screen()

Here are some screenshots of the game in action:

The end screen:

I’m just patting myself on the back here for a pretty basic achievement but I am open to any feedback on the changes I made!

Thank you for taking the time to read my post and look over my code.

I like seeing projects where someone keeps building after finishing the original tutorial. That’s usually where the most learning happens.

The updated card display and the game stats make it feel much more polished. It would be interesting to see how this project evolves if you keep adding small features over time. Revisiting older projects is something I think more people should do because it’s an easy way to notice how much you’ve improved.