As your third and final challenge, you’ll implement a game of Yahtzee.
You’ll complete this challenge using the structured approach described in Blackjack. You’ll be guided through the phases of the software development process: analysis, design, and implementation, but the explanations won’t be as detailed as they were previously. Please refer to the previous challenge if you need a refresher.
As always, a solution project is provided for you to compare your work against.
In the software analysis phase, you specify the requirements for your application. The requirements for this challenge are to implement the rules of Yahtzee and to support multiple players. You’ll use a terminal to interface with the players.
Start by learning the rules of the game and answer the following questions:
- How does re-rolling work?
- How is each field scored?
- What is the upper section bonus?
- What happens when a player rolls multiple Yahtzees in a single game?
Once you understand how the game works, write down some scenarios that describe in detail what actions the players take and what information the game displays.
Together, the requirements and scenarios specify what you expect from your application. In the next phase, you’ll consider how to implement them.
As with the previous challenge, you’ll build your game incrementally in iterations. Here are the suggested iterations:
- Implement a single turn of rolling and re-rolling the dice. Don’t worry about scoring or multiple players just yet.
- Add scoring for the thirteen fields so that you can play a complete turn.
- Add the upper section bonus, bonus Yahtzees, and jokers. Also, add multiple turns so that you can play a complete game.
- Extend the game to support multiple players.
Assign your requirements and scenarios to these iterations, then start the first design phase.
In the software design phase, you consider the requirements and scenarios for each iteration and come up with a design that you can implement. For the first design phase, consider only the requirements and scenarios for the first iteration.
For each scenario, write an outline of its implementation in pseudocode. This pseudocode can be anything from plain English to valid Swift. The closer your outlines are to actual code, the easier they’ll be to implement.
Next, use your outlines to identify the types you need. Find out what data you need to store and which operations you need to perform. Then, group these into types with a coherent set of properties and methods.
Also, consider how you encapsulate each type member. Use
private(set) to encapsulate members that shouldn’t be exposed, and use nested types or functions where appropriate.
With this preparation done, you’re ready to write some code.
Start by implementing the scenarios for the first iteration. First, implement the types you’ve designed, then implement the scenarios using their outline as a starting point.
When you’re done, look for opportunities to improve your code. Update your design to match these changes, then move on to the next iteration.
Continue designing and implementing one iteration at a time until you’ve worked your way through all of the requirements and scenarios.
If you want to become a software developer, you have to understand the importance of documenting your work. Proper documentation helps developers understand your code, which is important even when working on your own. So much thought goes into code that it’s easy to forget the details of how it works. You’ll want proper documentation when you revisit your code in the future to fix bugs or add features.
Saving your analysis and design documents for future reference is a good start. These documents describe what your application does and provide a high-level overview of how it works.
In your implementation, you use documentation comments to document your types and their members. Here’s an example from the Standard Library:
/// Returns the first index where the specified value appears in the
/// After using `firstIndex(of:)` to find the position of a particular element
/// in a collection, you can use it to access the element by subscripting.
/// - Parameter element: An element to search for in the collection.
/// - Returns: The first index where `element` is found. If `element` is not
/// found in the collection, returns `nil`.
func firstIndex(of element: Character) -> String.Index?
You start each line of a documentation comment with a triple-slash (
///). You can also use
*/ to create a multiline documentation comment, but a triple-slash per line is more common.
Documentation comments use DocC to support rich formatting. The previous example demonstrates some of these formatting features:
- Empty lines will separate paragraphs.
- The first paragraph provides a summary of the type or member. Subsequent paragraphs offer additional information.
- Backticks (
`) format text as code; use them around the names of variables, functions, types, and so on.
Parametersection describes a function’s parameter.
Returnssection describes a function’s return value.
Because of their rich formatting, documentation comments are useful outside of your code as well. They enable the quick help feature of your editor, and you can export them to other formats, such as a website. In fact, the Swift Standard Library website is largely generated from documentation comments.
This doesn’t mean you should only use documentation comments from now on. Think of documentation comments as part of your design: they document your types and their members. You still use regular comments to clarify the implementation details.
It’s time to add documentation comments to your code. Only when your work is adequately documented can you consider your challenge completed.
Congratulations on completing the final challenge.
The goal of this challenge was to design and implement an application using custom types. These types add structure and encapsulation to your code, which results in a smoother development experience. Of course, that also depends on the quality of your analysis and design work.
If you haven’t already done so, compare your solution with the one from the solutions bundle and learn from the differences.
This challenge completes the learning process for this course. The next chapter revisits the course objectives and discusses how the course is evaluated.