Skip to main content

Overview

The Hand class represents a collection of cards held by either the player or dealer in a Blackjack game. It encapsulates the core Blackjack scoring rules, including flexible Ace valuation (11 or 1) and special conditions like Blackjack and bust detection.

Class Structure

public class Hand {
    private final List<Card> cards;
    
    private Hand(List<Card> cards) {
        this.cards = List.copyOf(cards);
    }
}

Fields

cards
List<Card>
required
Immutable list of cards in the hand. Protected through defensive copying - the internal list cannot be modified from outside the class.

Factory Methods

Create Empty Hand

public static Hand empty() { 
    return new Hand(List.of()); 
}
Creates a new hand with no cards. Used as the starting point when dealing cards. Example:
Hand playerHand = Hand.empty();

Create from Card List

public static Hand fromCards(List<Card> cards) {
    return new Hand(cards);
}
Creates a hand from an existing list of cards. Useful for rehydrating hands from storage. Example:
List<Card> savedCards = Arrays.asList(
    new Card(Rank.ACE, Suit.SPADES),
    new Card(Rank.KING, Suit.HEARTS)
);
Hand restoredHand = Hand.fromCards(savedCards);

Methods

Add Card

Adds a single card to the hand:
public Hand add(Card card) {
    var next = new ArrayList<>(cards);
    next.add(card);
    return new Hand(next);
}
Follows immutable design - returns a new Hand instance with the card added. Example:
Hand hand = Hand.empty();
hand = hand.add(new Card(Rank.TEN, Suit.CLUBS));
hand = hand.add(new Card(Rank.SEVEN, Suit.DIAMONDS));
// hand now contains 2 cards with score of 17

Get Cards

public List<Card> cards() { 
    return List.copyOf(cards); 
}
Returns an immutable copy of the cards in the hand. Safe to use without risk of external modification. Example:
Hand hand = Hand.empty()
    .add(new Card(Rank.QUEEN, Suit.HEARTS))
    .add(new Card(Rank.FIVE, Suit.SPADES));

List<Card> cards = hand.cards();
System.out.println(cards.size()); // 2

Scoring Logic

The heart of the Hand class is its Blackjack scoring algorithm:
public int score() {
    int total = 0;
    int aces = 0;
    
    for (Card c: cards) {
        total += c.rank().getDefaultValue();
        if (c.rank().isAce()) aces++;
    }
    
    while (total > 21 && aces > 0) {
        total -= 10;
        aces--;
    }
    return total;
}

Scoring Rules

  1. Initial Calculation
    • Sum all card values using their default values
    • Count the number of Aces (initially valued at 11)
  2. Ace Adjustment
    • If total exceeds 21 and Aces are present
    • Convert Aces from 11 to 1 (subtract 10 per Ace)
    • Continue until score is ≤ 21 or all Aces are converted
  3. Final Score
    • Return the optimized total

Scoring Examples

Example 1: Simple Hand
Hand hand = Hand.empty()
    .add(new Card(Rank.TEN, Suit.HEARTS))
    .add(new Card(Rank.SEVEN, Suit.CLUBS));

System.out.println(hand.score()); // 17
Example 2: Blackjack (Natural 21)
Hand hand = Hand.empty()
    .add(new Card(Rank.ACE, Suit.SPADES))
    .add(new Card(Rank.KING, Suit.DIAMONDS));

System.out.println(hand.score()); // 21 (Ace as 11 + King as 10)
System.out.println(hand.isBlackjack()); // true
Example 3: Soft Hand (Ace as 11)
Hand hand = Hand.empty()
    .add(new Card(Rank.ACE, Suit.HEARTS))
    .add(new Card(Rank.SIX, Suit.CLUBS));

System.out.println(hand.score()); // 17 (Ace as 11 + 6)
// Called "soft 17" because Ace can still convert to 1 if another card is drawn
Example 4: Ace Adjustment (Ace as 1)
Hand hand = Hand.empty()
    .add(new Card(Rank.ACE, Suit.DIAMONDS))
    .add(new Card(Rank.NINE, Suit.HEARTS))
    .add(new Card(Rank.EIGHT, Suit.SPADES));

System.out.println(hand.score()); // 18
// Initial: 11 + 9 + 8 = 28 (bust!)
// Adjusted: 1 + 9 + 8 = 18 (Ace converted to 1)
Example 5: Multiple Aces
Hand hand = Hand.empty()
    .add(new Card(Rank.ACE, Suit.CLUBS))
    .add(new Card(Rank.ACE, Suit.DIAMONDS))
    .add(new Card(Rank.NINE, Suit.HEARTS));

System.out.println(hand.score()); // 21
// Initial: 11 + 11 + 9 = 31
// After 1st adjustment: 1 + 11 + 9 = 21
// One Ace as 1, one Ace as 11
Example 6: Bust
Hand hand = Hand.empty()
    .add(new Card(Rank.KING, Suit.SPADES))
    .add(new Card(Rank.QUEEN, Suit.HEARTS))
    .add(new Card(Rank.FIVE, Suit.CLUBS));

System.out.println(hand.score()); // 25
System.out.println(hand.isBust()); // true

Special Conditions

Blackjack Detection

public boolean isBlackjack() { 
    return cards.size() == 2 && score() == 21; 
}
Returns true if the hand is a natural Blackjack:
  • Exactly 2 cards
  • Total score of 21
Typical Blackjack combinations:
  • Ace + Ten/Jack/Queen/King
Example:
Hand blackjack = Hand.empty()
    .add(new Card(Rank.ACE, Suit.CLUBS))
    .add(new Card(Rank.JACK, Suit.SPADES));

System.out.println(blackjack.isBlackjack()); // true

Hand not21With2Cards = Hand.empty()
    .add(new Card(Rank.TEN, Suit.HEARTS))
    .add(new Card(Rank.NINE, Suit.DIAMONDS));

System.out.println(not21With2Cards.isBlackjack()); // false (only 19)

Hand threeCardTwentyOne = Hand.empty()
    .add(new Card(Rank.SEVEN, Suit.CLUBS))
    .add(new Card(Rank.SEVEN, Suit.DIAMONDS))
    .add(new Card(Rank.SEVEN, Suit.HEARTS));

System.out.println(threeCardTwentyOne.isBlackjack()); // false (3 cards)

Bust Detection

public boolean isBust() { 
    return score() > 21; 
}
Returns true if the hand total exceeds 21 (after Ace adjustments). Example:
Hand bustHand = Hand.empty()
    .add(new Card(Rank.KING, Suit.HEARTS))
    .add(new Card(Rank.QUEEN, Suit.CLUBS))
    .add(new Card(Rank.TEN, Suit.DIAMONDS));

System.out.println(bustHand.score()); // 30
System.out.println(bustHand.isBust()); // true

Immutability

The Hand class follows immutable design principles:
  • The cards field is final
  • All methods return new instances rather than modifying state
  • Defensive copying prevents external modification

Immutability Example

Hand original = Hand.empty()
    .add(new Card(Rank.FIVE, Suit.HEARTS));

Hand modified = original.add(new Card(Rank.NINE, Suit.CLUBS));

System.out.println(original.cards().size()); // 1 (unchanged)
System.out.println(modified.cards().size()); // 2 (new instance)

Usage in Game Logic

Player Hit

public Game hit() {
    ensureInProgress();
    var draw = deck.draw();
    Hand nextPlayer = playerHand.add(draw.card());
    Game next = new Game(id, playerId, draw.nextDeck(), nextPlayer, dealerHand, status);
    
    if(nextPlayer.isBust()) 
        return next.withStatus(GameStatus.PLAYER_BUST).finalizeOutcome();
    return next;
}

Dealer Play

private Game dealerPlay(){
    Deck d = deck;
    Hand dealer = dealerHand;
    
    while (dealer.score() < 17) {
        var draw = d.draw();
        dealer = dealer.add(draw.card());
        d = draw.nextDeck();
    }
    
    Game g = new Game(id, playerId, d, playerHand, dealer, status);
    if (dealer.isBust()) return g.withStatus(GameStatus.DEALER_BUST);
    return g;
}

Natural Blackjack Check

private Game resolveNaturals() {
    boolean playerBJ = playerHand.isBlackjack();
    boolean dealerBJ = dealerHand.isBlackjack();
    if (playerBJ && dealerBJ) return withStatus(GameStatus.PUSH);
    if (playerBJ) return withStatus(GameStatus.PLAYER_WINS);
    if (dealerBJ) return withStatus(GameStatus.DEALER_WINS);
    return this;
}

Complete Example

// Initial deal
Hand playerHand = Hand.empty()
    .add(new Card(Rank.SEVEN, Suit.HEARTS))
    .add(new Card(Rank.NINE, Suit.CLUBS));

System.out.println("Initial hand: " + playerHand.score()); // 16

// Player decides to hit
playerHand = playerHand.add(new Card(Rank.FOUR, Suit.DIAMONDS));
System.out.println("After hit: " + playerHand.score()); // 20

if (!playerHand.isBust()) {
    System.out.println("Still in the game!");
}

// Check for winning conditions
if (playerHand.score() == 21 && playerHand.isBlackjack()) {
    System.out.println("Blackjack!");
} else if (playerHand.score() == 21) {
    System.out.println("Twenty-one!");
}

// Display all cards
for (Card card : playerHand.cards()) {
    System.out.println(card.rank() + " of " + card.suit());
}
  • Card - Individual playing card in the hand
  • Game - Uses Hand for both player and dealer
  • Rank - Determines card values for scoring (see Card documentation)
  • Deck - Source of cards added to hands

Build docs developers (and LLMs) love