Skip to content

Blackjack

Harshit Aggarwal edited this page Oct 11, 2026 · 5 revisions

Introduction

Blackjack is a minigame where the player places a bet using their inventory gold, draws cards, and tries to finish with a hand value closer to 21 than the dealer without going over. It can be played as a standalone screen from the minigame select menu or as an overlay inside the main game via the shop.

Key Components

  • Blackjack Logic — Owns the deck, hands, balance, betting, and round resolution: Blackjack.
  • Card — A card value made from a suit and rank: Blackjack.Card.
  • Blackjack Config — Lists the card, button, background, sound, and reward assets: BlackjackConfig.
  • Blackjack Display — Builds the card table, controls, result popup overlay, deal animations, sound effects, and gold settlement: BlackjackDisplay, extends UIComponent.
  • Blackjack Actions — Returns the player to the minigame select menu: BlackjackActions, extends Component.
  • Blackjack Screen — Loads the standalone minigame assets, plays background music, and builds its UI entity: BlackjackScreen, extends MinigameScreen.
  • Blackjack Overlay — Shows Blackjack over the paused game with music, a blurred backdrop, and overlay input blocking: BlackjackOverlay.
  • Overlay Actions — Closes the overlay rather than changing screen: BlackjackOverlayActions, extends Component.
  • Blurred Backdrop — Draws the blurred, tinted snapshot behind the overlay: BlurredBackdropDisplay, extends UIComponent.
  • Screen Blur — Captures and downscales the frame the overlay opened on: ScreenBlur.
  • Overlay Input — Blocks gameplay keys while allowing bet text entry and ESC to close: MinigameOverlayInputComponent.
  • Overlay Manager — Ensures only one minigame overlay is open at a time: MinigameOverlayManager.
  • Terminal Command — Launches the standalone Blackjack screen from the debug console: BlackjackMinigameCommand.

Game flow

The player first places a bet using their inventory gold, then starts a round. The display deducts the bet from the player's gold immediately, syncs the balance to the game model, and deals two cards to each hand with sliding deal animations. The dealer's first card remains hidden while the player chooses to hit or stand.

placeBet(amount)
        |
 startNewRound()
        |
  deal two cards each (animated)
        |
   +----+----+
   |         |
  hit      stand
   |         |
 draw     dealer draws until 17
   |
 bust? ----+---- resolve winner
                      |
              settle gold + play sound
                      |
              show result overlay popup

Card totals treat an ace as 11 when possible and as 1 when counting it as 11 would exceed 21. The player wins a resolved round by having the higher total, by making the dealer bust, or by getting a natural blackjack. Equal totals are pushes.

When a round ends, the display settles gold back to the player's inventory: a win pays back double the bet (net profit equal to the bet), a push refunds the original stake, and a loss forfeits the bet. A win also grants the configured inventory reward. A result popup overlay fades in showing the outcome title, message, and a button to start the next round.

UML Diagram

classDiagram
    class Blackjack {
        -List~Card~ deck
        -List~Card~ playerHand
        -List~Card~ dealerHand
        -Random random
        -int balance
        -int bet
        -boolean roundInProgress
        -boolean roundOver
        -boolean playerWon
        -String resultMessage
        +Blackjack(int startingBalance)
        +Blackjack(int startingBalance, Random random)
        +getBalance() int
        +setBalance(int balance)
        +getBet() int
        +placeBet(int amount)
        +startNewRound()
        +hit()
        +stand()
        +calculateHandValue(List~Card~) int
        +getPlayerTotal() int
        +getDealerTotal() int
        +getPlayerHand() List~Card~
        +getDealerHand() List~Card~
        +isRoundInProgress() boolean
        +isRoundOver() boolean
        +isPlayerWinner() boolean
        +getResultMessage() String
    }

    class Card {
        -Suit suit
        -Rank rank
        +Card(Suit suit, Rank rank)
        +getSuit() Suit
        +getRank() Rank
        +toString() String
    }

    class Suit {
        <<enumeration>>
        HEARTS
        DIAMONDS
        CLUBS
        SPADES
    }

    class Rank {
        <<enumeration>>
        -int value
        TWO(2)
        THREE(3)
        FOUR(4)
        FIVE(5)
        SIX(6)
        SEVEN(7)
        EIGHT(8)
        NINE(9)
        TEN(10)
        JACK(10)
        QUEEN(10)
        KING(10)
        ACE(11)
        +getValue() int
    }

    class BlackjackConfig {
        +TEXTURES String[]
        +SOUNDS String[]
        +WIN_REWARD ItemType
        +WIN_REWARD_QUANTITY int
    }

    class BlackjackDisplay {
        -Blackjack blackjack
        -InventoryComponent inventory
        -boolean rewardGranted
        -boolean resultSoundPlayed
        -boolean soundEnabled
        -Consumer~Boolean~ soundToggleCallback
        -Runnable backCallback
        -boolean goldSettled
        -boolean dealInProgress
        -boolean revealDealerCard
        +BlackjackDisplay(Blackjack)
        +BlackjackDisplay(Blackjack, InventoryComponent)
        +BlackjackDisplay(Blackjack, InventoryComponent, Consumer~Boolean~)
        +BlackjackDisplay(Blackjack, InventoryComponent, Consumer~Boolean~, Runnable)
        +create()
        +draw(SpriteBatch)
        +toFront()
        +getZIndex() float
        +dispose()
    }

    class BlackjackActions {
        -GdxGame game
        +BlackjackActions(GdxGame)
        +create()
    }

    class BlackjackScreen {
        +BlackjackScreen(GdxGame)
        +show()
        +dispose()
        #getTextures() String[]
        #getSounds() String[]
        #createUI() Entity
    }

    class BlackjackOverlay {
        -Entity player
        -InventoryComponent inventory
        -MinigameOverlayManager overlayManager
        -Entity overlay
        -boolean openRequested
        -boolean closing
        +BlackjackOverlay()
        +BlackjackOverlay(Entity player)
        +BlackjackOverlay(Entity player, MinigameOverlayManager)
        +request()
        +afterRender()
    }

    class BlackjackOverlayActions {
        -Runnable onClose
        +BlackjackOverlayActions(Runnable onClose)
        +create()
    }

    class BlurredBackdropDisplay {
        -TextureRegion backdrop
        +BlurredBackdropDisplay(TextureRegion)
        +create()
        +toFront()
        +draw(SpriteBatch)
        +dispose()
    }

    class ScreenBlur {
        +capture()$ TextureRegion
    }

    class MinigameOverlayManager {
        +tryOpen() boolean
        +close()
    }

    class MinigameOverlayInputComponent {
        +MinigameOverlayInputComponent(Runnable onClose, boolean allowTyping)
    }

    class BlackjackMinigameCommand {
        -GdxGame game
        +BlackjackMinigameCommand(GdxGame)
        +action(ArrayList~String~) boolean
    }

    Blackjack "1" *-- "*" Card
    Card --> Suit
    Card --> Rank
    BlackjackConfig ..> Blackjack : asset paths
    BlackjackDisplay --> Blackjack
    BlackjackDisplay --> BlackjackConfig
    BlackjackDisplay --> InventoryComponent
    BlackjackScreen --> BlackjackDisplay
    BlackjackScreen --> BlackjackActions
    BlackjackScreen --|> MinigameScreen
    BlackjackOverlay --> BlackjackDisplay
    BlackjackOverlay --> BlackjackOverlayActions
    BlackjackOverlay --> BlurredBackdropDisplay
    BlackjackOverlay --> MinigameOverlayManager
    BlackjackOverlay --> MinigameOverlayInputComponent
    BlackjackOverlay ..> ScreenBlur
    BlackjackMinigameCommand ..> GdxGame
Loading

Usage

Key public methods

Method Purpose
Blackjack(startingBalance) Create a game with a starting chip balance
Blackjack(startingBalance, random) Create a game with a deterministic RNG for testing
placeBet(amount) Set the bet for the next round
startNewRound() Deal two cards to the player and dealer
hit() Deal one more card to the player
stand() Resolve the dealer's turn and the round result
calculateHandValue(cards) Calculate a hand total with ace adjustment
getPlayerHand() Get an unmodifiable copy of the player's cards
getDealerHand() Get an unmodifiable copy of the dealer's cards
getPlayerTotal() Get the player's calculated hand value
getDealerTotal() Get the dealer's calculated hand value
isRoundInProgress() Check whether the player can hit or stand
isRoundOver() Check whether a result is available
isPlayerWinner() Check whether the player won the resolved round
getResultMessage() Get the latest result text
getBalance() Get the current chip balance
setBalance(balance) Synchronise the chip balance with inventory gold
getBet() Get the current bet amount

Example usage

Blackjack game = new Blackjack(100);

game.placeBet(10);
game.startNewRound();

if (game.isRoundInProgress()) {
  if (game.getPlayerTotal() < 17) {
    game.hit();
  } else {
    game.stand();
  }
}

if (game.isPlayerWinner()) {
  // balance increased by the bet amount
}

String result = game.getResultMessage();

Opening Blackjack

Standalone screen. The minigame select menu launches BlackjackScreen, which creates a game with a balance of 100 and adds BlackjackDisplay, BlackjackActions, and InputDecorator to its UI entity. Background music loads, loops at 25% volume, and can be toggled from the in-game sound button.

In-game overlay. The shop's "Play Blackjack" button triggers the "openBlackjack" event, which calls BlackjackOverlay.request(). This pauses entity processing, and on the next afterRender() call, captures and blurs the current frame via ScreenBlur.capture(), loads the Blackjack resources and music, and registers the overlay entity with BlurredBackdropDisplay, BlackjackDisplay (connected to the player's real InventoryComponent), BlackjackOverlayActions, and MinigameOverlayInputComponent (with allowTyping = true so the bet text field works). Closing the overlay stops and unloads the music, schedules entity removal with a short delay to prevent click-through, releases the MinigameOverlayManager, and triggers "openShop" on the player entity to reopen the shop.

Debug terminal. The command blackjackMinigame load switches directly to the standalone MINIGAME_BLACKJACK screen.

Rules and rewards

  • Bets must be greater than zero and cannot exceed the current balance.
  • A round must have a valid bet before it can start.
  • Gold is deducted from the player's inventory when the bet is placed, not when the round ends.
  • A player bust immediately loses the bet.
  • A dealer bust awards the bet to the player (pays back double the deducted gold).
  • A push refunds the original stake to the player's inventory.
  • A player win awards the configured inventory reward once per round.
  • Natural blackjacks are resolved immediately after the initial deal.
  • Blackjack pays even money (1:1), not 3:2.
  • The dealer draws until their total is at least 17, including soft 17.
  • A push changes neither the net balance nor the winner state.
  • The deck is reshuffled at the start of every round.

The configured reward is one ItemType.STANDARD_ARROW:

public static final ItemType WIN_REWARD = ItemType.STANDARD_ARROW;
public static final int WIN_REWARD_QUANTITY = 1;

Assets

Asset File Sprite
Card faces images/minigames/blackjack/{rank}_{suit}.png
Card back images/minigames/blackjack/card_back.png
Background images/minigames/blackjack/god_of_wind_background.png
Background music sounds/minigames/blackjack/blackjack-bgm.mp3
Card deal sound sounds/minigames/blackjack/card-deal.mp3
Win sound sounds/minigames/blackjack/win.mp3
Lose sound sounds/minigames/blackjack/lose.mp3
Controls images/Buttons/{back,hit,stand,newRound}_{up,down}_btn.png

BlackjackConfig.TEXTURES contains 62 paths: 52 card faces, the card back, eight control textures, and the background. The card paths are generated from every Suit and Rank value, so adding a card rank or suit requires no separate card list in the configuration. BlackjackConfig.SOUNDS contains 3 paths for the card deal, win, and lose sound effects. Background music is loaded separately by BlackjackScreen and BlackjackOverlay.

Display features

BlackjackDisplay manages the full Scene2D UI layout including:

  • Card dealing animation — Cards slide from the deck stack to their table position with a fade-in effect. The initial deal animates four cards in sequence (player → dealer → player → dealer) with 0.3s duration and 0.15s gap between cards.
  • Dealer hole card — The dealer's first card renders as the card back while the round is in progress. It is revealed with a fade-in animation after the player stands.
  • Responsive card sizing — Hands with many cards scale down so the total width does not exceed 700 pixels.
  • Result popup overlay — A parchment-style modal with procedurally generated NinePatch background fades in after the deal animations complete. It shows "YOU WIN!", "YOU LOSE!", or "PUSH!" with the result message and a "New Round" button.
  • Bet validation — The bet text field only accepts digits. Validation messages cover empty input, zero bets, bets exceeding the player's gold, zero gold state, and missing inventory.
  • Sound effects — Card deal sounds play during animations. Win or lose sounds play once when the round resolves. A toggle button mutes/unmutes all sounds including background music.
  • Gold display — Shows current gold and active bet, sourced from the player's InventoryComponent when available or from the standalone balance.
  • Button state management — Hit and Stand buttons are disabled when no round is active, during deal animations, or after the round ends. New Round is disabled while a round is in progress.

Testing

BlackjackTest — 18 tests

Covers the pure game logic without a rendering context:

  • Rejects negative starting balance
  • Card accessor methods and toString() formatting
  • Invalid bet values and illegal state transitions
  • Rejects actions after a round ends
  • Score calculation with multiple aces and empty hands
  • Natural blackjack outcomes (player win, dealer win, push)
  • Dealer outcome branches (dealer bust, player win, dealer win, push)
  • Player bust settling as a loss
  • Initial state verification
  • Two-card dealing for a new round
  • Player hit adding a card and bust detection
  • Dealer drawing until at least 17
  • Ace scoring including soft totals
  • Winner state matching the resolved result over 100 seeded rounds
  • Winner state reset between rounds
  • setBalance() updates and rejects negative values
  • Bet validation against resynchronised balance

BlackjackDisplayTest — 13 tests

Guards the Scene2D UI lifecycle and bet validation:

  • UI table creation and bet field initialisation
  • Player gold display from inventory
  • Zero gold state handling
  • Hidden result overlay at startup
  • Construction without inventory
  • Drawing delegation to stage
  • Z-index renders above normal UI
  • Rejected bet when inventory is unavailable
  • Rejected bet when player has zero gold
  • Rejected empty bet
  • Rejected zero bet
  • Rejected bet greater than player gold
  • Gold deduction for a valid bet

BlackjackConfigTest — 5 tests

Guards the resource contract:

  • All 62 configured textures exist on disk
  • Every suit/rank combination has a card sprite
  • Shared card back and button textures are present
  • All 3 sound files exist on disk
  • The configured inventory reward can be added

BlackjackActionsTest — 1 test

  • The "back" event navigates to MINIGAME_SELECT

BlackjackOverlayActionsTest — 2 tests

Covers the overlay close path:

  • The "back" event runs the close callback
  • Nothing runs before the event is triggered

BlackjackMinigameCommandTest — 2 tests

Covers the debug terminal command:

  • blackjackMinigame load switches to MINIGAME_BLACKJACK
  • Invalid arguments are rejected

Clone this wiki locally