If I could tell my past self (a never-coder) how to actually develop software instead of just writing code, what would I say? I aim to provide some helpful tips for those just starting out. Let's use the example of a simple Blackjack game, a typical beginner project.
The Typical Beginner Experience
When I look back at my first experiences writing code, I hated it. I took a BASIC course in high school and loved learning about code, but when it came to writing it, I struggled. I'd try to write the entire program. Once done, I would wince and click "run." An error or some odd behavior would always follow. Then I'd go back through the whole thing to figure out where in my code the error or bug could have possibly occurred - a frustrating process - and I was done. This sucked. I failed the programming course. I declared myself a non-coder, never-ever, not-gonna-be-one, and eventually went into IT as a web admin. In the end I did get back into coding and eventually migrated into software engineering, ironically.
This is a very common experience for anyone learning to "code". As an industry, we place a lot of emphasis on teaching people to code, but we hardly ever teach them how to develop software. It would be like explaining to someone wanting to publish a book that they need to "learn how to words" instead of "learn how to write." Let's now move away from learning to write code and start talking about how to actually develop software.
What Does "To Develop" Mean?
It's important we define the term "To Develop". Here is a definition in the context of software engineering, (from chatGPT).
"to develop" in the context of software refers to the process of creating, improving, and maintaining software applications or systems.
The key terms to highlight here are to create, and to improve. Note that the words to code, and to write are nowhere to be found.
When we develop software, we want to first create a program, and then following some process, improve the program.
Our Program's Requirements
I'm going to use a very common coding problem that beginners are asked to complete: create a simple Blackjack game. This will be a bare-bones version with one player, where the program acts as the dealer and there is no betting involved. Here are the phases and rules.
-
Initial Setup
- Introduce the Game: Remind the player of the rules.
- Deck Preparation: Use a standard deck of 52 cards.
- Shuffle the Deck: Shuffle the deck to randomize the order of cards.
-
Initial Deal
-
Deal Cards:
- The player and the dealer each receive two cards.
- The player's cards are dealt face up.
- The dealer has one card face up (the "upcard") and one card face down (the "hole card").
-
Deal Cards:
-
Player Actions
-
Blackjack Check: Immediately check for Blackjack (an Ace and a 10-value card).
- If the player has Blackjack and the dealer does not, the player wins instantly.
- If both have Blackjack, it's a push (tie).
- If the dealer has Blackjack and the player does not, the dealer wins.
-
Player Decisions: The player takes their turn with the following options:
- Hit: Request an additional card to try to get as close to 21 as possible without going over.
- Stand: Keep the current hand and end the turn.
- Double Down: Receive one more card and then stand.
- Split: If the player's first two cards have the same value, they can split them into two separate hands and play each hand separately.
- Surrender: Forfeit the hand and end the turn (optional and depends on house rules).
-
Blackjack Check: Immediately check for Blackjack (an Ace and a 10-value card).
-
Dealer Actions
- Reveal Hole Card: The dealer reveals the face-down card.
-
Dealer Plays: The dealer plays according to fixed rules:
- Hits until reaching a hand value of 17 or more.
-
Resolution
- Determine Winner: Compare the player’s hand value to the dealer’s hand value.
- Bust: Any hand exceeding 21 automatically loses.
- Player Wins: The player's hand value is higher than the dealer’s without busting.
- Dealer Wins: The dealer’s hand value is higher than the player's without busting.
- Push: Both hands have equal value, resulting in a tie.
-
End of Round
- Collect Cards: Collect all cards and reshuffle if necessary.
- Continue: Decide whether to play another round.
What Does Development Look Like?
Looking at the first term in our definition of "to develop," we have to create. Currently, I have absolutely nothing. So, I'm going to create the file blackjack.py, add a single print statement, and run it. (I'm using Python in these examples, but any language will work.)
print("Welcome to Blackjack)
Running it already you ask? Yes. This leads into my first tip of developing software.
Tip 1 - Anytime you make a change, run your program
Anytime you make a change—and I mean every time you add a statement, a loop, a conditional, etc.—run your program. See how it reacts and ensure it is doing what you expect it to do. Did you notice the missing quote? Your brain will fill in missing bits, but the computer won't add anything for you.
$ python blackjack.py
File "blackjack.py", line 1
print("Welcome to Blackjack)
^
SyntaxError: EOL while scanning string literal
This is a prime example of the computer doing what you (or someone else) told it to do, not what you wanted it to do. The computer will always do what it's told, and someone wrote the code that says, "If this string doesn't end in a quote, crash with a syntax error."
The more code you add before you run the program, the more your brain will correct all the little mistakes, bugs, etc. If you're in "the zone" for an hour and then run your program, it will be a mess to retrace your steps. It's very hard to spot these little mistakes if you don't catch them early.
As you gain more experience, you will know about how many lines you can write before you need to run your program again, but when you're starting out, run your program very, very frequently.
For all the experienced developers reading this and screaming "TESTING," testing is just more code. When you code your test, run that often too, and make sure it's doing what it's supposed to do!
After fixing the mistake and running the code again, it works.
print("Welcome to Blackjack")
$ python blackjack.py
Welcome to Blackjack
At this point, we have completed one full cycle of software development. We created something and, by following a process, improved it. That's pretty much it. In the next tips, we're going to look at how to develop your thoughts inside a program by breaking a large problem into smaller problems and, hopefully keep your sanity along the way.
Tip 2 - Use Comments as Your Scaffolding
When we look at the churches and cathedrals built in medieval Europe, we're awed by their beauty and engineering feats. Arches seemingly float in the air, but how did they do it? Scaffolding.
What we don't see many years later is all the wooden scaffolding and supports that were holding up the building during the construction phase. In the same way they used wood to fill in the gaps in the cathedral, use comments as scaffolding to fill in the gaps of your program.
I'm simply going to paste all the rules of the game into my file, and yes, run the program (what if I forgot to comment a line? Error).
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
# - Deck Preparation: Use a standard deck of 52 cards.
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
# 2. Initial Deal
# - Deal Cards:
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# - The dealer has one card face up (the "upcard") and one card face down (the "hole card").
# 3. Player Actions
# - Blackjack Check: Immediately check for Blackjack (an Ace and a 10-value card).
# - If the player has Blackjack and the dealer does not, the player wins instantly.
# - If both have Blackjack, it's a push (tie).
# - If the dealer has Blackjack and the player does not, the dealer wins.
# - Player Decisions: The player takes their turn with the following options:
# - Hit: Request an additional card to try to get as close to 21 as possible without going over.
# - Stand: Keep the current hand and end the turn.
# - Double Down: Receive one more card and then stand.
# - Split: If the player's first two cards have the same value, they can split them into two separate hands and play each hand separately.
# - Surrender: Forfeit the hand and end the turn (optional and depends on house rules).
# 4. Dealer Actions
# - Reveal Hole Card: The dealer reveals the face-down card.
# - Dealer Plays: The dealer plays according to fixed rules:
# - Hits until reaching a hand value of 17 or more.
# 5. Resolution
# - Determine Winner: Compare the player’s hand value to the dealer’s hand value.
# - Bust: Any hand exceeding 21 automatically loses.
# - Player Wins: The player's hand value is higher than the dealer’s without busting.
# - Dealer Wins: The dealer’s hand value is higher than the player's without busting.
# - Push: Both hands have equal value, resulting in a tie.
# 6. End of Round
# - Collect Cards: Collect all cards and reshuffle if necessary.
# - Continue: Decide whether to play another round.
By doing this, we do no harm to our existing program. But as humans, we're left with something much less intimidating than a blank slate.
We also get some free "pseudocode," or fake code, which reads like English and describes what the code would do if it were written here. Those sentences starting with "if" sort of read like code, right? That leads us to the next tip.
Tip 3 - Use Pseudocode
For any problem you find complex and need to just get your thoughts out, you can write pseudocode as comments. For example, there's a lot that goes into # - Deck Preparation: Use a standard deck of 52 cards.
If this were written as pseudocode, it might look something like this.
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
# - Deck Preparation: Use a standard deck of 52 cards.
# create empty deck
# define suits and ranks
# for each suit:
# for each rank:
# create new card with rank and suit
# add card to deck
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
# 2. Initial Deal
# - Deal Cards:
# ...
Note that I didn't write anything that is syntactically correct. I'm not thinking about syntax or worrying about colons, parentheses, etc. I'm just trying to think through the problem and get it "on paper." And yes, I ran my program again.
Tip 4 - Weave your Code into Your Pseudocode
Now that I have my thoughts down for how I want to create the deck, let's write the code for it.
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
# - Deck Preparation: Use a standard deck of 52 cards.
# create empty deck
deck = []
# define suits and ranks
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
# for each suit:
for suit in suits:
# for each rank:
for rank in ranks:
# create new card with rank and suit
card = {"suit": suits, "rank": rank}
deck.append(card)
# add card to deck
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
# 2. Initial Deal
# - Deal Cards:
# ...
And you guessed it. Run your program.
$ python blackjack.py
Welcome to Blackjack
Good nothing broke, but did we get the deck we want?
Tip 5 - Always be Printing
I want to see that the I'm getting the deck I want before I add anything new. Remember by running your program you ensure it didn't break, and it's doing what you expect it to do. I'm expecting a deck of 52 cards. Let's add a print statement and see...
# create empty deck
deck = []
# define suits and ranks
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
# for each suit:
for suit in suits:
# for each rank:
for rank in ranks:
# create new card with rank and suit
card = {"suit": suits, "rank": rank}
deck.append(card)
# add card to deck
print(deck)
$ python .\blackjack.py
Welcome to Blackjack
[{'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '3'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '4'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '5'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '6'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '7'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '8'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '9'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '10'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Jack'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Queen'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'King'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Ace'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '3'}, {'suit': ['Hearts',
'Diamonds', 'Clubs', 'Spades'], 'rank': '4'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '5'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '6'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '7'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '8'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '9'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '10'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Jack'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Queen'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'King'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Ace'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '3'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '4'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '5'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '6'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '7'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '8'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '9'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '10'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Jack'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Queen'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'King'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Ace'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '3'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '4'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '5'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '6'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '7'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '8'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '9'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '10'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Jack'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'],
'rank': 'Queen'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'King'}, {'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': 'Ace'}]
Because I'm running my program often and printing key variables, I can catch bugs early. I know I just need to review the few lines since my last change.
Here's the problem: card = {"suit": suits, "rank": rank}
should be card = {"suit": suit, "rank": rank}
. I want just a single suit, not the whole suits list.
I made a change, and I'm going to run the program again.
$ python blackjack.py
Welcome to Blackjack
[{'suit': 'Hearts', 'rank': '2'}, {'suit': 'Hearts', 'rank': '3'}, {'suit': 'Hearts', 'rank': '4'}, {'suit': 'Hearts', 'rank': '5'}, {'suit': 'Hearts', 'rank': '6'}, {'suit': 'Hearts', 'rank': '7'}, {'suit': 'Hearts', 'rank': '8'}, {'suit': 'Hearts', 'rank': '9'}, {'suit': 'Hearts', 'rank': '10'}, {'suit': 'Hearts', 'rank': 'Jack'}, {'suit': 'Hearts', 'rank': 'Queen'}, {'suit': 'Hearts', 'rank': 'King'}, {'suit': 'Hearts', 'rank': 'Ace'}, {'suit': 'Diamonds', 'rank': '2'}, {'suit': 'Diamonds', 'rank': '3'}, {'suit': 'Diamonds', 'rank': '4'}, {'suit': 'Diamonds', 'rank': '5'}, {'suit': 'Diamonds', 'rank': '6'}, {'suit': 'Diamonds', 'rank': '7'}, {'suit': 'Diamonds', 'rank': '8'}, {'suit': 'Diamonds', 'rank': '9'}, {'suit': 'Diamonds', 'rank': '10'}, {'suit': 'Diamonds', 'rank': 'Jack'}, {'suit': 'Diamonds', 'rank': 'Queen'}, {'suit': 'Diamonds', 'rank': 'King'}, {'suit': 'Diamonds', 'rank': 'Ace'}, {'suit': 'Clubs', 'rank': '2'}, {'suit': 'Clubs', 'rank': '3'}, {'suit': 'Clubs', 'rank': '4'}, {'suit': 'Clubs', 'rank': '5'}, {'suit': 'Clubs', 'rank': '6'}, {'suit': 'Clubs', 'rank': '7'}, {'suit': 'Clubs', 'rank': '8'}, {'suit': 'Clubs', 'rank': '9'}, {'suit': 'Clubs', 'rank': '10'}, {'suit': 'Clubs', 'rank': 'Jack'}, {'suit': 'Clubs', 'rank': 'Queen'}, {'suit': 'Clubs', 'rank': 'King'}, {'suit': 'Clubs', 'rank': 'Ace'}, {'suit': 'Spades', 'rank': '2'}, {'suit': 'Spades', 'rank': '3'}, {'suit': 'Spades', 'rank': '4'}, {'suit': 'Spades', 'rank': '5'}, {'suit': 'Spades', 'rank': '6'}, {'suit': 'Spades', 'rank': '7'}, {'suit': 'Spades', 'rank': '8'}, {'suit': 'Spades', 'rank': '9'}, {'suit': 'Spades', 'rank': '10'}, {'suit': 'Spades', 'rank': 'Jack'}, {'suit': 'Spades', 'rank': 'Queen'}, {'suit': 'Spades', 'rank': 'King'}, {'suit': 'Spades', 'rank': 'Ace'}]
"Always Be Printing," also known as the "Poor Man's Debugger," is a great way to debug your code. If you don't know what's going on, just keep printing variables until you see something off. I've been doing this for 10+ years, and I still like using print statements for tracking down bugs.
For example, if I'm really hunting for a bug, I'll put multiple print statements everywhere. I'll even add in break
statements so I only see one element in a list instead of the whole list.
# for each suit:
for suit in suits:
print(suit)
# for each rank:
for rank in ranks:
print(rank)
# create new card with rank and suit
card = {"suit": suits, "rank": rank}
print(card)
break
deck.append(card)
# add card to deck
break
print(deck)
And when I run it, I it's easier to see what's going on. My inner thoughts might be going "ok when I print out the suit that's right... I print out the rank, that's right.... but when I print out the card, it's showing all the suits as the card's suit. So it must be when I construct the card, ah there it is!"
$ python blackjack.py
Welcome to Blackjack
Hearts
2
{'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}
[{'suit': ['Hearts', 'Diamonds', 'Clubs', 'Spades'], 'rank': '2'}]
Keep your print statements around as long as you need them.
Tip 6 - Bottle Up Working Code into a Function
When you have something working, encapsulate it into a function and clean it up if needed. Keeping yourself organized is key, and the best way to do that is to encapsulate working code into a function. You can then move that function around, and you'll know what it does. Here's my function.
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
# - Deck Preparation: Use a standard deck of 52 cards.
def prepare_deck():
deck = []
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
for suit in suits:
for rank in ranks:
card = {"suit": suit, "rank": rank}
deck.append(card)
return deck
deck = prepare_deck()
print(deck)
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
#...
Notice I used the wording from the comment above the function. - Deck Preparation
becomes prepare_deck()
. I cleaned up my scaffolding comments within the function, replaced the last print statement with a return
statement, and called the function right below where I declared it. I ran the program to ensure it did the exact same thing as before I made the function.
If you're writing tests for your code, this is a great place to write a test for this function. Typically, absolute beginners are not working with tests yet, so I'll skip it in this guide. But as you learn about testing, every time you create a function like this, create the test for it.
Tip 7 - Translate English Parts-of-Speech into their Code Equivalents
Let's look at the whole file again.
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
# - Deck Preparation: Use a standard deck of 52 cards.
def prepare_deck():
deck = []
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
for suit in suits:
for rank in ranks:
card = {"suit": suit, "rank": rank}
deck.append(card)
return deck
deck = prepare_deck()
print(deck)
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
# 2. Initial Deal
# - Deal Cards:
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# - The dealer has one card face up (the "upcard") and one card face down (the "hole card").
# 3. Player Actions
# - Blackjack Check: Immediately check for Blackjack (an Ace and a 10-value card).
# - If the player has Blackjack and the dealer does not, the player wins instantly.
# - If both have Blackjack, it's a push (tie).
# - If the dealer has Blackjack and the player does not, the dealer wins.
# - Player Decisions: The player takes their turn with the following options:
# - Hit: Request an additional card to try to get as close to 21 as possible without going over.
# - Stand: Keep the current hand and end the turn.
# - Double Down: Receive one more card and then stand.
# - Split: If the player's first two cards have the same value, they can split them into two separate hands and play each hand separately.
# - Surrender: Forfeit the hand and end the turn (optional and depends on house rules).
# 4. Dealer Actions
# - Reveal Hole Card: The dealer reveals the face-down card.
# - Dealer Plays: The dealer plays according to fixed rules:
# - Hits until reaching a hand value of 17 or more.
# 5. Resolution
# - Determine Winner: Compare the player’s hand value to the dealer’s hand value.
# - Bust: Any hand exceeding 21 automatically loses.
# - Player Wins: The player's hand value is higher than the dealer’s without busting.
# - Dealer Wins: The dealer’s hand value is higher than the player's without busting.
# - Push: Both hands have equal value, resulting in a tie.
# 6. End of Round
# - Collect Cards: Collect all cards and reshuffle if necessary.
# - Continue: Decide whether to play another round.
We can perform our process of writing pseudocode, weaving in code, and creating a function in reverse. Using the following set of rules, let's see what we can do.
What if we looked for all the verbs or actions in the comments and just stubbed out a function for each?
print("Welcome to Blackjack")
# 1. Initial Setup
# - Introduce the Game: Remind the player of the rules.
def introduce_game():
pass
# - Deck Preparation: Use a standard deck of 52 cards.
def prepare_deck():
deck = []
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
for suit in suits:
for rank in ranks:
card = {"suit": suit, "rank": rank}
deck.append(card)
return deck
deck = prepare_deck()
print(deck)
# - Shuffle the Deck: Shuffle the deck to randomize the order of cards.
def shuffle_deck():
pass
# 2. Initial Deal
# - Deal Cards:
def deal_cards():
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# - The dealer has one card face up (the "upcard") and one card face down (the "hole card").
pass
# 3. Player Actions
# - Blackjack Check: Immediately check for Blackjack (an Ace and a 10-value card).
def check_for_blackjack():
# - If the player has Blackjack and the dealer does not, the player wins instantly.
# - If both have Blackjack, it's a push (tie).
# - If the dealer has Blackjack and the player does not, the dealer wins.
pass
# - Player Decisions: The player takes their turn with the following options:
# - Hit: Request an additional card to try to get as close to 21 as possible without going over.
def player_hit():
pass
# - Stand: Keep the current hand and end the turn.
def player_stand():
pass
# - Double Down: Receive one more card and then stand.
def player_double_down():
pass
# - Split: If the player's first two cards have the same value, they can split them into two separate hands and play each hand separately.
def player_split():
pass
# - Surrender: Forfeit the hand and end the turn (optional and depends on house rules).
def player_surrender():
pass
# 4. Dealer Actions
# - Reveal Hole Card: The dealer reveals the face-down card.
def dealer_reveal_hole_card():
pass
# - Dealer Plays: The dealer plays according to fixed rules:
def dealer_reveal_hole_card():
# - Hits until reaching a hand value of 17 or more.
pass
# 5. Resolution
# - Determine Winner: Compare the player’s hand value to the dealer’s hand value.
def determine_winner():
pass
# - Bust: Any hand exceeding 21 automatically loses.
# - Player Wins: The player's hand value is higher than the dealer’s without busting.
# - Dealer Wins: The dealer’s hand value is higher than the player's without busting.
# - Push: Both hands have equal value, resulting in a tie.
# 6. End of Round
# - Collect Cards: Collect all cards and reshuffle if necessary.
# - Continue: Decide whether to play another round.
Now, instead of writing the whole program as one problem to solve, we've broken it down into a bunch of small functions, which are mini-programs in their own right. I've added the stubs and run the program with no issues.
Tip 8 - Reorganize When Your Code Becomes Difficult to Read
The above provided us with a bunch of function stubs, but it's hard to see the actual flow of the game anymore. We can stop here and reorganize our program to make it a little bit easier to read. I'll do that now.
#
# 1. Initial Setup
#
def introduce_game():
"""
Remind the player of the rules.
"""
pass
def prepare_deck():
"""
Use a standard deck of 52 cards.
"""
deck = []
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
for suit in suits:
for rank in ranks:
card = {"suit": suit, "rank": rank}
deck.append(card)
return deck
# - Shuffle the Deck:
def shuffle_deck():
"""
Shuffle the deck to randomize the order of cards.
"""
pass
#
# 2. Initial Deal
#
def deal_cards():
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# - The dealer has one card face up (the "upcard") and one card face down (the "hole card").
pass
#
# 3. Player Actions
#
def check_for_blackjack():
"""
Immediately check for Blackjack (an Ace and a 10-value card).
"""
# - If the player has Blackjack and the dealer does not, the player wins instantly.
# - If both have Blackjack, it's a push (tie).
# - If the dealer has Blackjack and the player does not, the dealer wins.
pass
# - Player Decisions: The player takes their turn with the following options:
def player_hit():
"""
Request an additional card to try to get as close to 21 as possible without going over.
"""
pass
def player_stand():
"""
Keep the current hand and end the turn.
"""
pass
def player_double_down():
"""
Receive one more card and then stand.
"""
pass
def player_split():
"""
If the player's first two cards have the same value, they can split them into two separate hands and play each hand separately.
"""
# If the player's first two cards have the same value then
# they can split them into two separate hands
# play each hand separately.
pass
def player_surrender():
"""
Forfeit the hand and end the turn (optional and depends on house rules).
"""
pass
#
# 4. Dealer Actions
#
def dealer_reveal_hole_card():
"""
The dealer reveals the face-down card.
"""
pass
def dealer_reveal_hole_card():
"""
The dealer plays according to fixed rules:
"""
# - Hits until reaching a hand value of 17 or more.
pass
#
# 5. Resolution
#
def determine_winner():
"""
Compare the player’s hand value to the dealer’s hand value.
"""
# - Bust: Any hand exceeding 21 automatically loses.
# - Player Wins: The player's hand value is higher than the dealer’s without busting.
# - Dealer Wins: The dealer’s hand value is higher than the player's without busting.
# - Push: Both hands have equal value, resulting in a tie.
pass
#
# 6. End of Round
#
def end_round():
# - Collect Cards: Collect all cards and reshuffle if necessary.
# - Continue: Decide whether to play another round.
pass
def main():
# 1. Initial Setup
print("Welcome to Blackjack")
deck = prepare_deck()
print(deck)
# 2. Initial Deal
# 3. Player Actions
# 4. Dealer Actions
# 5. Resolution
if __name__ == "__main__":
main()
During the reorganizing process I did the following:
- I added Python's version of a main function. (If you're using another language, you might have already been using a main function.)
- I reiterated the numbered phases of the game within the main function to show exactly where I am in the development process.
- As the program cleaned up, it became clear I could use some final functions for ending the round, etc.
- When a function was created from a comment, I moved that comment into the function. (In the Python example, I used a docstring for this as
"""comment"""
), which lets me know what that function should do. - After each small change, I ran the program, ensuring it did the exact same thing since I wrote the
prepare_deck()
function.
Tip 9 - Identify Key Variables and their Type
In my opinion, pulling out functions from English is fairly easy. You can think of something as a function if it performs an action (a verb) or answers a question. Examples are prepare_deck()
, deal_card()
, and player_has_busted()
.
The trickier bit is determining what variables you need based on English text. Here are some rules I follow when determining variables.
-
Plurals Nouns Become List/Arrays: Cards should be a list of card variables, for example,
cards = []
. Note that I did not populate the list; I simply know right now it needs to be some form of a list. In card games, there are some hidden plurals that present as singular such asdeck
, andhand
. So be on the look out for words like this. -
Identify the Singular Nouns: The next thing I'll do is think about what are the singular nouns, such as
rank
,suit
,card
,dealer
, andplayer
. -
Do the Singular Nouns Have Attributes?: Now I'll think about each of my variables and think, does
card
have any attributes which are important? Yes, each card has asuit
and arank
. This tells me thatcard
should be a dictionary|map. Something like this.card = {"suit": "", "rank": ""}
. For Player, and Dealer, they each have a hand.dealer = {"hand": []}
andplayer = {"hand": []}
. I also thought about my first rule, hand is really a plural so that should be a list. If a noun doesn't have any attributes other than itself it is usually a primitive, i.e.rank = 3
,suit = "hearts"
.
It's important to know that I don't really worry about populating these variables yet. I just jot down their basic structure and move on, I'll figure out how to create them later. You can refer to the prepare_deck()
function on how we populated the deck
.
Tip 10 - Determine Where Your Key Variables are Needed
Now that we have some key variables identified, I'll start thinking about where they should be placed throughout the program. From looking at the program so far, the common thread seems to be that nearly every function refers to deck
, player
, and dealer
. These variables should be declared someplace outside those functions. Usually, main()
is a great place for this, as main()
acts as the high-level orchestrator for the whole program.
Note that I'm already getting my deck created from prepare_deck()
which I wrote earlier, so I don't need to redeclare deck = []
. You may find yourself implementing these tips out of order, whatever you think is needed next is fine. And run your program.
def main():
player = {"hand": []}
dealer = {"hand": []}
# 1. Initial Setup
print("Welcome to Blackjack")
deck = prepare_deck()
print(deck)
# 2. Initial Deal
# 3. Player Actions
# 4. Dealer Actions
# 5. Resolution
if __name__ == "__main__":
main()
Next, I think about each function and look for the names of key variables being used in the function's name or in the comments within the function. A good example is the check_for_blackjack()
function. In the comments, it refers to the player and the deck. I'll add these variables as parameters to the function.
def check_for_blackjack(dealer, player):
"""
Immediately check for Blackjack (an Ace and a 10-value card).
"""
# - If the player has Blackjack and the dealer does not, the player wins instantly.
# - If both have Blackjack, it's a push (tie).
# - If the dealer has Blackjack and the player does not, the dealer wins.
pass
This one is easy, shuffle_deck()
will need the deck
.
def shuffle_deck(deck):
"""
Shuffle the deck to randomize the order of cards.
"""
pass
This one is slightly harder. In the comments, it mentions the player
and the dealer
, but they each need a card. Where do cards come from? The deck
. So, this function would most likely need access to the deck
as well.
def deal_cards(dealer, player, deck):
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# - The dealer has one card face up (the "upcard") and one card face down (the "hole card").
pass
What Next?
At the end of all this reorganizing, stubbing out functions, declaring key variables, and more, when I run the program, I get the exact same result as when I wrote the prepare_deck()
function. At this point, your code will tell you what the next step should be. If I go to main()
, I can see that we have pretty much accomplished Phase 1 of the game: # 1. Initial Setup
. Next is # 2. Initial Deal
, and if I scroll back up to find where # 2. Initial Deal
is also mentioned, I see:
#
# 2. Initial Deal
#
def deal_cards(player, dealer, deck):
# - The player and the dealer each receive two cards.
# - The player's cards are dealt face up.
# -
pass
That's the next problem to solve. I'm not thinking about an entire Blackjack game now. I'm simply focused on how to solve the problem defined in the comments, given a player, a dealer, and a deck.
Using this method, you will bounce from main()
into a function, solve it, bounce back to main()
, find the next step, bounce to that one, and so on.
At any given point, you might need to use one or more of the techniques listed above to solve a specific issue in the development process. A function isn't clear enough? Write some pseudocode. Is it clear? Identify verbs, nouns, etc., in your pseudocode, then declare new functions and variables. Always be printing along the way. And run, run, run your program after every change.
Appendix
Here's the entire Blackjack implementation if interested.
import random
# 1. Initial Setup
def introduce_game():
"""
Remind the player of the rules.
"""
print("Welcome to Blackjack!")
print("Try to get as close to 21 as possible without going over.")
print("Face cards (Jack, Queen, King) are worth 10 points.")
print("Aces are worth 1 or 11 points.")
print("Good luck!\n")
def prepare_deck():
"""
Use a standard deck of 52 cards.
"""
deck = []
suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
for suit in suits:
for rank in ranks:
card = {"suit": suit, "rank": rank}
deck.append(card)
return deck
# Shuffle the Deck
def shuffle_deck(deck):
"""
Shuffle the deck to randomize the order of cards.
"""
random.shuffle(deck)
# 2. Initial Deal
def deal_cards(dealer, player, deck):
"""
The player and the dealer each receive two cards.
The player's cards are dealt face up.
The dealer has one card face up (the "upcard") and one card face down (the "hole card").
"""
player['hand'] = [deck.pop(), deck.pop()]
dealer['hand'] = [deck.pop(), deck.pop()]
# 3. Player Actions
def calculate_hand_value(hand):
"""
Calculate the total value of a hand.
"""
value = 0
num_aces = 0
for card in hand:
rank = card['rank']
if rank in ['Jack', 'Queen', 'King']:
value += 10
elif rank == 'Ace':
value += 11
num_aces += 1
else:
value += int(rank)
# Adjust for Aces
while value > 21 and num_aces > 0:
value -= 10
num_aces -= 1
return value
def check_for_blackjack(dealer, player):
"""
Immediately check for Blackjack (an Ace and a 10-value card).
"""
player_value = calculate_hand_value(player['hand'])
dealer_value = calculate_hand_value(dealer['hand'])
if player_value == 21 and dealer_value != 21:
print("Player has Blackjack! Player wins!")
return True
elif dealer_value == 21 and player_value != 21:
print("Dealer has Blackjack! Dealer wins!")
return True
elif player_value == 21 and dealer_value == 21:
print("Both player and dealer have Blackjack! It's a push (tie).")
return True
return False
def player_hit(player, deck):
"""
Request an additional card to try to get as close to 21 as possible without going over.
"""
player['hand'].append(deck.pop())
print("Player hits.")
print_hand(player, reveal_all=True)
def player_stand():
"""
Keep the current hand and end the turn.
"""
print("Player stands.")
# Not Implementing split, double down, and surrender for simplicity
def player_actions(player, deck):
"""
Allow the player to take actions until they stand or bust.
"""
while True:
action = input("Do you want to hit or stand? (h/s): ").strip().lower()
if action == 'h':
player_hit(player, deck)
if calculate_hand_value(player['hand']) > 21:
print("Player busts!")
break
elif action == 's':
player_stand()
break
else:
print("Invalid input, please enter 'h' to hit or 's' to stand.")
# 4. Dealer Actions
def dealer_reveal_hole_card(dealer):
"""
The dealer reveals the face-down card.
"""
print("Dealer's hand:")
print_hand(dealer, reveal_all=True)
def dealer_plays(dealer, deck):
"""
The dealer plays according to fixed rules:
- Hits until reaching a hand value of 17 or more.
"""
dealer_reveal_hole_card(dealer)
while calculate_hand_value(dealer['hand']) < 17:
dealer['hand'].append(deck.pop())
print("Dealer hits.")
dealer_reveal_hole_card(dealer)
dealer_value = calculate_hand_value(dealer['hand'])
if dealer_value > 21:
print("Dealer busts!")
# 5. Resolution
def determine_winner(dealer, player):
"""
Compare the player’s hand value to the dealer’s hand value.
"""
player_value = calculate_hand_value(player['hand'])
dealer_value = calculate_hand_value(dealer['hand'])
if player_value > 21:
print("Dealer wins!")
elif dealer_value > 21 or player_value > dealer_value:
print("Player wins!")
elif dealer_value > player_value:
print("Dealer wins!")
else:
print("It's a tie!")
# 6. End of Round
def end_round(deck, player, dealer):
"""
Collect cards and reshuffle if necessary. Decide whether to play another round.
"""
player['hand'].clear()
dealer['hand'].clear()
if len(deck) < 10:
print("Reshuffling deck.")
deck = prepare_deck()
shuffle_deck(deck)
return deck
def print_hand(entity, reveal_all=False):
"""
Print the current hand of the player or dealer.
"""
hand_str = []
for i, card in enumerate(entity['hand']):
if i == 0 and not reveal_all and entity == dealer:
hand_str.append("Hidden Card")
else:
hand_str.append(f"{card['rank']} of {card['suit']}")
print("Hand:", ", ".join(hand_str))
if reveal_all or entity != dealer:
print("Hand Value:", calculate_hand_value(entity['hand']))
print()
def main():
# 1. Initial Setup
deck = prepare_deck()
shuffle_deck(deck)
player = {"hand": []}
dealer = {"hand": []}
introduce_game()
while True:
# 2. Initial Deal
deal_cards(dealer, player, deck)
print("Player's hand:")
print_hand(player, reveal_all=True)
if not check_for_blackjack(dealer, player):
# 3. Player Actions
player_actions(player, deck)
# 4. Dealer Actions
if calculate_hand_value(player['hand']) <= 21:
dealer_plays(dealer, deck)
# 5. Resolution
determine_winner(dealer, player)
# 6. End of Round
deck = end_round(deck, player, dealer)
play_again = input("Do you want to play another round? (y/n): ").strip().lower()
if play_again != 'y':
break
if __name__ == "__main__":
main()
Top comments (0)