Package edu.uw.tcss.model


package edu.uw.tcss.model
Provides the core game logic and data model for a Tetris game.

Overview

This package contains all the classes necessary to run a Tetris game, including game state management, piece movement, collision detection, and event notifications. You are responsible for creating the graphical user interface that interacts with this API.

Getting Started

The main entry point is TetrisGame, which implements the PropertyChangeEnabledGameControls interface. Create an instance, register listeners to receive game events, and call methods to control the game.

Minimal Example (Console):

TetrisGame game = new TetrisGame();

// Listen for game events
game.addPropertyChangeListener(evt -> {
    if (evt.getNewValue() instanceof GameEvent event) {
        System.out.println("Event: " + event.getClass().getSimpleName());
    }
});

// Start the game
game.newGame();  // Fires: GameStateChanged, NextPieceChanged,
                 //        CurrentPieceChanged, FrozenBlocksChanged

// Move and rotate pieces
game.left();
game.rotateCW();
game.down();

// Advance the game by one step (typically called by a timer)
game.step();

Key Classes

TetrisGame
The main game implementation. Create an instance of this class to run a game.
GameControls
Defines the core game operations (newGame, step, move, rotate, etc.) and data structures (Block, Point, IndividualPiece, FrozenBlocks, GameState).
PropertyChangeEnabledGameControls
Extends GameControls with PropertyChangeListener support. Contains comprehensive documentation on the event system and usage patterns.
GameEvent
Sealed interface defining all game events. Each event is a record containing specific game data (state changes, piece updates, score information, etc.).

Event System

The game uses Java's PropertyChangeListener framework with type-safe GameEvent records. When game state changes, listeners receive events containing the updated data.

Available Events

Listening to Events

Example: Handle Game State and Score Updates

game.addPropertyChangeListener(evt -> {
    if (evt.getNewValue() instanceof GameEvent event) {
        switch (event) {
            case GameEvent.GameStateChanged e ->
                System.out.println("State: " + e.newState());

            case GameEvent.CurrentPieceChanged e ->
                System.out.println("Piece at: " +
                    Arrays.toString(e.piece().location()));

            case GameEvent.RowsCleared e ->
                System.out.println("Cleared " + e.count() + " rows!");

            default -> { }
        }
    }
});

Example: Listen to Specific Events Only

// Only receive RowsCleared events
game.addPropertyChangeListener("RowsCleared", evt -> {
    if (evt.getNewValue() instanceof GameEvent.RowsCleared e) {
        int score = calculateScore(e.count());
        System.out.println("Score: " + score);
    }
});

Common Usage Patterns

Starting a Game

game.newGame();  // Required before the game can be played

Game Loop (with Timer)

Timer timer = new Timer(DELAY_MS, e -> game.step());
timer.start();

Handling User Input

// Map keyboard to game controls
addKeyListener(new KeyAdapter() {
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_LEFT -> game.left();
            case KeyEvent.VK_RIGHT -> game.right();
            case KeyEvent.VK_DOWN -> game.down();
            case KeyEvent.VK_UP -> game.rotateCW();
            case KeyEvent.VK_SPACE -> game.drop();
            case KeyEvent.VK_P -> game.togglePause();
        }
    }
});

Pausing and Resuming

game.pause();       // Pause the game
game.unPause();     // Resume the game
game.togglePause(); // Toggle between paused and running

Important Notes

  • Call newGame() first: The game will not respond to controls until newGame() is called.
  • Multiple events per action: Some operations fire multiple events. For example, newGame() fires GameStateChanged, NextPieceChanged, CurrentPieceChanged, FrozenBlocksChanged, and GameStateChanged again (NEW → RUNNING). See individual method documentation for details.
  • Property names are case-sensitive: When registering listeners for specific events, use exact class names: "GameStateChanged", "CurrentPieceChanged", etc. Or call event.getPropertyName() for type safety.
  • Frozen blocks representation: The FrozenBlocks data uses null to represent empty spaces on the board. Check for null when iterating through blocks.
  • Game state restrictions: Most game controls only work when the game is in the RUNNING state. Check GameState before enabling UI controls.

Data Structures

All data structures are defined as nested types in GameControls:

Version:
Autumn 2025
Author:
Charles Bryan
See Also: