Top-Down Designing a Game of Blackjack#

In this exercise, you will be writing a program to play the card game Blackjack. This will give you an opportunity to practice how you can take a big problem and break it into smaller part.

How Blackjack works#

If you are unfamiliar with Blackjack, it works as follows:

  • Blackjack is a card game where the objective is to receive cards that total as close or equal to 21 as possible without going over 21.

  • The game is played by two people: a Player and a Dealer.

  • Cards are scored as follows:

    • Numbered cards have the value of their number (3 of hearts is worth three, 7 of clubs is worth 7, etc.).

    • Face cards (Jack, Queen, King) have a value of 10.

    • Aces have a value of the 11, unless that causes the Player to have a score above 21, at which point the ace has a value of 1.

    • The suit of the card has no impact.

  • Initially, both the Player and the Dealer are dealt two cards from the deck. The Player’s cards are dealt face up, while one of the Dealer’s cards is face up and the other is face down.

  • The Player gets to decide whether they would like to get another card from the deck to improve their score with the hope of not going above 21. Taking another card is called a “hit”. Players may take as many additional cards as they like as long as they do not go above 21.

  • Going above 21 is called a “bust” and the Player immediately loses.

  • When the Player decides to not take any additional cards, that is called a “stay”.

  • After the Player has “stayed” (assuming they did not bust), the Dealer reveals their second card. If the value of the Dealer’s hand is less than 17, the Dealer must take additional cards until the total value is 17 or greater. So the Player takes an extra card if their hand is 16 or less, the Dealer takes a card if their hand is 17 or less.

  • If the Dealer busts, or stops with a total less than the Player, the Player wins. The Dealer wins any tie.

Our Version of Blackjack#

For this exercise, we will version of the game with four simplifications:

  1. There are no aces.

  2. If the Player’s cards total 16 or less, they take another card (“hit”); if the Player’s cards total more than 16, they “stay.” In other words, the Player isn’t making any complicated decisions, they use a simple rule just like the Dealer.

  3. Each person’s cards are provided as a list of cards (players_deck and dealers_deck). That way you don’t have to worry about the order cards are distributed. Player starts the game with the first two cards in players_deck, the first time they hit they get the third card in players_deck, etc.

Starter Code#

Below is a block of starter code. Copy-paste the starter code into a file called blackjack.py in VS Code. Then read the content below this starter code for directions on how to finish the code to play a full game of blackjack.

def play_blackjack(players_deck, dealers_deck):
    """Main function that plays a hand of blackjack"""

    # This function takes the decks the Players will use, and
    # should return the string "Player" if the Player has won
    # and the string "Dealer" if the Dealer has won. 
    # (note the capitalization of Player and Dealer.)

    pass


def main():

    # Test Case 0:
    # Both people "hit" once.
    # Player wins with 20 (v. 19)
    print("\nTest Case 0: Both people 'hit' once.")

    players_deck = [10, 5, 5]
    dealers_deck = [7, 2, 10]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Player.")
    print(f"Winner from the function was {result}")

    # Test Case 1:
    # Both people "hit" once.
    # They tie; Dealer wins.
    print("\nTest Case 1: Tie.")

    players_deck = [10, 5, 4]
    dealers_deck = [7, 2, 10]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Dealer.")
    print(f"Winner from the function was {result}")

    # Test Case 2:
    # Both people "hit" once
    # Player "bust"s. Dealer wins.
    print("\nTest Case 2: Player busts.")

    players_deck = [10, 5, 10]
    dealers_deck = [7, 2, 10]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Dealer.")
    print(f"Winner from the function was {result}")

    # Test Case 3:
    # Both people "hit" once.
    # Both people bust, Dealer wins.
    print("\nTest Case 3: Both bust.")

    players_deck = [10, 5, 10]
    dealers_deck = [10, 5, 10]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Dealer.")
    print(f"Winner from the function was {result}")

    # Test Case 4:
    # Player "hits" once, Dealer "stays".
    # Player wins.
    print("\nTest Case 4: Dealer stays.")

    players_deck = [10, 5, 5]
    dealers_deck = [10, 8, 3]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Player.")
    print(f"Winner from the function was {result}")

    # Test Case 5:
    # Same cards (17); Player stays, Dealer hits.
    # Dealer busts, Player wins.
    print("\nTest Case 5: Dealer busts.")

    players_deck = [10, 7, 7]
    dealers_deck = [10, 7, 7]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Player.")
    print(f"Winner from the function was {result}")


    # Test Case 6:
    # Player "hits" twice, Dealer "hits" once.
    # Player wins.
    print("\nTest Case 6: Player hits twice.")
    players_deck = [10, 2, 2, 7]
    dealers_deck = [10, 5, 5, 7]
    result = play_blackjack(players_deck, dealers_deck)
    print("The winner should be Player.")
    print(f"Winner from the function was {result}")


if __name__ == "__main__":
    main()

if __name__ == "__main__"#

Why did we put the main() call under the if statement if __name__ == "__main__":? Basically, this if statement will be true anytime a file is run directly (as you have been doing), but not if someone imports this file into another program. From now on, you should follow the practice of putting the primary function call of your script in this if-statement.

What exactly is __name__ and why does it matter if it is equal to "__main__"? You don’t need to worry about understanding that, but if you’re curious: when a Python file is run, Python automatically creates a variable called __name__. If the file has been called directly, the value of __name__ is set to "__main__". But if a file is imported (like when a file is run in the process of importing a module), __name__ takes on the value of module name.

The Exercise#

The goal of this exercise is to practice top-down design. As you may recall from your Coursera reading, top-down design is the process of taking large, complex pieces, and separating them out into their own function—known as top-down design—is crucial as you write larger and larger programs. While it may seem advantageous to just write everything in one giant function, such an approach not only makes the programming more difficult, but also tends to result in a complex mess that is difficult to test and debug. Whenever you have a chance to pull a well-defined logical piece of your program out into its own function, you should consider this an opportunity, not a burden.

In this case, we will take a full game of blackjack — in which Players are dealt cards, they decide whether to get more cards, and finally a winner is determined — and break it into smaller steps.

The approach we like is “top-down design, bottom-up implementation.” What exactly does this mean? Top-down design means that you start with the whole problem, and come up with a high-level algorithm for it. That algorithm will be in terms of tasks that are too big and complex to just write as single functions, but that is OK. You then repeat the process for those sub-tasks until you get to something you can write. At this point you are at the “bottom” of the design—the smallest pieces that will go into it. You start implementation from there, making (and testing!) small pieces and putting them together. As you assemble these pieces from the bottom up, you get a larger and larger working program until finally you have your entire program.

For example, if I were writing this program, I would start by thinking about the basic parts of a round of blackjack. In blackjack, you can probably think of three main steps:

  1. The first two cards are dealt to each player.

  2. Each person decides whether to hit or stay (and potentially hit multiple times).

  3. The winner is determined.

Given that, I would start to write out the function play_blackjack assuming the functions I need are complete. For example, the first line I might put in play_blackjack would be something like:

def play_blackjack(players_deck, dealers_deck):
    """Main function that plays a hand of blackjack"""

    # Step one: deal out first cards
    players_score = deal_first_two_cards(players_deck)
    pass

Now, I haven’t actually written deal_first_two_cards yet, but that’s ok — I have a sense of what it does! It takes the Player’s deck and calculates the score they start with after getting their first two cards.

To help myself, I can even write the function signature up at the top of my file and add a comment saying what my function should do:

def deal_first_two_cards(deck):
    """
    Takes a person's deck and calculates 
    the person's starting score based
    on first two cards.
    """

    pass

Now, the code that will go into deal_first_two_cards isn’t going to be very complicated, and you may feel like you could write it easily right away. Or you may not even feel like you need a separate function for the task! For this exercise, though, let’s practice top-down design by putting it in its own function. And don’t worry, you’ll find the other functions you need to write definitely warrant a function of their own.

So with that definition written, I’d go back to the play_blackjack and think about what would go next. In this case, for example, I could deal the cards for the Dealer:

def play_blackjack(players_deck, dealers_deck):
    """Main function that plays a hand of blackjack"""

    # Step one: deal out first cards
    players_score = deal_first_two_cards(players_deck)
    dealers_score = deal_first_two_cards(dealers_deck)
    pass

And so on.

OK, now that I’ve gotten you started, go ahead and work on implementing the rest of the program!

Functions to Write#

As mentioned above, there are three primary things that happen in blackjack:

  1. The first two cards are dealt to each player.

  2. Each person decides whether to hit or stay (and potentially hit multiple times).

  3. The winner is determined.

We’ve already discussed writing a function for the first task — deal_first_two_cards. You will probably want to write to more functions — say, hit_or_stay and determine_winner — for the other two activities.

Of these, hit_or_stay will be the most complicated, since a person gets to decide whether to stop (“stay”) or take another card (“hit”). If they hit, you then have to determine if they want to stay or hit again, etc.

Hints#

  • In a game without aces (like our simplified game), it is impossible to hit more than 9 times (take more than 9 extra cards in addition to the two you are originally dealt) without busting.

  • Dealing with the possibility that one of the people will ask for more than one extra card (“hit” more than once) is tricky. You will notice that in the first six test cases, neither player takes more than 1 card. Given that, it might be smart to start by writing something that assumes each player takes zero or one extra card. Then, once you can pass the first six tests, try to generalize your code to allow for the possibility of a player asking for more than one extra card.

  • Be very careful about the use of “greater than” versus “greater than or equal to”! If you get an inequality wrong, your code will pass most tests, but will fail on ones where the cards land right on a decision threshold. So as a reminder:

    • Players hit if their hand is 16 or less.

    • Dealers hit if their hand is 17 or less.

    • You bust if your hand is over 21.

Submitting#

When you feel ready, be sure your file is named blackjack.py and upload it to gradescope.

Done? Want More?#

Here are some extensions you can try!