As your second challenge, you’ll implement a game of Blackjack. In this game, the player draws cards to achieve a higher total than the dealer without exceeding 21. The player bets a number of credits on every hand they play and continues playing until they choose to stop or they run out of credits.
Unlike the previous challenge, this one doesn’t tell you which functions to declare and in what files to put them; that’s up to you now. Instead, the challenge will guide you through the phases of the software development process: analysis, design, and implementation.
By learning about the software development process, you’ll learn to take a structured approach to programming, and you’ll avoid getting stuck or becoming overwhelmed by the size of this challenge.
Before you start programming, take some time to study the topic of the challenge, also known as the domain. Familiarize yourself with the game of Blackjack and answer the following questions:
- What are the basic rules of the game?
- What are some commonly used advanced rules or variations?
- How does betting work? Are bets fixed, or can the player choose how much to bet?
- What does gameplay look like? What actions does the player take, and what information should the game display?
Once you understand how the game works, write down the requirements for your application. These requirements specify what you expect from your finished product. They contain the rules you must adhere to and the features you must implement.
Start by writing down the rules of Blackjack. You can decide for yourself which rules and variations you want to include. The challenge will be sufficiently difficult, even if you include only the basic rules.
Next, add the following requirements:
- The game uses a terminal to interface with the player. You’ll display information as text and use the
readLinefunction to read input from the player.
- The game supports only a single player. You don’t have to build a multi-player game.
Finally, feel free to add any requirements of your own. For example, the solution project includes a requirement to display suits as emojis (♥ ♦ ♠ ♣).
Now that you’ve specified the requirements for your application, write down one or more scenarios. A scenario describes the interactions between the player and the game in a step-by-step manner. It includes the actions that are taken, the order in which they’re taken, and who takes them.
Here’s a scenario that describes the player’s turn after receiving their initial hand:
- The game asks the player if they want to hit or stand.
- The player inputs their selection.
- If the player chooses to stand, their turn ends.
- Otherwise, the game deals the player a new card and displays their new hand.
- If the player’s hand exceeds 21, it’s a bust, and the game displays an appropriate message.
- Otherwise, return to Step 1.
Scenarios should be short and focused. You don’t have to cover the entire game in a single scenario, nor do you have to include every requirement at once. The previous scenario didn’t include actions such as doubling down or splitting, and didn’t cover the initial deal, the dealer’s turn, or betting. You can describe these separately in other scenarios.
When you write a scenario, include one or more examples that show the expected input and output for that scenario. Here’s an example where the player hits twice, then stands:
You have 13: 10♠, 3♦.
Dealer has 6♣.
Would you like to (h)it or (s)tand?
You have 15: 10♠, 3♦, 2♣.
Would you like to (h)it or (s)tand?
You have 19: 10♠, 3♦, 2♣, 4♦.
Would you like to (h)it or (s)tand?
This example also shows the initial hands; these aren’t displayed as part of the scenario.
If your scenario has branches, include an example for every branch. Here’s an example where the player hits, then busts:
You have 12: 2♣, 10♣.
Dealer has J♦.
Would you like to (h)it or (s)tand?
You have 22: 2♣, 10♣, K♠.
This shows the output of Step 5 of the scenario.
Once you’ve finished your analysis, compare it with the analysis document from the solutions bundle. This document contains all of the requirements and scenarios you’ve been asked to write down. Your analysis may be different, but most of the requirements and scenarios should be similar.
For an application of this size, you shouldn’t attempt to implement all of the requirements and scenarios at once. Instead, take an iterative approach; start small and build from there.
Here are the iterations for this challenge:
- Set up the game and implement the player’s turn. Include only the actions hit and stand, and ignore betting.
- Add the dealer’s turn so you can play a complete hand.
- Implement betting and add a loop so you can play and bet on multiple hands.
- Implement any remaining requirements.
You can use different iterations if you want; just make sure each iteration is manageable in size. It’s better to have many small iterations and make steady progress than to get stuck on an iteration that’s too big in scope.
Now that you have an iteration plan, assign each requirement and scenario to an iteration, as you’ve seen in the analysis document from the solutions bundle. You may have to restructure your requirements and scenarios a bit to better align them with the iterations.
In the upcoming design and implementation phases, you’ll work on each iteration in turn and use the result of each iteration to start the next one. This is how iterative development keeps your work manageable. Instead of trying to solve every problem at once, you build your application one requirement or scenario at a time.
Software analysis helps you understand and specify the requirements for your application. It focuses on what your application needs to do. The next phase, software design, translates these requirements into a design you can implement. It focuses on how you’re going to build your application.
Start by translating the scenarios for the first iteration into pseudocode, as discussed in Control Flow and Booleans. This pseudocode can be anything from plain English to valid Swift. At first, you may find it easier to use English, but as you gain experience, you’ll lean more toward using Swift.
The pseudocode you write is an outline of the implementation for each scenario. It’s an intermediary step between a scenario in English and its implementation in Swift. The better you get at writing outlines, the less work you’ll have implementing them.
Writing an outline in pseudocode lets you reason about the structure of your code without having to worry about getting it exactly right. Outlines help you locate the branches and loops in your code, as well as identify any variables and functions you’ll need to implement them. The latter is what you’ll do next.
Variables and functions
The next step in the design phase is to identify the variables and functions you need to implement your outlines. To do this, consider each statement in your outlines and ask yourself the following questions:
- What data do I need to implement this statement?
- What variables do I store this data in?
- What functions do I need to implement this statement?
- Do these functions require additional variables and functions of their own?
Use your answers to these questions to compile a list of variables and functions you think you need. This list won’t be complete, but it’s a good starting point for the implementation phase.
Also, consider how you can logically group your variables and functions into files. Your code will quickly become unmanageable if you put it all in a single file.
Once you have a design for the first iteration, compare it with the design document from the solutions bundle. Your design will be different, but it should be just as detailed.
Don’t be tempted to replace your design with the one from the solutions bundle. Stick with what you have and see how well it works. A single application can have multiple possible designs, each with its own benefits and drawbacks. It’s only through experimentation and experience that you’ll learn which designs work well and which don’t.
Now that you’ve gone through an analysis and design phase, you’re well prepared to write some code.
Start by implementing the variables and functions you identified in the design phase. If you encounter a function whose implementation isn’t straightforward, do some additional design work. Outline the function in pseudocode and identify any additional variables and functions it needs. Implement those requirements first, then try to implement the function itself.
After you’ve implemented all of the variables and functions on your list, move on to the scenarios. Implement them using their outline as a starting point.
You may encounter issues with your design as you implement it. Some pieces may be missing, while others may not work as intended. These issues are to be expected. A design is only a best effort; you won’t know how well it works until you implement it. Feel free to change your design as you implement it.
Once you have a working first iteration, review your work, and look for opportunities to improve it. Update your design and implementation with these improvements and verify everything still works.
With your first iteration polished, you’re ready to start the second one. Return to the design phase and outline the scenarios for the second iteration. Identify any new variables and functions you need and add them to your design. Then, continue your implementation with this new design. When you’re done, give your work another polish. You may discover better ways to design and implement your application as it grows.
Continue designing and implementing one iteration at a time until you’ve worked your way through all of the requirements and scenarios. At that point, your challenge is complete.
The solutions bundle includes a separate implementation for each iteration. By showing you how the design and implementation grow with each iteration, you’ll gain a better understanding of the iterative development process.
Congratulations on completing the second challenge.
Your skills have improved a lot since the previous challenge, and this challenge pushed those skills to their limit — perhaps even beyond. The experience you’ve gained here will help you understand the topics coming up in Part IV.
If you haven’t already done so, compare your solution with the one from the solutions bundle. Make sure you fully understand this solution as the upcoming chapters refer to it.
If you need a break, now is an excellent time for one. Take some well-deserved time off to rest and recharge so that you can start Part IV with a fresh mind.