Players, the Game Loop, and Class Relationships

The Crazy Eights rules at last, in a Player class that decides which card to play and an Eights class that holds the state of the game — assembled by bottom-up design, the counterpart to Chapter 13's top-down. Then the two relationships every object-oriented design is made of: HAS-A and IS-A. Follows Think Java 2e, Chapter 14 (Extending Classes), Sections 14.4-14.6, pp. 240-247, cross-referenced against The Java Tutorials — Overriding and Hiding Methods.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Players, the Game Loop, and Class Relationships

Title

Think Java 2e · Chapter 14 · Extending Classes

Sections 14.4-14.6 · pp. 240-247

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 14 (Extending Classes), Sections 14.4-14.6, pp. 240-247. Everything on these slides can be checked against those pages.

1. Separate game rules from reusable card classes, and say why that separation is worth keeping.

2. Write a strategy method that delegates to helper methods for its two cases.

3. Explain bottom-up design and contrast it with top-down design.

4. Trace a game loop that alternates between two players until one hand is empty.

5. Distinguish composition (HAS-A) from inheritance (IS-A).

6. Read and draw a UML class diagram using both kinds of arrow.

3. Retrieve before you read

Warm-up

Two ideas the chapter is about to use by name.

Discussion prompt

From Lesson 13a: what is top-down design, and what does pseudocode give you? And from Lesson 14a: what is the difference between saying a Player is a Hand and a Player has a Hand?

Hint: One writes the outline first. The other is the whole basis of the next section.

Answer:

Top-down design means writing pseudocode first and then writing the helper methods to make it work. And is a means inheritance — extends, with substitution — while has a means composition: an instance variable holding one.

A Player has a Hand, which is why Lesson 14a rejected Player extends Hand. This lesson writes that class, and Section 14.6 finally gives both relationships their proper names.

4. The rules, at last

Concept

The Deck and Hand classes we have defined so far could be used for any card game; we have not yet implemented any of the rules specific to Crazy Eights. But now it's time to implement the rules.

Figure (svg): Two panels separating the reusable card classes from the two game-specific classes

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 14 (Extending Classes), Sections 14.4-14.6, pp. 240-247 — Section 14.4 opens on printed page 240.

5. The Player class

Section

Section 14.4

6. A player has a name and a hand

Concept

We'll use two classes: Player, which encapsulates player strategy, and Eights, which creates and maintains the state of the game.

public class Player {
    private String name;
    private Hand hand;

    public Player(String name) {
        this.name = name;
        this.hand = new Hand(name);
    }
}
attributetyperelationship
nameStringa Player has a name
handHanda Player has a Hand

A Player has two private attributes: a name and a hand. The constructor takes the player's name as a string and saves it in an instance variable. In this example, we have to use this to distinguish between the instance variable and the parameter with the same name. Lesson 14a dropped this everywhere it was optional — this is a place it is not.

7. A Player has a Hand, and a Hand is a CardCollection

Picture it

Both relationships in one picture. The arrows point differently on purpose.

Figure (svg): A UML diagram showing Player composed of a Hand and Hand inheriting from CardCollection

Hand IS-A CardCollection; Player HAS-A Hand. A Player cannot be dealt cards directly and never should be — every card operation goes through hand, which is exactly the boundary composition draws.

8. The play method, and top-down design again

Worked example

The primary method that Player provides is play, which decides which card to discard during each turn. It is five lines and neither helper exists yet.

public Card play(Eights eights, Card prev) {
    Card card = searchForMatch(prev);
    if (card == null) {
        card = drawForMatch(eights, prev);
    }
    return card;
}
parameteris
eightsthe object holding the state of the game
prevthe card on top of the discard pile
the return valuethe card this player chose to play

Look in your hand first.

Why: searchForMatch(prev) returns a matching card, or null.

null means nothing matched.

Why: If there are no cards that match, it returns null.

Then draw until you get one.

Why: drawForMatch(eights, prev) — the rule that says you must keep drawing.

Neither helper is written yet.

Why: play invokes two helper methods: searchForMatch and drawForMatch. Since we have not written them yet, this is an example of top-down design.

Verify: Read play and check that it states the rule in five lines with no card handling in it.

Why: The two branches of the rule become the two helper methods, which is Lesson 13a's process exactly: the outline names the pieces. Note that null is being used as no card found — a sentinel, like -1 for a failed search in Lesson 12b.

9. What does searchForMatch return when nothing matches?

Prediction

The loop finishes without finding anything.

for (int i = 0; i < hand.size(); i++) {
    ...
}
return null;
outcomereturn
a matchthe card
no match?

Predict first

What comes back?

  • null — and play checks for it to decide whether to draw
  • an empty Card
  • the first card in the hand
  • it throws an exception

Correct: null — and play checks for it to decide whether to draw

Why: null is the sentinel for no card found, exactly as -1 was for a failed search in Lesson 12b — and the return sits after the loop, since only a finished loop proves no card matched. play tests for it with if (card == null).

10. The two helpers

Concept

searchForMatch looks in the player's hand for a card that matches the previously played card. drawForMatch handles the other case.

public Card searchForMatch(Card prev) {
    for (int i = 0; i < hand.size(); i++) {
        Card card = hand.getCard(i);
        if (cardMatches(card, prev)) {
            return hand.popCard(i);
        }
    }
    return null;
}
outcomereturnsside effect
a match is foundthe card, removed from the handthe hand shrinks
no matchnullnone
which card, if several matchthe first one foundthe strategy is simple on purpose

The strategy is pretty simple: the for loop searches for the first card that's legal to play and returns it. Exercise 14.1 asks you to do better — playing an eight only when you must, or the highest-ranking card first — by writing a subclass that overrides play.

11. Returning the card without removing it

Trap

The trap

getCard reads; popCard removes.

if (cardMatches(card, prev)) {
    return hand.getCard(i);      // the card is STILL in the hand
}
methodreturns the card?removes it?
hand.getCard(i)yesno
hand.popCard(i)yesyes

The card gets added to the discard pile and stays in the player's hand, so the game now has 53 cards and a player who can never run out. Cards stop being conserved, and the game never ends.

The fix

popCard, so the card actually leaves the hand.

if (cardMatches(card, prev)) {
    return hand.popCard(i);
}
stephand size
before5
popCard(i)4
the card is added to the discard pileconserved

Note the loop reads with getCard and then removes with popCard — read first, remove only once you have decided. Removing while searching would shift the remaining cards under the loop's own index, which is a separate bug of the same family.

12. Which class knows what?

Definition probe

The rules live in exactly one place.

Sort into buckets

Sort each piece of knowledge.

Card
a card's rank and suit
CardCollection
how to remove a card from a collection
Player
that an eight is wild; which card to play from a hand
card
Card knows its own data and nothing about any particular game.
cc
Holding and moving cards is what a collection does, for any game.
player
The Crazy Eights rules and the strategy for applying them — which is why Card and Hand stay reusable.

13. Search the hand

Fill the middle

Read each card, and remove the one you play.

Fill in the blanks

Card card = hand.getCard(i);
if (cardMatches(card, prev)) popCard}(i);
}

Why: getCard reads without modifying, so the loop can inspect each card in turn; popCard removes the one actually played. Using getCard for both would leave the card in the hand as well as on the discard pile, and the game would never end.

14. Why does play take the Eights object?

Socratic

It only needs one card from it.

Discussion prompt

play(Eights eights, Card prev) passes the whole game object. Why does the player need it — and what does that say about who owns the draw pile?

Hint: Where does a drawn card come from?

Answer:

Because drawing a card means taking one from the draw pile, and the draw pile belongs to the Eights object, not to any player.

So the player asks the game: eights.drawCard(). That method also handles reshuffling when the pile runs out — a rule the player should not have to know.

The state belongs to the game; the strategy belongs to the player. Passing this into play is how the two talk without either owning the other — and it is why takeTurn calls player.play(this, prev).

15. Drawing and matching

Section

Section 14.4

16. Draw until you get a match

Concept

When players don't have a matching card or an eight, they must draw new cards until they get one. That rule translates into a loop with no obvious end.

public Card drawForMatch(Eights eights, Card prev) {
    while (true) {
        Card card = eights.drawCard();
        System.out.println(name + " draws " + card);
        if (cardMatches(card, prev)) {
            return card;
        }
        hand.addCard(card);
    }
}
drawn cardmatches?what happens
3 of Clubsnoadded to the hand, draw again
Queen of Heartsnoadded to the hand, draw again
8 of Spadesyes — wildreturned, and played

The while loop runs until it finds a match (we'll assume for now that it always finds one). That parenthesis is the book being honest: while (true) with a return inside is correct only if a match eventually turns up.

17. while (true) with a return in the middle

Notation

The loop has no condition. Everything that ends it is inside the body.

Annotate

  • while (true) never ends on its own — the return is the only exit, which Lesson 6a called a loop that must be read carefully.
  • The shape fits the rule: you cannot know how many cards you will draw, so there is no count to loop over.
  • A non-matching card joins the hand. The player accumulates cards while searching, which is why hands grow when you cannot play.
  • The matching card is returned without joining the hand — it goes straight onto the discard pile in takeTurn.
  • The assumption is load-bearing. If no card in the game matched, this loop would spin forever calling drawCard — and drawCard reshuffles, so it would not even run out.

A while (true) is not automatically bad, but it is a promise you are making. Here the promise is that a match exists — reasonable, since eights are wild and there are four of them.

18. Translating the rules into cardMatches

Worked example

This method is a straightforward translation of the rules of the game. Read the rule and the code side by side.

public static boolean cardMatches(Card card1, Card card2) {
    return card1.getSuit() == card2.getSuit()
        || card1.getRank() == card2.getRank()
        || card1.getRank() == 8;
}
clausethe rule it encodes
card1.getSuit() == card2.getSuit()must match the suit
|| card1.getRank() == card2.getRank()or the rank
|| card1.getRank() == 8or be an eight — a wild card

The rule has three clauses.

Why: The card must match the rank or suit of the previously played card, or be an eight, which is a wild card.

Each becomes one comparison.

Why: Joined with ||, which is Lesson 5b's or.

Only card1 is checked for being an eight.

Why: card1 is the candidate; card2 is what is already on the pile.

It is static.

Why: cardMatches is a static method, also defined in Player — it reads no instance variable, only its two parameters.

Verify: Check the 8 of Clubs against the King of Hearts: suits differ, ranks differ, and the third clause makes it legal.

Why: A method that reads like the rule it implements is the goal. Exercise 14.4 notes the awkwardness: cardMatches is a static method in Player, but it would be more natural if it were an instance method in Card — except that Card is meant to work for any game, so the rule cannot live there.

19. Do these two cards match?

Prediction

Apply the three clauses.

Card prev = new Card(13, 2);   // King of Hearts
Card card = new Card(8, 0);    // 8 of Clubs
cardMatches(card, prev)
clauseresult
same suit? 0 vs 2no
same rank? 8 vs 13no
card1 is an eight??

Predict first

What does cardMatches return?

  • true — the third clause makes any eight legal
  • false — neither the suit nor the rank matches
  • true — because both are face cards
  • false — eights are only wild in the discard pile

Correct: true — the third clause makes any eight legal

Why: The three clauses are joined with ||, so any one of them is enough — and card1.getRank() == 8 is true. That is the wild-card rule the game is named after, and it is the reason the drawForMatch loop can assume a match will eventually turn up.

20. Where cardMatches really belongs

Concept

The book flags its own compromise. When we designed the program for this chapter, we tried to minimize the number of classes. As a result, we ended up with a few awkward methods.

optionproblem
an instance method in CardCard would know a Crazy Eights rule
a static method in Playerawkward — it is about cards, not players
a match method in an EightsCard subclassthe clean answer

The problem is that Card is supposed to be useful for any card game, not just Crazy Eights. You can solve this problem by adding a new class, EightsCard, that extends Card and provides a method, match. That is Exercise 14.4 — and it is inheritance used to add a game's rules without contaminating the reusable class.

21. Putting the game rule in Card

Trap

The trap

A reusable class that knows one game's rules.

public class Card {
    public boolean matches(Card that) {
        return this.suit == that.suit
            || this.rank == that.rank
            || this.rank == 8;      // Crazy Eights only
    }
}
gamedoes this method make sense?
Crazy Eightsyes
Pokerno
Warno
any future gameit is there anyway

Every program using Card now carries a rule about eights. The class stops being about cards and starts being about one game — and the next game has to work around it.

The fix

Subclass for the game, or keep the rule in the game's own class.

// Exercise 14.4's answer:
public class EightsCard extends Card {
    public boolean match(EightsCard that) { ... }
    public int scoreCard() { ... }
}

// the book's own compromise: a static method in Player
public static boolean cardMatches(Card c1, Card c2) { ... }
classknows about eights?
Cardno — reusable
EightsCardyes — that is its job
Playeryes

Inheritance lets a game extend the general classes rather than change them. Exercise 14.4 also suggests an EightsHand with a scoreHand method — the same move applied one level up.

22. Which class should own the rule?

Definition probe

Reusability is the deciding question.

Sort into buckets

Sort each method.

Card — any game
getRank; compareTo
the game's own classes
cardMatches; scoreCard — penalty points for Crazy Eights
card
It is about what a card is, and every card game needs it — so it belongs in the reusable class.
eights
It encodes a Crazy Eights rule, which no other game shares, so it belongs in Player or an EightsCard subclass.

23. Write the matching rule

Fill the middle

Suit, or rank, or a wild eight.

Fill in the blanks

return card1.getSuit() == card2.getSuit()
|| card1.getRank() == card2.getRank()
|| card1.getRank() == 8;

Why: The three conditions are alternatives, so they are joined with || — any one makes the play legal. Only card1 is checked for being an eight, because card1 is the card being played and card2 is what is already on top of the discard pile.

24. Could drawForMatch loop forever?

Edge cases

We'll assume for now that it always finds one.

Discussion prompt

The loop only exits on a match. Construct the situation where it never would, and say why it does not arise in practice.

Hint: How many eights are there, and what does drawCard do when the pile empties?

Answer:

It would need every remaining card to fail all three clauses — no card of the same suit, none of the same rank, and no eight anywhere available.

It cannot happen in practice: there are four eights and thirteen cards of each suit, and drawCard reshuffles the discard pile when the draw pile empties, so cards keep coming.

But the loop would spin forever rather than fail, which is worth noticing. The book's parenthesis is an assumption stated honestly — and an assumption stated is much better than one buried.

25. Bottom-up design

Section

Section 14.5

26. Start from the pieces, not the outline

Concept

In Section 13.2 we introduced top-down design. In this way of developing programs, we identify high-level goals and break them into smaller problems. In this section, we present bottom-up design, which goes the other way around: first we identify simple pieces we need and then we assemble them into more-complex algorithms.

// looking at the rules, we can identify some methods we'll need:
//   set up the game                      (Eights constructor)
//   check whether the game is over        (isDone)
//   shuffle the discard pile into the draw pile   (reshuffle)
//   draw a card, reshuffling if necessary (drawCard)
//   switch from one player to the next    (nextPlayer)
//   display the state and wait for input  (displayState)
top-downbottom-up
start withthe outlinethe pieces
discoverwhich helpers you needhow they assemble
good whenyou know the shapeyou know the parts
Chapter1314

bottom-up design — A way of developing programs by identifying simple pieces, implementing them first, and then assembling them into more-complex algorithms.

Looking at the rules of Crazy Eights, we can identify some of the methods we'll need. The rules are already a list of things that must happen — so reading them off is faster than inventing an outline.

27. The state of the game

Notation

Before any method, the attributes. Here is the beginning of the class definition for Eights, which encapsulates the state of the game.

Annotate

  • Two players. In this version, there are always two players. One of the exercises at the end of the chapter asks you to modify this code to handle more players.
  • Both piles are Hands, not Decks — because a pile changes size and needs no 52-card constructor. Lesson 14a's decision to use one class for hands and piles pays off here.
  • A Scanner, which we will use to prompt the user after each turn — so the game pauses between turns.
  • Every attribute is private, and every one is a reference to another object. That is composition, five times over.
  • Nothing here is a Card. The Eights object holds collections and players; individual cards live inside them.

Reading a class's attributes tells you what it is responsible for. Eights owns the piles and the players; it does not own a strategy, which is why play lives in Player.

28. reshuffle, and the card you must not lose

Worked example

If the draw pile ever runs out, the discard pile is shuffled (except the top card) and becomes the new draw pile. Four lines, and the order of them is the whole method.

public void reshuffle() {
    Card prev = discardPile.popCard();
    discardPile.dealAll(drawPile);
    discardPile.addCard(prev);
    drawPile.shuffle();
}

public Card drawCard() {
    if (drawPile.isEmpty()) {
        reshuffle();
    }
    return drawPile.popCard();
}
linediscardPiledrawPile
startn cards0
popCard() saves the topn − 10
dealAll(drawPile)0n − 1
addCard(prev)1 — the saved cardn − 1
drawPile.shuffle()1n − 1, shuffled

Save the top card first.

Why: The first line saves the top card from discardPile.

Move everything else across.

Why: The next line transfers the rest of the cards to drawPile.

Put the saved card back.

Why: Then we put the saved card back into discardPile.

Shuffle the new draw pile.

Why: And shuffle drawPile — which Hand can do only because it inherits shuffle from CardCollection.

Verify: Check that after reshuffle the discard pile has exactly one card and it is the one that was on top.

Why: The top card has to stay — it is what the next player must match against. Dealing it away would leave lastCard() reading a card nobody played, so the ordering of these four lines is not a style choice.

29. How many cards are in the discard pile after reshuffle?

Prediction

It had 30; the draw pile had 0.

Card prev = discardPile.popCard();
discardPile.dealAll(drawPile);
discardPile.addCard(prev);
stepdiscardPile
start30
popCard29
dealAll0
addCard(prev)?

Predict first

How many cards does the discard pile hold?

  • 1
  • 0
  • 29
  • 30

Correct: 1

Why: Everything but the saved top card moves to the draw pile, and then the saved card goes back — so the discard pile holds exactly one. That card is what the next player must match, which is why the rule says the discard pile is shuffled except the top card.

30. drawCard and nextPlayer

Concept

Two more pieces, each three or four lines, each hiding one rule from everything that calls it.

public Player nextPlayer(Player current) {
    if (current == one) {
        return two;
    } else {
        return one;
    }
}
methodhidescallers therefore
drawCard()that the pile might need reshufflingjust ask for a card
nextPlayer(current)that there are exactly two playersjust ask who is next
isDone()which hand to checkjust ask if the game is over

nextPlayer takes the current player as a parameter and returns the player who should go next. Note current == one compares references — which is right here, because the question really is is this the same object, not is this an equal player. That is Lesson 11b's identity, being the correct choice for once.

31. Dealing away the top of the discard pile

Trap

The trap

Reshuffling everything, including the card in play.

public void reshuffle() {
    discardPile.dealAll(drawPile);   // the top card goes too
    drawPile.shuffle();
}
afterdiscardPileproblem
dealAll0 cardsnothing to match against
takeTurn calls lastCard()—no last card
the game—breaks

The next takeTurn reads discardPile.lastCard() to find what must be matched — and there is no card to read. **The rule says except the top card for exactly this reason.**

The fix

Save it, move the rest, put it back.

Card prev = discardPile.popCard();
discardPile.dealAll(drawPile);
discardPile.addCard(prev);
drawPile.shuffle();
invariantheld?
the discard pile always has a top cardyes
every other card becomes available againyes
cards are conservedyes

Three lines to preserve one card, and the game depends on all three. This is the kind of detail that reads as fussy until you write the version without it and watch the game fail on the first reshuffle.

32. Top-down or bottom-up?

Definition probe

Two design processes, used in two chapters.

Sort into buckets

Sort each description.

top-down
write pseudocode, then the helpers it names; shuffle, which needed randomInt and swapCards
bottom-up
read the rules, list the methods, then assemble them; Eights, built from isDone, reshuffle, drawCard and nextPlayer
td
You start from the high-level goal and break it into smaller problems, discovering the helper methods as you go.
bu
You identify the simple pieces first, implement them, and then assemble them into more-complex algorithms.

33. Draw a card safely

Fill the middle

The pile might be empty.

Fill in the blanks

public Card drawCard() isEmpty}()) reshuffle}();
}
return drawPile.popCard();
}

Why: Checking before popping means no caller ever has to think about an empty pile — the rule about reshuffling is hidden inside this one method. That is why drawForMatch can call eights.drawCard() in a loop without any special case of its own.

34. Why is bottom-up a good fit here?

Explain it to yourself

Chapter 13 used top-down for shuffle.

Discussion prompt

Why did the rules of Crazy Eights suit bottom-up design, when shuffling suited top-down?

Hint: Where did the list of methods come from in each case?

Answer:

Because the rules are already a list of things that must happen: deal, check for a winner, reshuffle when the pile runs out, take turns. The pieces were given.

Shuffling was the opposite — one goal with no obvious parts, so you had to write the outline first and let it name the helpers.

The result is similar either way: we have a high-level method that calls helper methods. The difference is the development process we used to arrive at this solution. Which one to use depends on whether you know the shape or the parts.

35. The game loop

Section

Section 14.5

36. One turn

Concept

Using these pieces, we can write takeTurn, which executes one player's turn. Five lines, and every one of them calls something written earlier.

public void takeTurn(Player player) {
    Card prev = discardPile.lastCard();
    Card next = player.play(this, prev);
    discardPile.addCard(next);
    System.out.println(player.getName() + " plays " + next);
    System.out.println();
}
linedoesdefined in
discardPile.lastCard()read the card to matchCardCollection
player.play(this, prev)the player choosesPlayer
discardPile.addCard(next)play the cardCardCollection
printlnreport it—

It reads the top card off the discard pile and passes it to player.play, which you saw in the previous section. The result is the card the player chose, which is added to the discard pile. Note lastCard rather than popCard — the card stays on the pile.

37. One turn, end to end

Picture it

Five objects cooperate and each one does only its own job.

Figure (svg): A pipeline showing a turn passing from the discard pile to the player and back

The Eights object never looks at a card's rank or suit, and the Player never touches the draw pile directly. Each asks the other for what it needs — which is what the two-way play(this, prev) call is for.

38. playGame

Worked example

Finally, we use takeTurn and the other methods to write playGame. Every piece from the bottom-up list appears exactly once.

public void playGame() {
    Player player = one;

    // keep playing until there's a winner
    while (!isDone()) {
        displayState();
        takeTurn(player);
        player = nextPlayer(player);
    }

    // display the final score
    one.displayScore();
    two.displayScore();
}
iterationplayerafter the turn
1oneplayer becomes two
2twoplayer becomes one
3one...
lasteitherisDone() is true

Start with player one.

Why: Player player = one;

Loop until somebody wins.

Why: while (!isDone()) — keep playing until there's a winner.

Each iteration is show, play, switch.

Why: displayState, takeTurn, then player = nextPlayer(player).

Then score.

Why: Display the final score with one.displayScore() and two.displayScore().

Verify: Read playGame and check that no line of it mentions a card, a rank, or a suit.

Why: Done! The result of bottom-up design is similar to top-down: we have a high-level method that calls helper methods. Twelve lines, and every one names a piece rather than doing work — which is what makes the loop readable as the rules of the game.

39. When does playGame stop?

Prediction

isDone checks both hands.

public boolean isDone() {
    return one.getHand().isEmpty() || two.getHand().isEmpty();
}
hand onehand twoisDone
3 cards5 cardsfalse
0 cards5 cardstrue

Predict first

What ends the game?

  • Either player running out of cards
  • Both players running out
  • The draw pile running out
  • A fixed number of turns

Correct: Either player running out of cards

Why: The || means one empty hand is enough — as soon as a player has no cards, the game ends, and all other players score penalty points for their remaining cards. The draw pile running out triggers a reshuffle rather than ending anything.

40. Alternating without a counter

Concept

player = nextPlayer(player) is worth a second look — it alternates without any index or modulo arithmetic.

Player player = one;
while (!isDone()) {
    takeTurn(player);
    player = nextPlayer(player);   // one -> two -> one -> two
}
approachfor two playersfor n players
nextPlayer with an ifcleardoes not extend
an index and i = (i + 1) % nworksextends
what Exercise 14.3 asks for—an ArrayList of players

Modify the Eights class to create an ArrayList of players, and modify nextPlayer to select the next player — that is Exercise 14.3, and notice that only nextPlayer and the constructor would change. playGame and takeTurn are already written in terms of the next player, so they need no edit at all.

41. Popping the card you need to match

Trap

The trap

popCard removes; lastCard reads.

public void takeTurn(Player player) {
    Card prev = discardPile.popCard();   // takes it OFF the pile
    Card next = player.play(this, prev);
    discardPile.addCard(next);
}
methodreturns the top card?leaves it there?
lastCard()yesyes
popCard()yesno

The discard pile shrinks every turn while also growing — and the card that was in play vanishes from the game. Cards stop being conserved, and reshuffle eventually finds nothing to move.

The fix

Read it, do not take it.

Card prev = discardPile.lastCard();
Card next = player.play(this, prev);
discardPile.addCard(next);
after a turndiscard pile
beforen cards
aftern + 1 — the played card on top
the old topstill there, underneath

The discard pile only ever grows, until a reshuffle empties it deliberately. That is why CardCollection provides both lastCard and popCard — the distinction between reading and removing is exactly the distinction this method depends on.

42. What does takeTurn add to the discard pile?

Prediction

It reads one card and adds another.

Card prev = discardPile.lastCard();
Card next = player.play(this, prev);
discardPile.addCard(next);
variablewhere it came from
prevthe top of the discard pile, not removed
nextthe player's chosen card

Predict first

Which card is added?

  • next — the card the player chose, which came from their hand or the draw pile
  • prev — the card that was already on top
  • both
  • neither; addCard only reports

Correct: next — the card the player chose, which came from their hand or the draw pile

Why: prev is only read, so it stays where it is; next is what play returned, already removed from the player's hand by popCard or drawn from the pile. The discard pile grows by exactly one card per turn.

43. Who is responsible?

Definition probe

State against strategy.

Sort into buckets

Sort each responsibility.

Eights — the state
holding the draw and discard piles; reshuffling when the draw pile empties
Player — the strategy
choosing which card to play; deciding whether to draw again
eights
Eights encapsulates the state of the game — the piles, the players, and the rules about keeping the game running.
player
Player encapsulates strategy: given the card to match, which card to put down. Exercise 14.1 asks you to subclass it and do better.

44. Could playGame handle four players unchanged?

Counterexample

Exercise 14.3 asks for exactly that.

Discussion prompt

Look at playGame. How much of it would change to support four players, and which method takes the damage?

Hint: Which line mentions a specific player?

Answer:

The loop itself barely changes. It says take a turn, then move to the next player — a sentence that is already true for any number.

The changes land in the constructor (an ArrayList of players instead of two fields), nextPlayer (index arithmetic instead of an if), isDone, and the final scoring.

A well-chosen helper method absorbs a change that would otherwise spread. nextPlayer exists precisely so that playGame never has to know how many players there are — which is why bottom-up design named it as a piece before the loop was written.

45. Class relationships

Section

Section 14.6

46. HAS-A and IS-A

Concept

This chapter demonstrates two common relationships between classes. You have used both; now they have names.

HAS-A — A relationship between two classes in which one class has an instance of another class as one of its attributes.

IS-A — A relationship between two classes in which one class extends another; the subclass is an instance of the superclass.

relationshipdefinitionexample
compositioninstances of one class contain references to instances of anotheran Eights contains two Players, two Hands and a Scanner
inheritanceone class extends another classHand extends CardCollection
HAS-Athe name for compositionEights has a Scanner
IS-Athe name for inheritanceHand is a CardCollection

This vocabulary provides a concise way to talk about an object-oriented design. Two words settle a question that otherwise takes a paragraph — and they are the test Lesson 14a used before they had names.

47. The whole program in one diagram

Picture it

Figure 14.1 shows the classes defined in this chapter and the relationships among them. Two kinds of arrow, and they mean different things.

Figure (svg): A UML diagram of Eights, Player, Hand, Deck, CardCollection and Card with both kinds of relationship arrow

Composition arrows have a standard arrowhead, and inheritance arrows have a hollow triangle head, usually pointing up. UML is an international standard, so almost any software engineer in the world could look at this diagram and understand our design.

48. Reading the relationships aloud

Worked example

Every arrow in the diagram is a sentence. Say each one and check it is true.

Eights   HAS-A  Player       "an Eights has a Player"
Eights   HAS-A  Hand         "an Eights has a draw pile"
Eights   HAS-A  Scanner      "an Eights has a Scanner"
Player   HAS-A  Hand         "a Player has a Hand"
Hand     IS-A   CardCollection   "a Hand is a CardCollection"
Deck     IS-A   CardCollection   "a Deck is a CardCollection"
sentencetrue?so
a Player has a Handyesan instance variable
a Player is a Handnonot extends
a Hand is a CardCollectionyesextends
a Hand has a CardCollectionnoit is one

Write the sentence both ways.

Why: X has a Y and X is a Y — usually only one is true.

The true one names the tool.

Why: has a means an instance variable; is a means extends.

Check every method still makes sense.

Why: If is a is true but some inherited method is nonsense on the subclass, it is not really IS-A.

Draw the arrow accordingly.

Why: Plain head for HAS-A, hollow triangle for IS-A.

Verify: Try an Eights is a Player and a Card has a Hand and notice how obviously wrong they sound.

Why: The sentence test is not a rule of thumb — it is the definition. IS-A means substitutable, which is exactly what Lesson 14a's parameter rule was about, and HAS-A means contained, which is what an instance variable is.

49. HAS-A or IS-A?

Definition probe

Say the sentence out loud.

Sort into buckets

Sort each pair from this chapter.

HAS-A — composition
Eights and Scanner; Player and Hand
IS-A — inheritance
Hand and CardCollection; Deck and CardCollection
has
One class contains a reference to an instance of the other as one of its attributes — an Eights has a Scanner.
is
One class extends the other, so every instance of the subclass is also an instance of the superclass — and can be used wherever it is expected.

50. Everything you have built, in six classes

Concept

Three chapters, six classes, and every one of them holds a decision the others do not need to know.

classrelationship to the othersintroduced in
Cardheld by collectionsChapter 12
CardCollectionthe superclass of Deck and HandChapter 14
DeckIS-A CardCollectionChapter 14
HandIS-A CardCollectionChapter 14
PlayerHAS-A HandChapter 14
EightsHAS-A two Players and two HandsChapter 14

And class diagrams are only one of many graphical representations defined in the UML standard. The diagram is worth drawing before the code as well as after — it is where a wrong relationship shows up cheaply.

51. Drawing the arrows the wrong way round

Trap

The trap

An inheritance arrow pointing from the superclass down.

// wrong reading:
CardCollection ---|> Hand      "a CardCollection is a Hand"

// and a plain arrow used for inheritance:
Hand -----------> CardCollection
arrowmeansreads as
hollow triangle headinheritancethe source IS-A the target
standard arrowheadcompositionthe source HAS-A the target
directionalways subclass to superclassspecific to general

Both errors say something false. Not every CardCollection is a Hand — that is exactly the direction Lesson 14a showed does not work.

The fix

Subclass to superclass, hollow head, usually pointing up.

Hand -----------|> CardCollection    "a Hand IS-A CardCollection"
Player --------->  Hand              "a Player HAS-A Hand"
headdirection
inheritancehollow trianglesubclass to superclass
compositionstandard arrowheadcontainer to contained

Inheritance arrows have a hollow triangle head, usually pointing up — so the general classes sit at the top of the page and the specific ones below. That convention alone makes a diagram readable at a glance.

52. Which arrowhead?

Prediction

Two notations, two meanings.

// Hand extends CardCollection
// Player has a Hand
relationshiparrowhead
inheritancehollow triangle
compositionstandard

Predict first

How is Hand's relationship to CardCollection drawn?

  • An arrow from Hand to CardCollection with a hollow triangle head
  • An arrow from CardCollection to Hand with a hollow triangle head
  • A standard arrow from Hand to CardCollection
  • A dashed line with no head

Correct: An arrow from Hand to CardCollection with a hollow triangle head

Why: Inheritance arrows have a hollow triangle head, usually pointing up, and they run from the subclass to the superclass — the specific pointing at the general. Reversing it would claim every CardCollection is a Hand, which is the direction that does not hold.

53. Match the term to its meaning

Matching

The chapter's vocabulary.

Match the pairs

  • a. inheritance
  • b. subclass
  • c. superclass
  • d. bottom-up design
  • r1. defining a class with the same variables and methods as an existing one
  • r2. a class that extends an existing class
  • r3. an existing class that is extended by another
  • r4. identifying simple pieces first, then assembling them

Why: These are the chapter's four vocabulary entries. Note that bottom-up design sits alongside top-down from Chapter 13 and incremental development from Chapter 4 — three named processes, and you choose by whether you know the shape, the parts, or neither.

54. Why does a shared notation matter?

Real world

UML is an international standard.

Discussion prompt

Everyone could invent their own way of drawing classes. What does a standard notation buy that a clear hand-drawn diagram does not?

Hint: Who is reading it, and did you meet them?

Answer:

Almost any software engineer in the world could look at this diagram and understand our design — with no legend and no explanation from you.

A private notation needs teaching every time. A standard one travels: into a document, a whiteboard, an interview, a codebase you join years later.

The same argument as naming conventions and as toString. Following the shared form costs nothing and means the next reader spends their attention on your design rather than on your notation.

55. The three design processes

Comparison

Fill the blanks.

Comparison matrix

incrementaltop-downbottom-up
start froma tiny working programa high-level outlinethe simple pieces
the example in this bookdistance, in Section 4.10shuffle, in Section 13.2Eights, in Section 14.5
what you discoverthat each step workswhich helper methods you needhow the pieces assemble
the resulta high-level method calling helpersthe samethe same

The last row is the point. The result of bottom-up design is similar to top-down: we have a high-level method that calls helper methods. The difference is the development process we used to arrive at this solution.

56. The pattern to carry away

Pattern

Separate what a thing is from what a game does with it, and use the two relationships accordingly.

// reusable: knows nothing about any game
public class Card { ... }
public class CardCollection { ... }
public class Hand extends CardCollection { ... }   // IS-A

// game-specific: knows the rules
public class Player {
    private Hand hand;                             // HAS-A
    public Card play(Eights eights, Card prev) { ... }
    public static boolean cardMatches(Card c1, Card c2) {
        return c1.getSuit() == c2.getSuit()
            || c1.getRank() == c2.getRank()
            || c1.getRank() == 8;                  // the rule lives HERE
    }
}
questionanswertool
is X a kind of Y?yesextends
does X contain a Y?yesan instance variable
is this a rule of one game?yesa game class, or a subclass
would every game want it?yesthe reusable class

57. Check: relationships

Check

Work it out before you click.

public class Player {
    private String name;
    private Hand hand;
}

public class Hand extends CardCollection { ... }
pairrelationship
Player and Hand?
Hand and CardCollection?

Check your understanding

What are the two relationships?

  • A. Player HAS-A Hand; Hand IS-A CardCollection (correct)
  • B. Player IS-A Hand; Hand HAS-A CardCollection
  • C. Both are HAS-A
  • D. Both are IS-A

Answer: A

Why: A Player contains a Hand as an attribute — composition, or HAS-A. Hand extends CardCollection — inheritance, or IS-A, which also means a Hand can be passed wherever a CardCollection is expected. Saying each sentence aloud settles it: a Player has a Hand is true; a Player is a Hand is not.

Why B tempts people
This reverses both. Lesson 14a's trap was exactly Player extends Hand, which would make every Player usable as a card collection.
Why C tempts people
Hand genuinely extends CardCollection — the extends keyword is in the code.
Why D tempts people
Player has no extends clause; its Hand is an instance variable.

58. Check: reshuffle

Check

Work it out before you click.

public void reshuffle() {
    Card prev = discardPile.popCard();
    discardPile.dealAll(drawPile);
    discardPile.addCard(prev);
    drawPile.shuffle();
}
linepurpose
popCard?
addCard(prev)?

Check your understanding

Why are the first and third lines there?

  • A. To keep the top card on the discard pile, since the next player must match against it (correct)
  • B. To make sure the draw pile has an odd number of cards
  • C. Because dealAll cannot move the last card
  • D. To shuffle the top card separately

Answer: A

Why: The rule says the discard pile is shuffled except the top card — that card is what the next play must match. Without saving it, takeTurn's discardPile.lastCard() would find an empty pile. Three lines to preserve one card, and the game depends on all three.

Why B tempts people
Nothing in the game cares about parity.
Why C tempts people
dealAll moves every card, including the last — that is why the top must be saved first.
Why D tempts people
The saved card is never shuffled; it goes straight back on top.

59. Check: takeTurn

Check

Work it out before you click.

Card prev = discardPile.lastCard();
Card next = player.play(this, prev);
discardPile.addCard(next);
methodremoves the card?
lastCard()no
popCard()yes

Check your understanding

Why lastCard rather than popCard on the first line?

  • A. The card must stay on the pile — it is only being read to see what must be matched (correct)
  • B. popCard would return the wrong card
  • C. lastCard is faster
  • D. popCard is private

Answer: A

Why: prev is the card the player must match, and it belongs on the discard pile with the new card played on top of it. Using popCard would remove it from the game entirely, so cards would stop being conserved and reshuffle would eventually find nothing to move. Providing both methods is exactly so this distinction can be made.

Why B tempts people
Both return the same card; they differ in whether it stays.
Why C tempts people
Any speed difference is irrelevant; correctness is the issue.
Why D tempts people
popCard is public — it is used by searchForMatch and dealAll.

60. Strategy as a separate class

Real world

Design a better strategy for the Player.play method. Write a new class that extends Player and overrides play to implement your strategy.

Discussion prompt

Exercise 14.2 suggests a Genius class that extends Player and plays better. Why is that a better design than adding an if to Player — and where else does the same pattern appear?

Hint: How would you compare two strategies?

Answer:

Because you can then play them against each other: replace one player with a Genius, run a hundred games, and count. An if inside Player gives you one behaviour at a time.

And the game code needs no change at all. A Genius is a Player, so takeTurn(player) works unmodified — which is the substitution rule doing real work rather than being an example.

The pattern appears everywhere a program must choose between interchangeable behaviours — sorting orders, compression methods, AI difficulty levels. Make the varying part a subclass, and the code around it stops needing to know which one it has.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

What is the difference between top-down and bottom-up design?

  • Top-down starts with a high-level outline and discovers the helpers; bottom-up starts with the pieces and assembles them
  • Top-down is for object-oriented programs and bottom-up is for procedural ones
  • Bottom-up produces faster code
  • Top-down requires pseudocode and bottom-up requires UML

Correct: Top-down starts with a high-level outline and discovers the helpers; bottom-up starts with the pieces and assembles them

Why: In top-down design we identify high-level goals, like shuffling a deck, and break them into smaller problems. Bottom-up design goes the other way around: first we identify simple pieces we need and then we assemble them into more-complex algorithms. The finished code looks the same either way — a high-level method that calls helper methods — and the choice depends on whether you know the shape or the parts. Crazy Eights suited bottom-up because the rules already listed the pieces.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate is designing a program and asks whether class A should extend class B or contain one. Give them the test, and one example of each from this chapter.

Hint: Two sentences, and only one of them is true.

Answer:

Say both sentences out loud: A is a B, and A has a B. Usually only one sounds true, and that one names the tool — is a means extends, has a means an instance variable.

A Hand is a CardCollection, so Hand extends it — and that means a Hand can be passed anywhere a CardCollection is expected. A Player has a Hand, so Player just holds one as a field.

One extra check for is a: every inherited method must still make sense on the subclass. If any of them would be nonsense, it is not really IS-A. That check is what stops inheritance being used just to reuse code.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Why do Card, CardCollection, Deck and Hand know nothing about Crazy Eights?

  • So they can be reused for any card game — the rules live in Player and Eights instead
  • Because the rules are too complicated for them
  • Because they were written before the rules were known
  • Because a class cannot contain rules

Correct: So they can be reused for any card game — the rules live in Player and Eights instead

Why: Think Java says it directly: the Deck and Hand classes we have defined so far could be used for any card game, and that's probably a good thing, since it makes it easy to reuse these classes if we want to make another game in the future. It is why cardMatches sits awkwardly in Player as a static method rather than comfortably in Card — and why Exercise 14.4's answer is an EightsCard subclass, which adds the rule without contaminating the general class.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the six-class UML diagram — Eights, Player, CardCollection, Deck, Hand, Card — using hollow triangle heads for IS-A and plain heads for HAS-A, and write each arrow's sentence beside it. Then write playGame's twelve lines and mark, for each call, which class defines it. Finish by writing cardMatches's three clauses next to the three sentences of the rule they encode, and one line on why that method is not in Card.

65. Recap

Recap

Three sections that add the rules, name the design process, and name the relationships.

if you remember one thingit is this
about designthe rules belong outside the reusable classes
about processtop-down and bottom-up meet in the middle
about relationshipssay the sentence aloud; only one is true

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 14 (Extending Classes), Sections 14.4-14.6, pp. 240-247
  2. The Java Tutorials — Overriding and Hiding Methods
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108