State / Behavioral• Patterns: Observer, State, Strategy
Chess Clock
Easy
Problem Summary
Design a dual-timer Chess Clock system supporting classical countdowns, Fischer and Bronstein dynamic move increments, arbiter pause controls, and flag-fall events.
Functional Scope
- Dual independently running player timers (White and Black).
- Atomic turn switching: stopping current clock, awarding increment, and starting opponent clock.
- Support multiple time controls: Simple, Fischer increment, Bronstein delay.
- Pub-sub observer notification on every clock second tick and flag-fall timeout.
- Thread-safe controls for rapid time-scramble button pressing.
Entity-Relationship (ER) Schema
ChessClock [1] <---> [2] PlayerTimer ChessClock [1] <---> [1] ClockState ChessClock [1] --> [1] IncrementStrategy ChessClock [1] --> [*] ClockObserver
Design Approach
Combine the State pattern for active/paused turns with the Strategy pattern for Fischer vs Bronstein move increments. Use high-resolution monotonic timestamps (`System.nanoTime()` / `steady_clock`) to prevent time drift.
Core Classes & Models
ChessClock (Context controller maintaining timer states and referee controls)PlayerTimer (Tracks milliseconds remaining, start times, and increments)ClockState (WhiteTurnState, BlackTurnState, PausedState, FlaggedState)IncrementStrategy (FischerIncrementStrategy, BronsteinDelayStrategy)ClockObserver (Interface for UI and digital board listeners)
Code Blueprint
144 lines/**
* Low-Level Design: Dual Chess Clock
* Design Patterns Applied:
* - Observer Pattern: Real-time tick alerts and flag-fall timeout notifications
* - State Pattern: Clock states (WhiteTurnState, BlackTurnState, PausedState)
* - Strategy Pattern: Increment algorithms (Fischer increment vs Bronstein delay)
*/
import java.util.*;
enum ClockMode { WHITE_TURN, BLACK_TURN, PAUSED, FLAGGED }
// --- [DESIGN PATTERN: Observer] Observer for UI and digital board listeners ---
interface ClockListener {
void onTick(String activePlayer, long remainingMillis);
void onFlagFall(String timedOutPlayer);
}
class ConsoleClockDisplay implements ClockListener {
@Override
public void onTick(String activePlayer, long remainingMillis) {
System.out.println("⏱ [" + activePlayer + "] " + (remainingMillis / 1000) + "s remaining");
}
@Override
public void onFlagFall(String timedOutPlayer) {
System.out.println("🚩 TIME OUT! " + timedOutPlayer + " flagged out!");
}
}
// --- [DESIGN PATTERN: Strategy] Time increment calculation strategy ---
interface IncrementStrategy {
long calculateIncrement(long baseIncrementMillis, long elapsedMoveMillis);
}
class FischerIncrementStrategy implements IncrementStrategy {
@Override
public long calculateIncrement(long baseIncrementMillis, long elapsedMoveMillis) {
// Fischer adds fixed bonus on each completed move
return baseIncrementMillis;
}
}
class BronsteinDelayStrategy implements IncrementStrategy {
@Override
public long calculateIncrement(long baseIncrementMillis, long elapsedMoveMillis) {
// Bronstein adds back elapsed time up to base increment
return Math.min(baseIncrementMillis, elapsedMoveMillis);
}
}
// --- [DESIGN PATTERN: State] State interface managing active clock behavior ---
interface ClockState {
void pressClock(ChessClock context);
void pause(ChessClock context);
}
class WhiteTurnState implements ClockState {
@Override
public void pressClock(ChessClock context) {
context.applyIncrementToWhite();
context.setState(new BlackTurnState());
System.out.println(">> Switch to Black's turn");
}
@Override
public void pause(ChessClock context) {
context.setState(new PausedState(this));
}
}
class BlackTurnState implements ClockState {
@Override
public void pressClock(ChessClock context) {
context.applyIncrementToBlack();
context.setState(new WhiteTurnState());
System.out.println(">> Switch to White's turn");
}
@Override
public void pause(ChessClock context) {
context.setState(new PausedState(this));
}
}
class PausedState implements ClockState {
private final ClockState previousState;
public PausedState(ClockState previousState) { this.previousState = previousState; }
@Override
public void pressClock(ChessClock context) {
System.out.println("Clock is paused. Resume before making move.");
}
@Override
public void pause(ChessClock context) {
context.setState(previousState);
System.out.println(">> Resumed clock");
}
}
class ChessClock {
private ClockState currentState;
private long whiteMillis;
private long blackMillis;
private final long incrementMillis;
private final IncrementStrategy incrementStrategy;
private final List<ClockListener> listeners = new ArrayList<>();
public ChessClock(long initialMinutes, long incrementSeconds, IncrementStrategy strategy) {
this.whiteMillis = initialMinutes * 60 * 1000;
this.blackMillis = initialMinutes * 60 * 1000;
this.incrementMillis = incrementSeconds * 1000;
this.incrementStrategy = strategy;
this.currentState = new WhiteTurnState();
}
public void addListener(ClockListener listener) { listeners.add(listener); }
public void setState(ClockState state) { this.currentState = state; }
public void pressButton() { currentState.pressClock(this); }
public void togglePause() { currentState.pause(this); }
public void applyIncrementToWhite() {
whiteMillis += incrementStrategy.calculateIncrement(incrementMillis, 2000);
notifyTick("White", whiteMillis);
}
public void applyIncrementToBlack() {
blackMillis += incrementStrategy.calculateIncrement(incrementMillis, 2000);
notifyTick("Black", blackMillis);
}
private void notifyTick(String player, long remaining) {
for (ClockListener l : listeners) l.onTick(player, remaining);
}
}
public class Main {
public static void main(String[] args) {
ChessClock clock = new ChessClock(5, 3, new FischerIncrementStrategy());
clock.addListener(new ConsoleClockDisplay());
System.out.println("--- Starting Blitz Match (5 min + 3s increment) ---");
clock.pressButton(); // White moves -> switch to Black
clock.pressButton(); // Black moves -> switch to White
clock.togglePause(); // Pause game
clock.togglePause(); // Resume game
}
}