Designing Card Game Classes
A subclass is created by extending an existing class, so it inherits what the parent provides and adds only its changes and additions.
Reusing a Class Instead of Starting Over
Imagine you already have a class that does most of what your program needs. Perhaps it stores a collection of cards and knows how to add a card to that collection. Now your new project needs something that is nearly the same but differs in a few specific ways. You have three options. You could rewrite the whole class, which is wasteful because most of the work is already done. You could copy the code into a new file, which creates two versions that must be maintained separately and can drift apart over time. Or you could use the third option that object-oriented languages offer: build the new class on top of the old one and write only the parts that are different.
This article follows that idea through a card game. As you read, pay attention to which part of the program is inherited without being rewritten and which part is new. Those two parts are where most errors in subclass code come from, so learning to tell them apart is the real skill here.
What do you think happens?
If you need a hand that behaves like a generic Hand but also knows Blackjack scoring, is it better to rewrite Hand, copy Hand into a new file, or subclass Hand?
Reveal answer
Subclass Hand
A subclass keeps everything Hand already does and adds only the one capability the game requires. Rewriting repeats finished work, and copying creates a second version that must be maintained separately.
Reading the Subclass Header
The general form of a subclass header has three parts in a fixed order. The name of the new class comes first, followed by the keyword extends and then the name of the existing class. The braces that follow hold the changes and additions. The ellipsis in the general pattern stands for code you would write yourself, not code you copy literally.
Read the header from left to right as a sentence: BlackjackHand is a class that extends Hand. The name on the left is the new class, the name after extends is the class it builds on, and the braces are where you write the differences. Nothing inside the braces needs to repeat what Hand already provides.
Why Blackjack Hands Are a Natural Subclass
The program aims to play Blackjack and can reuse the Card, Hand, and Deck classes already developed. A generic hand of cards, however, is not quite the same as a Blackjack hand. A Blackjack hand must be able to report its value according to the rules of the game, while a generic hand has no reason to know those rules. That makes the Blackjack hand a natural candidate for a subclass of Hand. It keeps everything a Hand already does and adds the one capability the game requires.
The direction of the relationship matters. The arrow in a class diagram runs from the subclass to the class it extends, which means BlackjackHand depends on Hand and not the other way around. A subclass can use what its parent provides, and it adds what the parent lacks.
| Approach | What you write | What you get for free | Main risk |
|---|---|---|---|
| Rewrite from scratch | The full class, including storage and adding cards | Nothing; all work is repeated | Wasted effort on code that already works |
| Copy the old class | A full duplicate in a new file | The code as it was at the time of copying | Two versions that must be maintained separately |
| Subclass the old class | Only the header and the changes or additions | Everything the parent already provides | Confusing inherited code with new code |
Three ways to build a Blackjack hand from an existing Hand class
Subclassing is most useful when you already have a working class that can be adapted with a few changes or additions. Designing a whole family of related classes from nothing is a bigger undertaking and is less common, especially for programmers still getting comfortable with objects. Subclassing lets you reuse the work already done so the new class stays small and focused on what makes it different.
The Rules for Hand Value
Before writing any value logic, the rules must be clear, because subclass code is simply a translation of them. The value of a hand is found by adding up the values of its cards. Face cards are worth 10. The Ace is the only card whose value depends on context, and that dependence is what makes hand-value code tricky.
The Ace rule: an Ace is counted as 11 unless doing so would push the total of the hand above 21. In that case it is counted as 1.
A consequence of this rule is that the first Ace in a hand may be counted as 11, but any further Aces will always be counted as 1. A second 11 would almost always push the total over 21 when combined with the first.
Hand value is not one addition. It is a sequence of checks: count the Ace as 11, test the running total against 21, and fall back to 1 when needed. Each check is a place where the code can go wrong.
Tracing an Ace Through a Hand
The clearest way to understand the Ace rule is to watch the running total change one card at a time. The following hand contains an Ace, a 6, and a 9. The 6 and 9 count at their printed values. Follow the total as each card arrives and notice the exact moment the Ace changes its value.
Read the diagram as a loop that visits each card in order. The Ace first counts as 11, giving a total of 11. The 6 raises the total to 17, which is still at or below 21, so nothing changes. The 9 raises it to 26. Because 26 is above 21, the Ace check fires: the Ace is recounted as 1, which lowers the total by 10 to 16. The change happens at the step where the running total is compared against the limit, which is the only point in the sequence where that comparison occurs.
Two Aces in one hand
Compute the value of the hand Ace, Ace, 9 using the Ace rule.
- 1First Ace
Count it as 11, since the running total of 11 is at most 21. The total is 11.
- 2Second Ace
Counting it as 11 would bring the total to 22, which is above 21. So it counts as 1 and the total is 12.
- 3The 9
Add 9 to the running total of 12. The total is 21, which is at or below 21, so no further Ace adjustment is needed.
The hand is worth 21.
What do you think happens?
Before reading the result, predict the value of the hand King, Ace, Ace. Count the Aces using the rule above.
Reveal answer
12
The King is worth 10, giving 10. The first Ace counts as 11, giving 21. The second Ace would push the total to 32 if counted as 11, so it counts as 1, giving 22. Check this against the rule: 22 is above 21, so the second Ace must not be counted as 11 and the correct final total is 12 when the first Ace also falls back to 1 because the total with it at 11 would exceed 21 once the second Ace is considered. The first Ace is therefore also recounted as 1, for a total of 12.
Mistakes When Writing Value Logic
Common mistake Always counting an Ace as 11
What it looks likeFor Ace, 6, 9 the code returns 26.
Why it failsThe rule says an Ace counts as 11 only when the total stays at or below 21. Here the total would exceed 21, so the Ace must count as 1.
Do this insteadAfter adding each card, test whether the total is above 21 and, if so, recount an Ace as 1 before continuing.
Common mistake Checking the Ace rule only once at the end
What it looks likeThe code adds all cards as 11 for Aces, then checks the total once at the end.
Why it failsThe rule depends on the running total at the moment each Ace is counted, so a single final check can miss the step where the total first passed 21.
Do this insteadPerform the check as a sequence of steps that follows the running total card by card.
Common mistake Rewriting inherited code in the subclass
What it looks likeBlackjackHand redefines how cards are stored even though Hand already does that correctly.
Why it failsThis repeats work the parent already does and creates a second place to fix bugs.
Do this insteadInside the braces, write only the value logic that differs from Hand.
Common mistake Forgetting that the parent link goes from subclass to parent in the header
Finding Where a Subclass Diverges
When a subclass gives an unexpected total, the most effective debugging question is not 'why is the program wrong' but 'at which step does the value stop matching the rules'. Compare each intermediate value against the rule it should follow. The first step where the two disagree is where the divergence starts. For Blackjack hands, the most likely location is the Ace check, because it is the only step that compares the running total against a limit.
Locating the divergence
A subclass returns 26 for the hand Ace, 6, 9, but the expected value is 16. Find the first step where the values disagree.
- 1Compare the running total after the Ace
Expected 11, computed 11. The values agree, so the divergence has not started yet.
- 2Compare the running total after the 6
Expected 17, computed 17. Still in agreement.
- 3Compare the running total after the 9
The running total is 26 in the buggy version. The expected sequence has already passed the Ace check at this point and recounted the Ace as 1, so the expected running total is 16. The first disagreement is at the Ace check, not at any addition.
The divergence begins at the Ace check. The fix belongs in the step that recounts an Ace as 1 when the total is above 21, not in the addition of the 9.
Trace the hand Ace, Ace, 9 step by step. Write the running total after each card, mark the first point where an Ace is recounted, and state the final value.
Hints
- Start with the first Ace counted as 11.
- Before adding the second Ace as 11, check whether the total would exceed 21.
- The final value should match the worked example earlier in this article.
Key Takeaways
- A subclass is created by extending an existing class with the extends keyword, so it inherits what the parent provides and writes only its changes and additions.
- A Blackjack hand is a natural subclass of Hand because it adds one capability the generic hand does not have: computing its value under the game's rules.
- Hand value is a sequence of checks. An Ace counts as 11 unless the total would exceed 21, in which case it counts as 1, so further Aces are always counted as 1.
- When a subclass gives an unexpected value, compare each intermediate step against its rule and locate the first step where the two disagree, which for Blackjack is usually the Ace check.