How the Game Clock Manages Tournament Blind Levels in DD Poker

The DD Poker game clock orchestrates tournament blind progression through a tightly-coupled pipeline where GameClock tracks remaining time, PokerGame advances levels upon expiration, and TournamentProfile defines blind amounts and level durations.

The ddpoker open-source project implements a robust tournament timer system that automatically escalates blinds at scheduled intervals. Understanding how the game clock manages tournament blind levels reveals a coordinated architecture spanning timer logic, game state management, and profile-based configuration.

Tournament Profile Configuration

All blind structures and timing data live inside the TournamentProfile class, which acts as the single source of truth for tournament parameters.

Defining Blind Amounts and Level Durations

The profile stores per-level configurations including small blind, big blind, ante, and duration. Key accessor methods include getSmallBlind(int level), getBigBlind(int level), getAnte(int level), and getMinutes(int level)【5†L35-L46】【5†L114-L124】. The profile also identifies breaks via isBreak(level), returning true when the specified level represents a scheduled pause rather than active play【5†L87-L93】.

// Loading a tournament profile and querying level 5 blinds
TournamentProfile profile = TournamentProfile.loadFromFile("mytourney.prf");
int small = profile.getSmallBlind(5);
int big = profile.getBigBlind(5);
int minutes = profile.getMinutes(5);

Game Clock Architecture

The clock itself extends javax.swing.Timer and serves as the heartbeat of the tournament system.

Timer Foundation and Tick Notifications

GameClock fires an action event every second, decrementing its internal nMillisRemaining_ counter and notifying all registered GameClockListener instances【4†L41-L50】【4†L165-L185】. When the counter reaches zero, isExpired() returns true (specifically when getMillis() == 0)【4†L78-L80】, signaling that blind levels must advance.

// Inside GameClock.actionPerformed – the tick handler
if (e.getID() == ACTION_TICK) {
    long now = System.currentTimeMillis();
    long elapsed = now - nTickBegin_;
    if (elapsed >= getMillis()) {      // time ran out
        setMillis(0);
        stop();                         // fire stop event
    } else {
        setMillis(getMillis() - elapsed);
    }
    nTickBegin_ = now;
    // notify listeners (e.g., DashboardClock) …
}

Level Initialization and Progression

PokerGame encapsulates the state machine that drives level transitions, holding a GameClock instance (clock_) and the current level index (nLevel_).

Starting Tournament Clock Mode

When initTournament() is invoked, the game checks whether it runs in clock mode (timed-blind tournaments). If so, it immediately calls nextLevel() to initialize level 0 and prime the countdown timer【8†L998-L1002】.

Advancing Between Levels

The nextLevel() method updates the minimum-chip index, then delegates to changeLevel(+1)【8†L780-L784】. Inside changeLevel(), the internal nLevel_ increments, and the clock resets using setSecondsRemaining(getSecondsInLevel(nLevel_))【8†L811-L815】. The helper getSecondsInLevel(int level) simply multiplies the profile's minutes by 60【8†L511-L516】.

// Advancing the level when the clock expires
if (game.isLevelExpired()) {           // called by TournamentDirector
    game.nextLevel();                  // increment nLevel_, reset clock,
                                       // fire property-change events
}

Detecting Level Expiration

Game components periodically query the clock status to trigger transitions. Classes like TournamentDirector and PokerNight call game_.isLevelExpired()【7†L1337-L1340】【7†L1432-L1435】 to determine when the current blind period ends. Upon detection, they invoke game_.nextLevel() to load the next level's blinds and reset the countdown.

Clock Advancement During Play

In practice modes or between hands, the game calls advanceClock() (or advanceClockBreak() for breaks) to deduct elapsed time from the remaining counter【8†L330-L339】【8†L445-L452】. This mechanism keeps the tournament clock synchronized with actual hand-by-hand progression rather than relying solely on real-time seconds.

UI Synchronization with DashboardClock

The user interface remains synchronized through the observer pattern. DashboardClock implements GameClockListener, responding to gameClockTicked and gameClockSet callbacks by dispatching updates to the Swing event thread via GuiUtils.invoke(updateTimeRunner_)【6†L215-L224】【6†L242-L250】.

The updateLevel() method retrieves the current level from game_.getLevel(), then queries the TournamentProfile to format the display string. It handles both active levels (showing blinds and antes) and breaks, using profile.isBreak(nLevel) to branch the rendering logic【6†L66-L99】.

// DashboardClock UI update implementation
public void gameClockTicked(GameClock clock) {
    GuiUtils.invoke(updateTimeRunner_);   // Swing thread updates time label
}

private void updateLevel() {
    int nLevel = game_.getLevel();         // current tournament level
    TournamentProfile profile = game_.getProfile();

    if (profile.isBreak(nLevel)) {
        labelBlinds_.setText(PropertyConfig.getMessage(
            "msg.dash.break", profile.getMinutes(nLevel)));
    } else {
        int ante   = profile.getAnte(nLevel);
        int big    = profile.getBigBlind(nLevel);
        int small  = profile.getSmallBlind(nLevel);
        String gt = profile.getGameTypeDisplay(nLevel);
        labelBlinds_.setText(PropertyConfig.getMessage(
            ante == 0 ? "msg.dash.blinds" : "msg.dash.blinds.a",
            small, big, ante == 0 ? null : ante, gt));
    }
}

Summary

  • TournamentProfile stores blind amounts, antes, and level durations, providing getters like getBigBlind(int level) and isBreak(level)【5†L35-L46】【5†L87-L93】.
  • GameClock extends javax.swing.Timer, counting down milliseconds and notifying listeners each tick until isExpired() returns true【4†L41-L50】【4†L78-L80】.
  • PokerGame manages the current level index and transitions via nextLevel() and changeLevel(), resetting the clock using profile-defined seconds【8†L780-L784】【8†L811-L815】.
  • TournamentDirector and similar components poll isLevelExpired() to trigger level advances at the appropriate time【7†L1337-L1340】.
  • DashboardClock listens to clock events and renders current blinds by querying the profile, ensuring the UI reflects the exact tournament state【6†L66-L99】.

Frequently Asked Questions

How does the game clock know when to increase blinds?

The GameClock class tracks remaining milliseconds via nMillisRemaining_. Each second, it checks whether the elapsed time has consumed the remaining allocation. When isExpired() detects that getMillis() == 0【4†L78-L80】, it signals expiration to registered listeners. The TournamentDirector or PokerNight components detect this condition and call nextLevel() to load the next blind structure【7†L1337-L1340】.

What happens when a tournament level expires in DD Poker?

Upon expiration, PokerGame.nextLevel() increments the internal nLevel_ counter and invokes changeLevel(), which resets the clock to the duration defined in TournamentProfile【8†L780-L784】. The system fires property-change events that trigger UI updates and potentially pause play if the new level is a break.

Where are blind amounts and level durations defined?

All tournament parameters reside in TournamentProfile.java. This class provides getSmallBlind(int level), getBigBlind(int level), getAnte(int level), and getMinutes(int level) to retrieve specific values for any given level index【5†L35-L46】【5†L114-L124】. The profile also distinguishes breaks from active play through isBreak(level)【5†L87-L93】.

How does the UI stay synchronized with the tournament clock?

DashboardClock implements the GameClockListener interface and registers with the GameClock. Its gameClockTicked callback executes on every timer tick, dispatching a runnable to the Swing thread that refreshes the time display and blind information【6†L215-L224】. The updateLevel() method formats the display using live data from PokerGame.getLevel() and the associated TournamentProfile【6†L66-L99】.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →