Interface PropertyChangeEnabledGameControls

All Superinterfaces:
GameControls
All Known Implementing Classes:
TetrisGame

public interface PropertyChangeEnabledGameControls extends GameControls
Allows implementing classes to leverage the PropertyChange framework of the Observer Design Pattern to inform interested parties of updates to the Tetris game state.

Defines behaviors allowing PropertyChangeListeners to be added or removed from a GameControls object. Objects from implementing classes should inform added PropertyChangeListeners when methods defined in GameControls mutate the state of the object.

Event System Overview

This interface uses Java's PropertyChangeListener framework combined with modern type-safe GameEvent objects. When game state changes, listeners receive PropertyChangeEvents where the newValue contains a GameEvent record with the actual event data.

Available Game Events

The following events can be observed, each as a sealed GameEvent subtype:

  • GameEvent.GameStateChanged (property name: "GameStateChanged") - Fired when game transitions between NEW, RUNNING, PAUSED, or OVER states
  • GameEvent.NextPieceChanged (property name: "NextPieceChanged") - Fired when a new next piece is selected (typically after current piece freezes)
  • GameEvent.CurrentPieceChanged (property name: "CurrentPieceChanged") - Fired when the current piece moves, rotates, or a new piece spawns
  • GameEvent.FrozenBlocksChanged (property name: "FrozenBlocksChanged") - Fired when a piece freezes into place on the board
  • GameEvent.RowsCleared (property name: "RowsCleared") - Fired when one or more complete rows are cleared from the board

Usage Patterns

Pattern 1: Listen to All Events

Register a listener without specifying a property name to receive all events. Use pattern matching to handle different event types:

TetrisGame game = new TetrisGame();
game.addPropertyChangeListener(evt -> {
    if (evt.getNewValue() instanceof GameEvent event) {
        switch (event) {
            case GameEvent.GameStateChanged e ->
                System.out.println("State: " + e.oldState() + " -> " + e.newState());
            case GameEvent.CurrentPieceChanged e ->
                updateDisplay(e.piece());
            case GameEvent.RowsCleared e ->
                updateScore(e.count());
            case GameEvent.NextPieceChanged e ->
                updateNextPiecePreview(e.piece());
            case GameEvent.FrozenBlocksChanged e ->
                redrawBoard(e.blocks());
        }
    }
});

Pattern 2: Listen to Specific Events

Register a listener with a specific property name to receive only that event type. The property name is the simple class name of the GameEvent subtype:

// Listen only for game state changes
game.addPropertyChangeListener("GameStateChanged", evt -> {
    if (evt.getNewValue() instanceof GameEvent.GameStateChanged e) {
        if (e.newState() == GameControls.GameState.OVER) {
            showGameOverDialog();
        }
    }
});

// Listen only for rows cleared
game.addPropertyChangeListener("RowsCleared", evt -> {
    if (evt.getNewValue() instanceof GameEvent.RowsCleared e) {
        playSound("row_clear.wav");
        addScore(e.count() * 100);
    }
});

Pattern 3: Combined Approach

Use multiple specific listeners for better organization and performance:

// Separate concerns into different listeners
game.addPropertyChangeListener("CurrentPieceChanged", this::handlePieceMovement);
game.addPropertyChangeListener("GameStateChanged", this::handleStateChange);
game.addPropertyChangeListener("RowsCleared", this::handleScoring);

Event Timing and Order

When multiple events are fired from a single action (e.g., drop()), they are fired in a specific order. See individual method documentation in TetrisGame for details on which events are fired and in what order.

Type Safety Benefits

Unlike traditional string-based property changes, this system provides:

  • Compile-time type checking with pattern matching
  • IDE autocomplete for event types and their data fields
  • Exhaustiveness checking in switch expressions
  • Immutable event data via records
  • No casting required when using pattern matching
Version:
Autumn 2025
Author:
Charles Bryan
See Also:
  • Method Details

    • addPropertyChangeListener

      void addPropertyChangeListener(PropertyChangeListener theListener)
      Add a PropertyChangeListener to the listener list. The listener is registered for all properties. The same listener object may be added more than once, and will be called as many times as it is added. If listener is null, no exception is thrown and no action is taken.
      Parameters:
      theListener - The PropertyChangeListener to be added
    • addPropertyChangeListener

      void addPropertyChangeListener(String thePropertyName, PropertyChangeListener theListener)
      Add a PropertyChangeListener for a specific property. The listener will be invoked only when a call on firePropertyChange names that specific property. The same listener object may be added more than once. For each property, the listener will be invoked the number of times it was added for that property. If propertyName or listener is null, no exception is thrown and no action is taken.
      Parameters:
      thePropertyName - The name of the property to listen on.
      theListener - The PropertyChangeListener to be added
    • removePropertyChangeListener

      void removePropertyChangeListener(PropertyChangeListener theListener)
      Remove a PropertyChangeListener from the listener list. This removes a PropertyChangeListener that was registered for all properties. If listener was added more than once to the same event source, it will be notified one less time after being removed. If listener is null, or was never added, no exception is thrown and no action is taken.
      Parameters:
      theListener - The PropertyChangeListener to be removed
    • removePropertyChangeListener

      void removePropertyChangeListener(String thePropertyName, PropertyChangeListener theListener)
      Remove a PropertyChangeListener for a specific property. If listener was added more than once to the same event source for the specified property, it will be notified one less time after being removed. If propertyName is null, no exception is thrown and no action is taken. If listener is null, or was never added for the specified property, no exception is thrown and take no action.
      Parameters:
      thePropertyName - The name of the property that was listened on.
      theListener - The PropertyChangeListener to be removed