Python Simulation Setup

Below is my progress on the Python blackjack simulation, which can be used as the basis for the rest of the lessons.

Card Class

I decided to use a class to define the cards and deck, since they'd be easier to interact with using the other functions.

class Card:
    def __init__(self, suit, val):
        self.suit = suit
        self.val = val
        if val == 11:
            self.kind = "Ace"
        elif val == 12:
            self.kind = "Jack"
        elif val == 13:
            self.kind = "Queen"
        elif val == 14:
            self.kind = "King"
        else:
            self.kind = str(self.val)

    def show(self):
        return f"{self.kind} of {self.suit}"
    
    def ace_adj(self):
        if self.kind == "Ace":
            self.val = 1

card = Card("Spades", 11)
card.show()
'Ace of Spades'

Deck Class

Here is a class that is able to form itself full of cards and shuffle.

import random

class Deck:
    def __init__(self):
        self.cards = []
        self.build()

    def build(self):
        for suit in ["Spades", "Clubs", "Diamonds", "Hearts"]:
            for val in range(2, 15):
                self.cards.append(Card(suit, val))
    
    def show(self):
        card_disp = [card.show() for card in self.cards]
        return card_disp
    
    def shuffle(self):
        random.shuffle(self.cards)

    def draw_card(self):
        return self.cards.pop()

deck = Deck()
deck.shuffle()

Blackjack Game Procedures

Bringing the deck and the cards together, the code below simulates a game of blackjack.

For simplicity, there is no splitting with pairs or doubling down at the moment. I may add this later.

from IPython.display import clear_output as clr

player_chips = 100

def game_start():
    clr(wait=True)
    global player_hand #initializing hands and deck
    global dealer_hand
    global deck
    global p_bet
    deck = Deck()
    deck.shuffle()
    player_hand = []
    dealer_hand = []
    p_bet = bet(player_chips) #getting the player bet

    print("Initial draws:") #giving the initial draws
    d1 = hit(dealer_hand)
    print(f"The dealer draws: {d1}")
    p1 = hit(player_hand)
    print(f"You receive: {p1}")
    d2 = hit(dealer_hand)
    print("The dealer draws a face-down card...")
    p2 = hit(player_hand)
    print(f"You receive: {p2}")
    if sum(player_hand) == 21: #instant player win on blackjack potentially
        if sum(dealer_hand) != 21:
            print("WOW! A blackjack! You win!")
            win(p_bet)
            play_again()
            return
        else:
            print("Both you and the dealer have blackjack, so it's a bust! Keep your bet.")
            play_again()
            return
    print("--------------------------------")
    print(f"Dealer's hand: {d1}, ???")
    player_turn() #once player turn finishes, the dealer turn occurs

    play_again()

def bet(chips):
    print(f"Current chips: {chips}")
    b = input(f"How much would you like to bet? (Input an integer {str(player_chips)} or less.)")
    try:
        if int(b) <= chips:
            return int(b)
        else:
            print("Invalid bet.")
            bet(chips)
    except:
        print("Invalid bet.")
        bet(chips)

def sum(hand):
    sm = 0
    for card in hand:
        if card.val > 11:
            sm += 10
        else:
            sm += card.val
    if sm > 21:
        for card in hand:
            if card.val == 11:
                card.ace_adj()
                return sum(hand)
    return sm

def hit(hand):
    res = deck.draw_card()
    if (res.val == 11) and (sum(hand) + 11 > 21): #adjusting ace if it would break
        res.ace_adj()
    hand.append(res)
    return res.show()

def hand_display(hand):
    disp_hand = []
    for card in hand:
        disp_hand.append(card.show())
    return ", ".join(disp_hand)

def player_turn():
    print(f"Your hand: {hand_display(player_hand)}")
    if sum(player_hand) > 21:
        print("You break! You lose.")
        lose(p_bet)
        return
    rsp = input("Would you like to hit (h) or stay (s)? (input either option)")
    if rsp == "h":
        received = hit(player_hand)
        print(f"You drew a {received}!")
        player_turn()
    elif rsp == "s":
        print("You stand.")
        dealer_turn()
    else:
        print('Invalid input. Input "h" to hit or "s" to stand.')
        player_turn()

def dealer_turn():
    print(f"Dealer's hand: {hand_display(dealer_hand)}")
    if sum(dealer_hand) > 16:
        print("The dealer stays.")
        pass
    else:
        print(f"The dealer draws: {hit(dealer_hand)}")
        if sum(dealer_hand) > 21:
            print("The dealer breaks! You win.")
            win(p_bet)
            return
        dealer_turn()
        return
    if sum(player_hand) > sum(dealer_hand):
        print(f"Congratulations! You won with a hand worth {sum(player_hand)}!")
        win(p_bet)
    elif sum(dealer_hand) > sum(player_hand):
        print(f"Too bad! You lost to the dealer's hand, worth {sum(dealer_hand)}.")
        lose(p_bet)
    else:
        print("It's a push! You keep your bet.")
    return

def win(bet):
    global player_chips
    player_chips += bet
    return

def lose(bet):
    global player_chips
    player_chips -= bet
    return

def play_again():
    if player_chips != 0:
            pa = input('Would you like to play again? (Input "y" for yes and "n" for no.)')
            if pa.lower() == "y":
                game_start()
            else:
                print(f"You finished with {str(player_chips)} chips!")
                return
    else:
        print("You lost all of your chips! Better luck next time.")
        return

game_start()
Current chips: 100
Initial draws:
The dealer draws: 6 of Diamonds
You receive: 5 of Spades
The dealer draws a face-down card...
You receive: Jack of Spades
--------------------------------
Dealer's hand: 6 of Diamonds, ???
Your hand: 5 of Spades, Jack of Spades
You drew a 5 of Clubs!
Your hand: 5 of Spades, Jack of Spades, 5 of Clubs
You stand.
Dealer's hand: 6 of Diamonds, 4 of Clubs
The dealer draws: 9 of Clubs
Dealer's hand: 6 of Diamonds, 4 of Clubs, 9 of Clubs
The dealer stays.
Congratulations! You won with a hand worth 20!
You finished with 107 chips!

Tentative Lesson Plan

My lesson will mainly focus on these topics:

  • Lists (Review)
  • Iteration (Review)
  • Random Values
  • Simulations

The lesson will be structured as follows.

Introduction: Simulations

Simulations are models of real-world phenomena or systems that use mathematical algorithms and computer programs simulate the real behavior and aspects of the subject being modeled.

Simulations are most often used to model complex or time-consuming things that would be difficult to test in real life, such as modeling the spread of diseases in certain ecosystems or testing the functionality of a potential product before it is made.

In this lesson, we will be looking at lists, iteration, and random values through the lens of simulations.

Review: Lists and Iteration

In this section, I go over lists to set up for iteration.

Code plans:

# Unique concept: adding things to the list

# Going over lists using the deck list
# Unique concept: shuffling the list
# Unique concept: getting rid of things with pop

Recursive Loops

Recursive loops are used throughout the Python blackjack model. This is seen with the interaction between the game_start() function and the play_again() function.

def game_start():
    #after the game setup and outcome...
    play_again()

def play_again():
    if player_chips != 0:
            pa = input('Would you like to play again? (Input "y" for yes and "n" for no.)')
            if pa.lower() == "y":
                game_start() #HERE
            else:
                print(f"You finished with {str(player_chips)} chips!")
                return
    else:
        print("You lost all of your chips! Better luck next time.")
        return

For Loops

For loops are used quite often throughout the simulation and are helpful to easily be able to repeat a set of conditions to everything inside of a list/collection of data or to repeat a process a certain number of times.

An example of both of these things are very conveniently seen in the Deck class function build(). It also shows loops being nested.

def build(self):
        for suit in ["Spades", "Clubs", "Diamonds", "Hearts"]:
            for val in range(2, 15):
                self.cards.append(Card(suit, val))

Going piece by piece:

for suit in ["Spades", "Clubs", "Diamonds", "Hearts"]:

Whatever is mentioned below this will be repeated four times, once for each of the listed card suits.

for val in range(2, 15)

Here, the for loop is essentially doing the same thing as before, this time with a list of numbers starting with 2 and ending with 14. As a result, the function runs 13 times, each time with a new integer value that is assigned to the value of a card of a certain suit.

While Loops

While loops aren't used in the program, but they offer a different way to repeat a set of instructions in a program. The procedure below the while [condition] line will occur until the condition is made not true.

**Student Interation**: How could this `build` function be altered to function with a while loop within it?

def build(self):
        for suit in ["Spades", "Clubs", "Diamonds", "Hearts"]:
            for val in range(2, 15): #HINT: try replacing this function
                self.cards.append(Card(suit, val))

#HINT: you may want to make a variable i to be the numbers in the range
# you could increment it each loop

While loops also alter an alternative way to loop a set of instructions forever, until a precise thing occurs to break the loop. See the code below.

i = 0

while True:
    i += 1
    ch = random.randint(1, 11)
    if ch == 10:
        print(f"It took {str(i)} random generations to get 10.")
        break
It took 35 random generations to get 10.

Speaking of random generations...

Random Values

Because unpredictable randomness occurs in the real world, it's important to have a way to represent it. Simulations are able to use randomization, which could be in the form of random number generation or other methods like shuffle.

Card decks are a great example of how random values can be used to represent real-world scenarios. In the card simulation, the random module's shuffle function is used to quite literally shuffle the deck, seen below.

def shuffle(self):
    random.shuffle(self.cards)

Often, random selection methods use functions like randint or randrange as ways to select certain indexes in lists, or might use the random numbers in some other way.

Without shuffling the card order of the deck, can you think of a way that the aforementioned random module functions could be used to get a random card from the deck?

import random
s_deck = Deck().show() #the deck shuffle is a separate function, so this is unsorted
rando = random.randint(0, len(s_deck)) #generating a random number to use as index
randsel = s_deck[rando] #selecting a random card with the index
s_deck.pop(rando) #getting rid of the selected card from the deck
print(randsel)
10 of Diamonds

Possible Student Assignment

I might make an assignment here for the student to make a simple simulation that is at least mostly accurate to its real-world scenario. It must use:

  • A random value
  • At least one list or similar data type (dictionary, set, etc.)
  • Two different types of iteration