Java Sliding Puzzle Game Implementation

Learning Objectives

GUI Application Development

Event Handling

Data Structures and Algorithms

Timer Implementation

Graphic Interface Design

Overview

1. Game Window and Interface Design

We begin by creating a game window class PuzzleFrame that extends JFrame. This class is responsible for displaying the game interface and handling game logic. The interface design utilizes JPanel components to arrange puzzle pieces and buttons, while JLabel components are used to display the game title and background images.

2. Game Data and Logic

The puzzle state is represented by a 2D array gameBoard, where 0 represents the empty space. We define a corresponding victory state array victoryBoard to determine if the game has been won. The core game logic includes moving puzzle pieces, checking for victory conditions, and shuffling the puzzle.

3. User Interaction and Event Handling

User interactions are handled by adding event listeners to buttons and the window. Players can move puzzle pieces by clicking buttons or using keyboard keys. Additional features include displaying helpful messages and resetting the game.

4. Timer and Interface Updates

To enhance gameplay, we implement a timer to track game duration. The timer updates every second and displays the current time in the window title. After each puzzle piece movement, the interface is redrawn to reflect the updated game state.

Key Code Examples

1. Timer Implementation

private Timer gameTimer;
private int elapsedTime;

/**
 * Start the timer and update elapsed time
 */
private void startTimer() {
    // Create timer that triggers actionPerformed method every second
    gameTimer = new Timer(1000, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            elapsedTime++; // Increment seconds each timer trigger
            setTitle("Anime Puzzle - Time: " + elapsedTime + " seconds"); // Update window title with current time
        }
    });
    elapsedTime = 0; // Initialize seconds to 0
    gameTimer.start(); // Start the timer
}

First, we declare a Timer object gameTimer and an integer variable elapsedTime to track seconds. Then, we define a startTimer method to initiate the timer. In this method:

  1. Create a Timer object with a 1000-millisecond trigger interval and an anonymous ActionListener that implements the actionPerformed method.
  2. In the actionPerformed method, increment the seconds count and update the window title with the current elapsed time.
  3. Initialize seconds to 0 and start the timer.

2. 2D Array Manipulation

/**
 * Randomly shuffle the puzzle pieces
 */
private void shufflePuzzle() {
    // Create a Random object for generating random numbers
    Random randomGenerator = new Random();
    
    // Use nested loops to traverse the 2D array
    for (int i = 0; i < gameBoard.length; i++) {
        for (int j = 0; j < gameBoard.length; j++) {
            // Generate two random indices for swapping
            int rowIndex = randomGenerator.nextInt(gameBoard.length);
            int colIndex = randomGenerator.nextInt(gameBoard[rowIndex].length);
            
            // Swap values between current position and random position
            int temporary = gameBoard[i][j];
            gameBoard[i][j] = gameBoard[rowIndex][colIndex];
            gameBoard[rowIndex][colIndex] = temporary;
        }
    }
    
    // Locate the empty space (value 0) in the array
    findEmptyPosition();
}

/**
 * Find and record the position of the empty space
 */
private void findEmptyPosition() {
    exitLoop:
    for (int i = 0; i < gameBoard.length; i++) {
        for (int j = 0; j < gameBoard.length; j++) {
            if (gameBoard[i][j] == 0) {
                emptyRow = i; // Record empty space row index
                emptyCol = j; // Record empty space column index
                break exitLoop; // Exit the loop
            }
        }
    }
}
  1. Create a Random object for generating random numbers.
  2. Use nested for loops to traverse the 2D array and swap values at each position with a randomly selected position.
  3. After shuffling, locate the empty space (value 0) and record its position.

3. Movement Logic

/**
 * Move the empty space upward
 */
private void moveUp() {
    // If empty space is at the top, don't move
    if (emptyRow == 0) {
        return;
    }
    // If empty space is not at the top, move it up
    if (emptyRow > 0) {
        // Swap empty space with the piece above it
        gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow - 1][emptyCol];
        gameBoard[emptyRow - 1][emptyCol] = 0;
        // Update empty space position
        emptyRow--;
    }
    // Check if puzzle is solved
    if (isVictory()) {
        victory();
    }
    // Redraw the game interface
    refreshView();
}

/**
 * Move the empty space downward
 */
private void moveDown() {
    // If empty space is at the bottom, don't move
    if (emptyRow == 3) {
        return;
    }
    // If empty space is not at the bottom, move it down
    if (emptyRow < 3) {
        // Swap empty space with the piece below it
        gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow + 1][emptyCol];
        gameBoard[emptyRow + 1][emptyCol] = 0;
        // Update empty space position
        emptyRow++;
    }
    // Check if puzzle is solved
    if (isVictory()) {
        victory();
    }
    // Redraw the game interface
    refreshView();
}

/**
 * Move the empty space leftward
 */
private void moveLeft() {
    // If empty space is at the left edge, don't move
    if (emptyCol == 0) {
        return;
    }
    // If empty space is not at the left edge, move it left
    if (emptyCol > 0) {
        // Swap empty space with the piece to its left
        gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow][emptyCol - 1];
        gameBoard[emptyRow][emptyCol - 1] = 0;
        // Update empty space position
        emptyCol--;
    }
    // Check if puzzle is solved
    if (isVictory()) {
        victory();
    }
    // Redraw the game interface
    refreshView();
}

/**
 * Move the empty space rightward
 */
private void moveRight() {
    // If empty space is at the right edge, don't move
    if (emptyCol == 3) {
        return;
    }
    // If empty space is not at the right edge, move it right
    if (emptyCol < 3) {
        // Swap empty space with the piece to its right
        gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow][emptyCol + 1];
        gameBoard[emptyRow][emptyCol + 1] = 0;
        // Update empty space position
        emptyCol++;
    }
    // Check if puzzle is solved
    if (isVictory()) {
        victory();
    }
    // Redraw the game interface
    refreshView();
}
  1. moveUp() method: Moves the empty space upward if possible, checks for victory, and refreshes the view.
  2. moveDown() method: Moves the empty space downward if possible, checks for victory, and refreshes the view.
  3. moveLeft() method: Moves the empty space leftward if possible, checks for victory, and refreshes the view.
  4. moveRight() method: Moves the empty space rightward if possible, checks for victory, and refreshes the view.

Complete Code Implementation

1. Main File: PuzzleFrame

package com.puzzle;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.util.Random;

/**
 * Game window class for displaying the game interface and handling game logic
 */
public class PuzzleFrame extends JFrame {
    // Game board array representing puzzle state
    private int[][] gameBoard = {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };

    // Victory state array for comparison
    private int[][] victoryBoard = {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };

    // Row and column of the empty space
    private int emptyRow;
    private int emptyCol;

    // Button and panel components
    private JButton upButton;
    private JButton downButton;
    private JButton leftButton;
    private JButton rightButton;
    private JButton helpButton;
    private JButton resetButton;
    private JPanel gamePanel;

    // Timer and elapsed time
    private Timer gameTimer;
    private int elapsedTime;

    // Time display label
    private JLabel timeLabel;

    /**
     * Constructor to initialize game interface and components
     */
    public PuzzleFrame() {
        initializeFrame();
        shufflePuzzle();
        paintView();
        addButtonListeners();
        addKeyboardListener();

        // Start the timer
        startTimer();

        this.setVisible(true);
        this.setFocusable(true);
    }

    /**
     * Actions to perform when game is won
     */
    private void victory() {
        gameBoard = new int[][]{
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 16}
        };
        // Disable movement buttons
        upButton.setEnabled(false);
        rightButton.setEnabled(false);
        downButton.setEnabled(false);
        leftButton.setEnabled(false);
    }

    /**
     * Check if current game state is victorious
     *
     * @return true if game is won
     */
    public boolean isVictory() {
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard[i].length; j++) {
                if (gameBoard[i][j] != victoryBoard[i][j]) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Redraw the game interface
     */
    private void refreshView() {
        gamePanel.removeAll();
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard[i].length; j++) {
                JLabel imageLabel = new JLabel(new ImageIcon("images/" + gameBoard[i][j] + ".png"));
                imageLabel.setBounds(j * 90, i * 90, 90, 90);
                gamePanel.add(imageLabel);
                gamePanel.repaint();
            }
        }
    }

    /**
     * Add event listeners to buttons
     */
    private void addButtonListeners() {
        upButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                moveUp();
                requestFocusInWindow();
            }
        });
        downButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                moveDown();
                requestFocusInWindow();
            }
        });
        leftButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                moveLeft();
                requestFocusInWindow();
            }
        });
        rightButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                moveRight();
                requestFocusInWindow();
            }
        });
        helpButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(PuzzleFrame.this, "Contact support", "Help", JOptionPane.INFORMATION_MESSAGE);
            }
        });
        resetButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                shufflePuzzle();
                refreshView();
            }
        });
    }

    /**
     * Add keyboard event listener
     */
    private void addKeyboardListener() {
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                switch (keyCode) {
                    case KeyEvent.VK_UP:
                        moveUp();
                        break;
                    case KeyEvent.VK_DOWN:
                        moveDown();
                        break;
                    case KeyEvent.VK_LEFT:
                        moveLeft();
                        break;
                    case KeyEvent.VK_RIGHT:
                        moveRight();
                        break;
                }
            }
        });
    }

    /**
     * Move the empty space upward
     */
    private void moveUp() {
        if (emptyRow == 0) {
            return;
        }
        if (emptyRow > 0) {
            gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow - 1][emptyCol];
            gameBoard[emptyRow - 1][emptyCol] = 0;
            emptyRow--;
        }
        if (isVictory()) {
            victory();
        }
        refreshView();
    }

    /**
     * Move the empty space downward
     */
    private void moveDown() {
        if (emptyRow == 3) {
            return;
        }
        if (emptyRow < 3) {
            gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow + 1][emptyCol];
            gameBoard[emptyRow + 1][emptyCol] = 0;
            emptyRow++;
        }
        if (isVictory()) {
            victory();
        }
        refreshView();
    }

    /**
     * Move the empty space leftward
     */
    private void moveLeft() {
        if (emptyCol == 0) {
            return;
        }
        if (emptyCol > 0) {
            gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow][emptyCol - 1];
            gameBoard[emptyRow][emptyCol - 1] = 0;
            emptyCol--;
        }
        if (isVictory()) {
            victory();
        }
        refreshView();
    }

    /**
     * Move the empty space rightward
     */
    private void moveRight() {
        if (emptyCol == 3) {
            return;
        }
        if (emptyCol < 3) {
            gameBoard[emptyRow][emptyCol] = gameBoard[emptyRow][emptyCol + 1];
            gameBoard[emptyRow][emptyCol + 1] = 0;
            emptyCol++;
        }
        if (isVictory()) {
            victory();
        }
        refreshView();
    }

    /**
     * Randomly shuffle the puzzle
     */
    private void shufflePuzzle() {
        Random randomGenerator = new Random();
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard.length; j++) {
                int rowIndex = randomGenerator.nextInt(gameBoard.length);
                int colIndex = randomGenerator.nextInt(gameBoard[rowIndex].length);
                int temporary = gameBoard[i][j];
                gameBoard[i][j] = gameBoard[rowIndex][colIndex];
                gameBoard[rowIndex][colIndex] = temporary;
            }
        }
        findEmptyPosition();
    }

    /**
     * Find the position of the empty space
     */
    private void findEmptyPosition() {
        exitLoop:
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard.length; j++) {
                if (gameBoard[i][j] == 0) {
                    emptyRow = i;
                    emptyCol = j;
                    break exitLoop;
                }
            }
        }
    }

    /**
     * Draw the game interface
     */
    private void paintView() {
        JLabel title = new JLabel(new ImageIcon("images/title.png"));
        title.setBounds(354, 27, 232, 57);
        this.add(title);

        gamePanel = new JPanel();
        gamePanel.setBounds(150, 114, 360, 360);
        gamePanel.setLayout(null);
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard[i].length; j++) {
                JLabel imageLabel = new JLabel(new ImageIcon("images/" + gameBoard[i][j] + ".png"));
                imageLabel.setBounds(90 * j, 90 * i, 90, 90);
                gamePanel.add(imageLabel);
            }
        }
        this.add(gamePanel);

        JLabel referenceImage = new JLabel(new ImageIcon("images/reference.png"));
        referenceImage.setBounds(574, 114, 122, 121);
        this.add(referenceImage);

        upButton = new JButton(new ImageIcon("images/up.png"));
        downButton = new JButton(new ImageIcon("images/down.png"));
        leftButton = new JButton(new ImageIcon("images/left.png"));
        rightButton = new JButton(new ImageIcon("images/right.png"));
        helpButton = new JButton(new ImageIcon("images/help.png"));
        resetButton = new JButton(new ImageIcon("images/reset.png"));

        upButton.setBounds(732, 265, 57, 57);
        downButton.setBounds(732, 347, 57, 57);
        leftButton.setBounds(650, 347, 57, 57);
        rightButton.setBounds(813, 347, 57, 57);
        helpButton.setBounds(626, 444, 108, 45);
        resetButton.setBounds(786, 444, 108, 45);

        this.add(upButton);
        this.add(downButton);
        this.add(leftButton);
        this.add(rightButton);
        this.add(helpButton);
        this.add(resetButton);

        JLabel background = new JLabel(new ImageIcon("images/background.png"));
        background.setBounds(0, 0, 960, 530);
        this.add(background);
    }

    /**
     * Initialize the game window
     */
    private void initializeFrame() {
        this.setSize(960, 565);
        this.setTitle("Anime Puzzle");
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(null);
    }

    /**
     * Start the timer and update elapsed time
     */
    private void startTimer() {
        gameTimer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                elapsedTime++;
                setTitle("Anime Puzzle - Time: " + elapsedTime + " seconds");
            }
        });
        elapsedTime = 0;
        gameTimer.start();
    }
}

2. Program Entry Point: App

package com.puzzle;

public class App {
    public static void main(String[] args) {
        PuzzleFrame gameWindow = new PuzzleFrame();
    }
}

Tags: java Puzzle Game gui swing Event Handling

Posted on Wed, 12 Aug 2026 16:00:28 +0000 by maxime