# How the Game Clock Manages Tournament Blind Levels in DD Poker

> Discover how the DD Poker game clock manages tournament blind levels. Learn about its pipeline for tracking time, advancing levels, and defining blind amounts to optimize gameplay.

- Repository: [Doug Donohoe/ddpoker](https://github.com/dougdonohoe/ddpoker)
- Tags: internals
- Published: 2026-02-28

---

**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】.

```java
// 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.

```java
// 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】.

```java
// 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】.

```java
// 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`](https://github.com/dougdonohoe/ddpoker/blob/main/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】.