Robert Johns | 06 Mar, 2024
Fact checked by Jim Markus

How To Create A Java Chess Game Application for Beginners

Want to know how to build a Java chess game application? In this tutorial, I’ll walk you through this fun and practical Java project step-by-step. 

Whether you’re just starting your Java development journey or are keen to learn Java, a Java chess game application is a fun project for beginners to learn real-world Java skills.

In this Java tutorial, you'll:

  • Design a visually appealing and functional UI using Java Swing
  • Implement core functionalities, including the logic for standard chess moves, player turn switching, and enforcing game rules such as check and checkmate conditions.
  • Dynamically update the chessboard and piece positions in response to player actions.
  • Introduce advanced features like highlighting legal moves for selected pieces, implementing a reset game option, and providing feedback for check and checkmate.
  • Apply final touches and refinements to the game's logic, user interface, and overall design to prepare it for sharing and distribution.

To make the most of this tutorial, it helps to have basic Java skills, including familiarity with basic object-oriented programming principles. 

Some previous experience with Java, such as working with Swing components for building user interfaces and handling events, can be beneficial. However, you don't need to be a Java expert or have prior experience with Java chess game applications.

I’ve also provided the full source code for this Java project so you can follow along, experiment, and even build upon it for your own projects. 

Let’s dive in and start building!

How To Create A Java Chess Game Application

Are you ready to dive into the world of Java development with a hands-on Java project

If so, you're in the right place because we're going to create a chess game using Java. 

This Java project is an excellent starting point, especially if you're passionate about game development or looking to delve into game programming.

At the core of our project, we'll be using Java to implement game logic, manage player interactions, and handle the graphical representation of a chessboard and its pieces. 

Java's versatility makes it a perfect choice for developing interactive games, providing a solid mix of logic, design, and user interaction to bring the classic game of chess to life.

In our chess game, Java will orchestrate the game's backend logic, including the rules governing the movement of pieces, turn management, and the detection of check, checkmate, or stalemate conditions. 

But that's not all. We'll also explore how to create an appealing and intuitive UI with JavaFX, ensuring our chess game is not only functional but also visually engaging.

Take a look at the image below to get an idea of what you’re going to build!

Build your own Java chess game application

Now, you might wonder, "Is this going to be difficult to build?" Not at all! 

I've designed this Java project to be beginner-friendly, breaking it down into manageable, easy-to-follow steps.

So whether you're just starting with Java development or have some experience but are new to game programming, this venture is an excellent way to sharpen your skills.

So, let's gear up, open our IDE, and get ready to create our very own chess game application. 

By the end of this tutorial, you'll not only have a functional chess game to add to your portfolio, but you'll also gain a deeper understanding of Java's capabilities in game development and how to craft interactive, real-time applications.

Let’s get started and build something awesome!

Project Prerequisites

Before we jump into coding our Java chess game, let's outline the skills you'll need to follow along with me in this tutorial. 

And don't worry, you don't need to be a Java expert to get started, but having a few basics under your belt will make this journey smoother and more enjoyable. 

Plus, if you're rusty in any of these areas, you can always brush up with a Java course

Remember, we're also here to help, so don’t hesitate to search hackr.io for help as you go along.

Basic Java Knowledge

You should be comfortable with Java syntax and the fundamentals of object-oriented programming, including classes, objects, methods, and inheritance.

Understanding of Game Programming Concepts

A basic understanding of game development concepts such as game loops, state management, and event handling can be beneficial. 

But again, don’t worry, as, we'll cover the essentials as we progress with our project.

Familiarity with JavaFX 

For crafting the game's interface, some knowledge of JavaFX can be helpful. Nevertheless, this tutorial will provide all of the necessary guidance for beginners to get started.

A Curious and Experimental Mind 

This might be the most crucial prerequisite! 

I really believe that when it comes to coding in Java, the most effective way to learn is through hands-on experience, making errors, and trying again. 

Be prepared to experiment, modify the code, and perhaps even cause a few glitches (which you'll then resolve). 

That's the essence of learning and development!

You could also consider using an AI coding assistant like GitHub Copilot to help out, but I’d recommend waiting until you’re 100% stuck, as this is where you really learn.

Step 1: Setting Up The Project

Alright! Let's kick things off by setting up our Java chess game project. 

This initial step is crucial as it lays the foundation for our entire application, ensuring we have a structured and organized workspace from the get-go.

i. Install Java Development Kit (JDK)

Before anything else, ensure that you have the Java Development Kit (JDK) installed on your computer. 

The JDK is essential for developing and running Java applications. If you haven't installed it yet, visit the Oracle website or search for a JDK version compatible with your system and follow the installation instructions.

ii. Choose and Set Up Your IDE

It’s time to choose an Integrated Development Environment for developing your Java chat application. 

If you’ve read my article on the best Java IDEs, you’ll see that I favor IntelliJ IDEA, Eclipse, and NetBeans.

But I’d also encourage you to check out VSCode if you’re already familiar with that coding environment and you’d like to carry on with what you know. 

Simply head to the VSCode extension marketplace and install the ‘Extension Pack for Java’ from Microsoft, and you’ll be good to go.

iii. Create a New Java Project

Once your IDE is ready, it's time to create a new Java project:

  1. Open your IDE and select the option to create a new project.
  2. Choose a Java project from the list of project types.
  3. Name your project something descriptive, like JavaChessGame.
  4. If prompted, set the JDK version to use for this project.
  5. Finish the setup process, and your IDE will generate the project structure for you.

iv. Organize Your Project Structure

Organize your project structure for better management and scalability. Here's a simple way to structure your Java chat application:

  • src: This directory will contain all your source code files.
  • com.yourname.javachessgame: Replace yourname with your or your organization's name. This will be your base package where your Java files will reside.
  • server: A package for your server-side code.
  • client: A package for your client-side code.
  • lib: If your project requires external libraries, you can place them in this directory.

v. Set Up a Version Control System (Optional but Recommended)

Consider initializing a Git repository in your project folder to manage your source code versions. 

Use the command line or your IDE's built-in Git support to create the repository. This step is highly recommended as it helps in tracking changes and collaborating with others.

vi. Verify Project Setup

To ensure everything is set up correctly, try running a simple "Hello World" Java program in your project environment. 

This test will confirm that your JDK and IDE are correctly configured:

public class HelloWorld {
  public static void main(String[] args) {
      System.out.println("Hello, Java Chess Game!");
  }
}

vii. Ready Your Development Environment

As we move forward with building the Java chess game, keep your IDE open and familiarize yourself with its layout and features. 

You'll be spending a lot of time here, writing code, debugging, and running your application.

And there you have it! You've successfully set up your Java chess game application project. 

With the foundation laid down, we're ready to dive into the exciting parts of building our chess game. 

Let's move on to step 2, where we'll delve into the chess game mechanics essential for programming our game.

Step 2: Understanding Chess Game Mechanics

Now, let's take some time to cover the essential chess game mechanics we need to program our Java chess game. 

As you’d expect, understanding these mechanics is crucial because our Java chess game will depend on them to simulate the rules and strategies of chess accurately within our app. 

Let’s take a close look at the rules governing the movement of pieces, the structure of the chessboard, and how we can model these aspects in Java.

i. Overview of Chess Rules and Piece Movements

Chess is played on an 8x8 square grid known as a chessboard, with each of the two players controlling 16 pieces at the outset: one king, one queen, two rooks, two knights, two bishops, and eight pawns. 

While traditionally one set of pieces is white and the other black, you're welcome to choose any color scheme. 

The ultimate aim is to place the opponent's king under an inescapable threat of capture, known as checkmate. 

Here's a concise rundown of how each piece moves:

  • King: Can move one square in any direction.
  • Queen: Capable of moving any number of squares along a row, column, or diagonal.
  • Rook: Travels any number of squares along a row or column.
  • Bishop: Diagonally moves over any number of squares.
  • Knight: Leaps in an 'L' shape, covering two squares in one direction and then one square perpendicular, or vice versa, able to jump over other pieces.
  • Pawn: Advances forward by one square (or two from its initial position), captures diagonally, and is subject to unique moves like 'en passant' and promotion.

ii. Chessboard Representation

We can represent the chessboard in Java using a two-dimensional array, with each cell corresponding to a square on the chessboard. 

This array can hold objects that represent chess pieces, with each piece knowing its type (king, queen, rook, etc.) and color (white or black).

iii. Implementing Game Logic

The chess game logic involves enforcing the rules for each piece's movement, including special rules like castling (a move involving the king and one of the rooks) and pawn promotion. 

Additionally, the game must detect check, checkmate, and stalemate conditions:

  • Check: The king is threatened but can avoid capture.
  • Checkmate: The king is threatened with no legal moves to evade capture.
  • Stalemate: The player to move is not in check but has no legal moves.

iv. Modeling Pieces and Moves in Java

Each chess piece can be modeled as an object in Java, with properties for its type and color and methods to determine legal moves from a given position on the board. 

This approach allows us to use polymorphism, where each piece type (subclass) implements movement logic specific to its characteristics, adhering to the rules of chess.

By understanding these chess game mechanics and how they can be represented in code, we have laid the initial groundwork for developing our Java chess game. 

Now, without further ado, let’s have some fun and get coding!

Step 3: Designing the Chess Board

Now, let’s get to work and start creating our chessboard.

This will be the foundation for our chess game and it will serve as the central structure for all interactions to take place (game moves).

i. Create New Java File for the Chessboard

In your project directory, create a new file named ChessBoard.java

This file should be located in the src folder if you're using an IDE like Eclipse or IntelliJ IDEA or directly in your project folder if you're using a simple text editor and compiling from the command line.

If you’re choosing to organize your code into packages, the first line in your ChessBoard.java file should declare the package.

For instance, if you named your package com.javachess.game, the first line should be package com.javachess.game;.

If you're not using packages, you can skip this step!

ii. Setting Up the Chessboard Structure

In our Java chess game, the chessboard will be represented as a two-dimensional array. Here's how we’ll initialize it:

public class ChessBoard {
  private Piece[][] board;

  public ChessBoard() {
      this.board = new Piece[8][8]; // 8x8 chessboard
      setupPieces();
  }

  private void setupPieces() {
      // Initial setup will be detailed in subsequent snippets
  }
}

In this code, we’re:

  • We define the ChessBoard class with a 2D array representing the chessboard.
  • The constructor initializes the board and calls setupPieces() to place pieces in their starting positions.

iii. Representing Chess Pieces

Each piece on the chessboard can be represented by a Piece object. So, let’s create a Piece.java file to house this base class. 

Just like before, this file should be located in the src folder, and if you’re organizing your code into packages, the first line in your ChessBoard.java file should declare the package.

We’ll be using this base class for the separate classes we’ll create for each type of piece (e.g., King, Queen, Rook):

public abstract class Piece {
  protected Position position;
  protected PieceColor color;

  public Piece(PieceColor color, Position position) {
      this.color = color;
      this.position = position;
  }

  public PieceColor getColor() {
      return color;
  }

  public Position getPosition() {
      return position;
  }

  public void setPosition(Position position) {
      this.position = position;
  }

  public abstract boolean isValidMove(Position newPosition, Piece[][] board);
}

In this code, we’ve:

  • Defined an abstract Piece class with color and position attributes and an abstract method isValidMove() to determine if a move is legal.
  • Defined a getColor() getter method to return the piece's color, which will be essential for determining which player the piece belongs to.
  • Defined a getPosition() getter to provide access to the piece's current position on the chessboard.
  • Defined a setPosition setter method for updating a piece's position.

This setup allows for polymorphism when moving pieces, making it a great way to get more practice with essential OOP concepts.

Each specific piece class (e.g., Rook, Bishop) will then extend Piece and implement the isValidMove method to check for legal moves according to chess rules.

Note that we’ll need to define PieceColor and Position types, but we’ll handle that shortly!

iv. Initializing Pieces on the Chessboard

Now we have our base Piece class, we can use this to create our set of standard chess pieces with individual classes for each piece type:

private void setupPieces() {
  // Place Rooks
  board[0][0] = new Rook(PieceColor.BLACK, new Position(0, 0));
  board[0][7] = new Rook(PieceColor.BLACK, new Position(0, 7));
  board[7][0] = new Rook(PieceColor.WHITE, new Position(7, 0));
  board[7][7] = new Rook(PieceColor.WHITE, new Position(7, 7));
  // Place Knights
  board[0][1] = new Knight(PieceColor.BLACK, new Position(0, 1));
  board[0][6] = new Knight(PieceColor.BLACK, new Position(0, 6));
  board[7][1] = new Knight(PieceColor.WHITE, new Position(7, 1));
  board[7][6] = new Knight(PieceColor.WHITE, new Position(7, 6));
  // Place Bishops
  board[0][2] = new Bishop(PieceColor.BLACK, new Position(0, 2));
  board[0][5] = new Bishop(PieceColor.BLACK, new Position(0, 5));
  board[7][2] = new Bishop(PieceColor.WHITE, new Position(7, 2));
  board[7][5] = new Bishop(PieceColor.WHITE, new Position(7, 5));
  // Place Queens
  board[0][3] = new Queen(PieceColor.BLACK, new Position(0, 3));
  board[7][3] = new Queen(PieceColor.WHITE, new Position(7, 3));
  // Place Kings
  board[0][4] = new King(PieceColor.BLACK, new Position(0, 4));
  board[7][4] = new King(PieceColor.WHITE, new Position(7, 4));
  // Place Pawns
  for (int i = 0; i < 8; i++) {
      board[1][i] = new Pawn(PieceColor.BLACK, new Position(1, i));
      board[6][i] = new Pawn(PieceColor.WHITE, new Position(6, i));
  }
}

In this code, we’ve:

  • Populated the ChessBoard with all chess pieces in their standard starting positions.
  • Each piece type (Rook, Knight, Bishop, Queen, King, Pawn) is instantiated with the appropriate color and placed on the board.

Note that this code assumes we can access classes for each chess piece type, but you’ve probably noticed that we’ve yet to define these!

Well, don’t worry, we’ll handle that shortly.

v. Handling Moves

To handle moves on the chessboard, you can implement a method that updates the board's state based on the move's start and end positions. 

This involves checking if the move is valid for the selected piece and then moving it if the rules allow it.

public void movePiece(Position start, Position end) {
  // Check if there is a piece at the start position and if the move is valid
  if (board[start.getRow()][start.getColumn()] != null &&
          board[start.getRow()][start.getColumn()].isValidMove(end, board)) {

      // Perform the move: place the piece at the end position
      board[end.getRow()][end.getColumn()] = board[start.getRow()][start.getColumn()];

      // Update the piece's position
      board[end.getRow()][end.getColumn()].setPosition(end);

      // Clear the start position
      board[start.getRow()][start.getColumn()] = null;
  }
}

In this code, we’ve:

  • Defined a movePiece method to move a piece from a start position to an end position, provided the move is valid according to the piece's movement rules.

Congratulations on designing and initializing your chess board. You've now laid the groundwork for the chess game. Great work!

Next, we'll develop the game logic further, including creating specific piece classes that override the isValidMove method and include piece-specific movement logic.

Step 4: Defining Chess Pieces & Supporting Classes

Okay, now let’s focus on defining the various chess piece classes, including PieceColor and Position classes.

You should have noticed in step 3 that we laid the groundwork for this but needed to flesh it out.

i. Creating the PieceColor Enum

First, we'll create a new file called PieceColor.java and use this to define a PieceColor enum to distinguish between the two sets of pieces.

Just like before, this file should be located in the src folder, and if you’re organizing your code into packages, the first line in your ChessBoard.java file should declare the package.

This small piece of code is essential for determining the pieces' movements and capturing logic.

Let’s also opt for the classic black-and-white setup.

public enum PieceColor {
  BLACK, WHITE;
}

In this code, we’ve:

  • Defined a simple enum named PieceColor with two possible values: BLACK and WHITE. This will be used to specify the color of each chess piece.

ii. Defining the Position Class

Next, we need a way to represent positions on the chessboard. 

Let’s create a Position.java file to create the Position class. This will hold row and column values corresponding to a piece's location on the board.

Again, place this file in your src folder, and remember your package declaration if you’re organizing your code into packages.

public class Position {
  private int row;
  private int column;

  public Position(int row, int column) {
      this.row = row;
      this.column = column;
  }

  public int getRow() {
      return row;
  }

  public int getColumn() {
      return column;
  }
}

In this code, we’ve:

  • Introduced the Position class with row and column attributes to represent a piece's location on the chessboard.

iii. Creating Chess Piece Classes

Now, let's start defining the classes for each type of chess piece. Again, each class will need its own Java file, so be sure to create the following files in your src directory:

  • Pawn.java
  • Rook.java
  • Knight.java
  • Bishop.java
  • Queen.java
  • King.java

And remember your package declarations if you’re organizing your code into packages

These will all extend the abstract Piece class and implement specific movements and capture logic for each piece.

public class Pawn extends Piece {
  public Pawn(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int forwardDirection = color == PieceColor.WHITE ? -1 : 1;
      int rowDiff = (newPosition.getRow() - position.getRow()) * forwardDirection;
      int colDiff = newPosition.getColumn() - position.getColumn();

      // Forward move
      if (colDiff == 0 && rowDiff == 1 && board[newPosition.getRow()][newPosition.getColumn()] == null) {
          return true; // Move forward one square
      }

      // Initial two-square move
      boolean isStartingPosition = (color == PieceColor.WHITE && position.getRow() == 6) ||
              (color == PieceColor.BLACK && position.getRow() == 1);
      if (colDiff == 0 && rowDiff == 2 && isStartingPosition
              && board[newPosition.getRow()][newPosition.getColumn()] == null) {
          // Check the square in between for blocking pieces
          int middleRow = position.getRow() + forwardDirection;
          if (board[middleRow][position.getColumn()] == null) {
              return true; // Move forward two squares
          }
      }

      // Diagonal capture
      if (Math.abs(colDiff) == 1 && rowDiff == 1 && board[newPosition.getRow()][newPosition.getColumn()] != null &&
              board[newPosition.getRow()][newPosition.getColumn()].color != this.color) {
          return true; // Capture an opponent's piece
      }

      return false;
  }
}

In this code:

  • The Pawn class first ensures that a direct forward move or an initial two-square forward move is to an unoccupied square, aligning with the pawn's movement rules in chess.
  • For diagonal moves, the Pawn class checks if the destination square is occupied by an opponent's piece, which allows us to capture this piece
  • If the destination square for any move is occupied by a piece of the same color, the move is deemed invalid
public class Rook extends Piece {
  public Rook(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      // Rooks can move vertically or horizontally any number of squares.
      // They cannot jump over pieces.
      if (position.getRow() == newPosition.getRow()) {
          int columnStart = Math.min(position.getColumn(), newPosition.getColumn()) + 1;
          int columnEnd = Math.max(position.getColumn(), newPosition.getColumn());
          for (int column = columnStart; column < columnEnd; column++) {
              if (board[position.getRow()][column] != null) {
                  return false; // There's a piece in the way
              }
          }
      } else if (position.getColumn() == newPosition.getColumn()) {
          int rowStart = Math.min(position.getRow(), newPosition.getRow()) + 1;
          int rowEnd = Math.max(position.getRow(), newPosition.getRow());
          for (int row = rowStart; row < rowEnd; row++) {
              if (board[row][position.getColumn()] != null) {
                  return false; // There's a piece in the way
              }
          }
      } else {
          return false; // Not a valid rook move (not straight line)
      }

      // Check the destination square for capturing
      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (destinationPiece == null) {
          return true; // The destination is empty, move is valid.
      } else if (destinationPiece.getColor() != this.getColor()) {
          return true; // The destination has an opponent's piece, capture is valid.
      }

      return false; // The destination has a piece of the same color, move is invalid.
  }
}

In this code:

  • The Rook class checks for straight-line movement and ensures there are no pieces between the start and end positions. 
  • After validating that the path is clear, we then check if there are any pieces at the destination square, newPosition.
  • If the destination square is empty, the move is valid, but if it’s occupied by an opponent's piece, the move is also valid and we capture the opponent's piece.
  • Naturally, if the destination square is occupied by a piece of the same color, the move is invalid.
public class Knight extends Piece {
  public Knight(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      if (newPosition.equals(this.position)) {
          return false; // Cannot move to the same position
      }

      int rowDiff = Math.abs(this.position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(this.position.getColumn() - newPosition.getColumn());

      // Check for the 'L' shaped move pattern
      boolean isValidLMove = (rowDiff == 2 && colDiff == 1) || (rowDiff == 1 && colDiff == 2);

      if (!isValidLMove) {
          return false; // Not a valid knight move
      }

      // Move is valid if the destination square is empty or contains an opponent's
      // piece
      Piece targetPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (targetPiece == null) {
          return true; // The square is empty, move is valid
      } else {
          return targetPiece.getColor() != this.getColor(); // Can capture if it's an opponent's piece
      }
  }
}

In this code:

  • The isValidMove method ensures we don’t attempt to move to the current position.
  • It then checks if the move fits the knight's unique 'L'-shaped pattern.
  • Knights can jump over other pieces, so there is no need to check the path between the start and end positions.
  • If the move is valid, the method checks the target square at newPosition
  • The move is valid if the square is empty or occupied by an opponent's piece, which we can capture.
  • Of course, we cannot move to a square occupied by our own pieces.
public class Bishop extends Piece {
  public Bishop(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int rowDiff = Math.abs(position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(position.getColumn() - newPosition.getColumn());

      if (rowDiff != colDiff) {
          return false; // Move is not diagonal
      }

      int rowStep = newPosition.getRow() > position.getRow() ? 1 : -1;
      int colStep = newPosition.getColumn() > position.getColumn() ? 1 : -1;
      int steps = rowDiff - 1; // Number of squares to check for obstruction

      // Check for obstructions along the path
      for (int i = 1; i <= steps; i++) {
          if (board[position.getRow() + i * rowStep][position.getColumn() + i * colStep] != null) {
              return false; // There's a piece in the way
          }
      }

      // Check the destination square for capturing or moving to an empty square
      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (destinationPiece == null) {
          return true; // The destination is empty, move is valid.
      } else if (destinationPiece.getColor() != this.getColor()) {
          return true; // The destination has an opponent's piece, capture is valid.
      }

      return false; // The destination has a piece of the same color, move is invalid.
  }
}

In this code:

  • The Bishop class checks for diagonal movement and ensures there are no pieces between the start and end positions. 
  • After ensuring no obstructions, we check for a piece at the destination square, newPosition.
  • If the destination is empty, the Bishop can legally move there.
  • If the destination square is occupied by an opponent's piece, we can capture it.
  • If the destination is occupied by a piece of the same color, the move is invalid.
public class Queen extends Piece {
  public Queen(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      // Check if the new position is the same as the current position
      if (newPosition.equals(this.position)) {
          return false;
      }

      int rowDiff = Math.abs(newPosition.getRow() - this.position.getRow());
      int colDiff = Math.abs(newPosition.getColumn() - this.position.getColumn());

      // Check for straight line movement
      boolean straightLine = this.position.getRow() == newPosition.getRow()
              || this.position.getColumn() == newPosition.getColumn();

      // Check for diagonal movement
      boolean diagonal = rowDiff == colDiff;

      if (!straightLine && !diagonal) {
          return false; // The move is neither straight nor diagonal
      }

      // Calculate direction of movement
      int rowDirection = Integer.compare(newPosition.getRow(), this.position.getRow());
      int colDirection = Integer.compare(newPosition.getColumn(), this.position.getColumn());

      // Check for any pieces in the path
      int currentRow = this.position.getRow() + rowDirection;
      int currentCol = this.position.getColumn() + colDirection;
      while (currentRow != newPosition.getRow() || currentCol != newPosition.getColumn()) {
          if (board[currentRow][currentCol] != null) {
              return false; // Path is blocked
          }
          currentRow += rowDirection;
          currentCol += colDirection;
      }

      // The move is valid if the destination is empty or contains an opponent's piece
      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      return destinationPiece == null || destinationPiece.getColor() != this.getColor();
  }
}

In this code:

  • The Queen class ensures the move adheres to legal movement patterns, either straight along rows and columns or diagonally across the board.
  • It then checks each square along the proposed path to the newPosition, which must be clear of any other pieces. Remember, only Knights can jump over other pieces.
  • If the destination square from a valid path is occupied by an opponent’s piece, we can capture this
  • If the destination is occupied by our own pieces, the move is invalid
public class King extends Piece {
  public King(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int rowDiff = Math.abs(position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(position.getColumn() - newPosition.getColumn());

      // Kings can move one square in any direction.
      boolean isOneSquareMove = rowDiff <= 1 && colDiff <= 1 && !(rowDiff == 0 && colDiff == 0);

      if (!isOneSquareMove) {
          return false; // Move is not within one square.
      }

      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      // The move is valid if the destination is empty or contains an opponent's
      // piece.
      return destinationPiece == null || destinationPiece.getColor() != this.getColor();
  }
}

In this code:

  • The King class first checks the move is within the legal range, which is one square in any direction. 
  • This means there's no need to check for pieces along the King’s path.
  • If an opponent's piece occupies a destination square, we capture it. 
  • if the destination square is occupied by our own pieces, the move is invalid.

Wow, that was a major step! Nice work!

With the chess pieces defined, we can now proceed to implement the game logic, handling moves, game states (like check and checkmate), and player interactions. 

We’ve set the stage for a fully functional chess game where players can make moves, capture pieces, and aim for a checkmate.

Let’s dive into the next steps!

Step 5: Developing Game Logic

We’re making great progress. We have our chessboard and pieces set up, so the next step is to develop the core game logic to allow players to move pieces.

This requires respecting chess rules, allowing players to capture opponent pieces, and checking for game-ending conditions like checkmate. 

Let's get started!

i. Create a New Java File for the Game Logic

In your project directory, create a new file named ChessGame.java

Be sure to place this file in your src folder, and remember your package declaration if you’re organizing your code into packages.

This is the same process we’ve covered for every Java file, so you should be familiar with it now.

ii. Initializing the Game State

We’ll use the ChessGame class to initialize the game by setting up a new ChessBoard.

This also means we’ll set up the chessboard with pieces in their starting positions.

public class ChessGame {
  private ChessBoard board;
  private boolean whiteTurn = true;

  public ChessGame() {
      this.board = new ChessBoard();
  }
}

In this code, we've:

  • Created a ChessGame class to manage the overall game.
  • Initialized the chessboard within the ChessGame constructor, setting up pieces in their starting positions.
  • Set whiteTurn to true, indicating that the White pieces will make the first move, adhering to standard chess rules.

iii. Handling Moves

We’ll define a makeMove method to allow players to move pieces on the chessboard.

This works by checking for the piece's existence at the start position and ensuring it's the correct player's turn.

public boolean makeMove(Position start, Position end) {
  Piece movingPiece = board.getPiece(start.getRow(), start.getColumn());
  if (movingPiece == null || movingPiece.getColor() != (whiteTurn ? PieceColor.WHITE : PieceColor.BLACK)) {
      return false; // No piece at the start position or not the player's turn
  }

  if (movingPiece.isValidMove(end, board.getBoard())) {
      // Execute the move
      board.movePiece(start, end);
      whiteTurn = !whiteTurn; // Switch turns
      return true;
  }
  return false;
}

In this code, we've:

  • Checked for the presence of a piece at the specified start position.
  • Verified it's the correct player's turn based on the piece's color.
  • Used the PIece class’ isValidMove method to determine if the move is legal.
  • Moved the piece and switched turns if the move is valid.

iv. Checking for Check

To determine if a player's king is in check, we can iterate over all pieces on the board to see if any can capture the king.

public boolean isInCheck(PieceColor kingColor) {
      Position kingPosition = findKingPosition(kingColor);
      for (int row = 0; row < board.getBoard().length; row++) {
          for (int col = 0; col < board.getBoard()[row].length; col++) {
              Piece piece = board.getPiece(row, col);
              if (piece != null && piece.getColor() != kingColor) {
                  if (piece.isValidMove(kingPosition, board.getBoard())) {
                      return true; // An opposing piece can capture the king
                  }
              }
          }
      }
      return false;

}

In this code, we've:

  • Located the king's position on the board for the specified color.
  • Checked each piece on the board to see if it could move to the king's position, indicating the king was in check.

v. Find The King’s Position

Before we can check if a king is in check, we need to know exactly where the king is located on the chessboard. 

We’ll do this by iterating over every square on the board and identifying the king's position based on its color:

private Position findKingPosition(PieceColor color) {
      for (int row = 0; row < board.getBoard().length; row++) {
          for (int col = 0; col < board.getBoard()[row].length; col++) {
              Piece piece = board.getPiece(row, col);
              if (piece instanceof King && piece.getColor() == color) {
                  return new Position(row, col);
              }
          }
      }
      throw new RuntimeException("King not found, which should never happen.");

}

In this code, we've:

  • Iterated through each square on the board by navigating the rows and columns.
  • Checked for a piece on each square and verified whether it is a King and if its color matches our search parameter.
  • Returned the position (row, col) of the king once it's found.
  • Included a fail-safe RuntimeException to handle the unexpected scenario where a king of the specified color is not found on the board, which should never happen!

vi. Checking for Checkmate

After verifying the king is in check, we can assess whether the king can make any move to escape this dire situation. 

The process requires us to consider each potential move the king can make within its one-square radius of movement. 

If any of these results in the king not being in check, it's not a checkmate situation.

As you’d expect, this is a very important piece of our code:

public boolean isCheckmate(PieceColor kingColor) {
      if (!isInCheck(kingColor)) {
          return false; // Not in check, so cannot be checkmate
      }

      Position kingPosition = findKingPosition(kingColor);
      King king = (King) board.getPiece(kingPosition.getRow(), kingPosition.getColumn());

      // Attempt to find a move that gets the king out of check
      for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
          for (int colOffset = -1; colOffset <= 1; colOffset++) {
              if (rowOffset == 0 && colOffset == 0) {
                  continue; // Skip the current position of the king
              }
              Position newPosition = new Position(kingPosition.getRow() + rowOffset,
                      kingPosition.getColumn() + colOffset);
              // Check if moving the king to the new position is a legal move and does not
              // result in a check
              if (isPositionOnBoard(newPosition) && king.isValidMove(newPosition, board.getBoard())
                      && !wouldBeInCheckAfterMove(kingColor, kingPosition, newPosition)) {
                  return false; // Found a move that gets the king out of check, so it's not checkmate
              }
          }
      }

      return true; // No legal moves available to escape check, so it's checkmate

}

In this code, we've:

  • Ensured the king is in check; if not, it's impossible to be in checkmate.
  • Located the king's position on the board.
  • Iterated over all possible one-square moves the king can make from its current position.
  • Skipped the iteration where the offset would keep the king in its original position.
  • Checked each potential move to determine if it was legal and whether it resulted in the king not being in check.
  • If no such move exists, the king has no legal moves to escape check, and the situation is a checkmate.

vii. Validating Board Position

Before any piece is moved, we need to verify that the intended destination is a valid square on the chessboard. 

This simple method checks that the row and column indices of the desired position fall within the acceptable range of the board's dimensions.

private boolean isPositionOnBoard(Position position) {
      return position.getRow() >= 0 && position.getRow() < board.getBoard().length &&
              position.getColumn() >= 0 && position.getColumn() < board.getBoard()[0].length;

}

In this code, we've:

  • Checked the row index of the position is within the bounds of the chessboard, which means it's non-negative and less than the total number of rows.
  • Done the same to verify the column is within the legal range.

viii. Simulating Moves For Check Position

This is an interesting aspect of our game.

Before executing a move on the board, we should also check whether it would result in the player's king being in check. 

This pre-validation step is essential for ensuring the legality of moves according to chess rules.

private boolean wouldBeInCheckAfterMove(PieceColor kingColor, Position from, Position to) {
      // Simulate the move temporarily
      Piece temp = board.getPiece(to.getRow(), to.getColumn());
      board.setPiece(to.getRow(), to.getColumn(), board.getPiece(from.getRow(), from.getColumn()));
      board.setPiece(from.getRow(), from.getColumn(), null);

      boolean inCheck = isInCheck(kingColor);

      // Undo the move
      board.setPiece(from.getRow(), from.getColumn(), board.getPiece(to.getRow(), to.getColumn()));
      board.setPiece(to.getRow(), to.getColumn(), temp);

      return inCheck;

}

In this code, we've:

  • Temporarily executed the proposed move by placing the moving piece at the 'to' position and clearing the 'from' position.
  • Checked whether this move would result in the player's king being in check.
  • Reversed the temporary move to restore the board to its original state.
  • Returned a boolean indicating whether the king would be in check after the move. If true, the move would illegally put or leave the king in check and it should not be allowed.

ix. Chessboard Enhancements

To round things out, we need to make some enhancements to our ChessBoard class to accommodate some of the methods we’ve been calling in our ChessGame class.

These are fairly simple setters and getters that allow us to neatly return important information to the ChessGame.

The getBoard method provides a way to access the current state of the chessboard, which is essential for various game logic evaluations.

To interact with pieces on the chessboard, we need to retrieve them based on their position. The getPiece method does this by returning the piece located at a specified row and column.

Moving or placing a piece on the chessboard will require us to update the board's state. 

The setPiece method allows this by placing a piece at the specified location and, if the piece is not null, updating the piece's position to reflect its new location.

public Piece[][] getBoard() {
  return board;
}

public Piece getPiece(int row, int column) {
  return board[row][column];
}

public void setPiece(int row, int column, Piece piece) {
  board[row][column] = piece;
  if (piece != null) {
      piece.setPosition(new Position(row, column));
  }
}

Congratulations! We just wrote a lot of code!

We've now successfully established the core logic for our Java chess game, incorporating piece movements, checks, and checkmate validations. 

Next, we'll delve into creating a user interface using JavaFX to bring our chess game to life, enhancing player interaction and visual appeal. Keep up the fantastic work!

Step 6: Creating the GUI with Java Swing

Our chess game app is really coming together, but how about we breathe some life into it by adding a GUI with Java Swing?

If you followed our project setup steps and installed the Java Development Kit (JDK), you should also have Swing bundled with it, which means we’re good to go!

i. Creating the Chess Game GUI

Firstly, let’s create a new Java file, ChessGameGUI.java, in your project's source folder. 

This class will extend JFrame to create the application window and contain the main method to run the game.

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;

public class ChessGameGUI extends JFrame {
  private final ChessSquareComponent[][] squares = new ChessSquareComponent[8][8];
  private final ChessGame game = new ChessGame();

  private final Map<Class<? extends Piece>, String> pieceUnicodeMap = new HashMap<>() {
      {
          put(Pawn.class, "\u265F");
          put(Rook.class, "\u265C");
          put(Knight.class, "\u265E");
          put(Bishop.class, "\u265D");
          put(Queen.class, "\u265B");
          put(King.class, "\u265A");
      }
  };

  public ChessGameGUI() {
      setTitle("Chess Game");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLayout(new GridLayout(8, 8));
      initializeBoard();
      pack(); // Adjust window size to fit the chessboard
      setVisible(true);
  }

  private void initializeBoard() {
  }

  private void refreshBoard() {
  }

   private void handleSquareClick(int row, int col) {
  }

  private void checkGameState() {
  }

  public static void main(String[] args) {
      SwingUtilities.invokeLater(ChessGameGUI::new);
  }
}

In this code, we've:

  • Extended JFrame to create the main window of our chess game application, setting essential properties like the title and close operation.
  • Defined a 2D array of ChessSquareComponent to represent each square on the chessboard.
  • Initialized a ChessGame instance to manage the game logic.
  • Created a map to associate chess piece classes with their Unicode symbols, allowing us to visually represent chess pieces on the GUI.
  • In the constructor, we set the layout of our frame to a 8x8 GridLayout, representing the chessboard, and called initializeBoard to set up the board's squares.
  • The main method uses SwingUtilities.invokeLater to ensure that the GUI is created and updated on the Event Dispatch Thread (EDT).

You’ll also notice that we’ve added methods to initialize the board, refresh the board, and handle events when players click on squares.

Let’s set about defining these now.

ii. Adding Chess Pieces to the Board

To visually represent the game, it's essential to add chess pieces to the board. 

This involves initializing each square with a ChessSquareComponent and equipping it with a mouse listener to handle clicks. 

After the initialization, the refreshBoard method updates each square with the appropriate piece symbol or clears it if the square is empty.

private void initializeBoard() {
  for (int row = 0; row < squares.length; row++) {
      for (int col = 0; col < squares[row].length; col++) {
          final int finalRow = row;
          final int finalCol = col;
          ChessSquareComponent square = new ChessSquareComponent(row, col);
          square.addMouseListener(new MouseAdapter() {
              @Override
              public void mouseClicked(MouseEvent e) {
                  handleSquareClick(finalRow, finalCol);
              }
          });
          add(square);
          squares[row][col] = square;
      }
  }
  refreshBoard();
}

In this code, we've:

  • Initialized the chessboard with ChessSquareComponent for each square.
  • Assigned a MouseAdapter to handle mouse clicks, linking user interaction to the game logic.
  • Called refreshBoard at the end to populate the board with pieces initially.
private void refreshBoard() {
  ChessBoard board = game.getBoard();
  for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
          Piece piece = board.getPiece(row, col); // Assuming ChessBoard has a getPiece method
          if (piece != null) {
              // If using Unicode symbols:
              String symbol = pieceUnicodeMap.get(piece.getClass());
              Color color = (piece.getColor() == PieceColor.WHITE) ? Color.WHITE : Color.BLACK;
              squares[row][col].setPieceSymbol(symbol, color);
              // Or, if updating with icons or any other graphical representation
          } else {
              squares[row][col].clearPieceSymbol(); // Ensure this method clears the square
          }
      }
  }
}

In this code, we've:

  • Iterated over each square of the chessboard to check for the presence of a chess piece.
  • Used a map to convert the piece's class into a Unicode symbol, applying color based on the piece's side.
  • Updated each square with the appropriate symbol for the piece it holds or cleared the symbol if the square is empty.

iii. Handling User Interactions with Chess Squares

To interact with the chess game, users click on squares to select a piece to move or to choose a destination for the selected piece. 

The handleSquareClick method bridges user actions with the game's logic, determining if a move or selection has been made and subsequently updating the board.

private void handleSquareClick(int row, int col) {
      if (game.handleSquareSelection(row, col)) {
          refreshBoard();
          checkGameState();
      }

}

In this code, we've:

  • Called handleSquareSelection within the ChessGame class, which either selects a piece or moves a selected piece based on the square clicked.
  • Refreshed the board to reflect any changes resulting from the user's action, such as moving a piece.
  • Checked the current game state after the move to provide immediate feedback to the player.

iv. Evaluating and Displaying Game State

After each move, it's crucial to assess the game state to notify players of special conditions like check or checkmate. 

The checkGameState function accomplishes this by verifying if the current player is in check or checkmate and then communicates these states through dialog messages.

private void checkGameState() {
  PieceColor currentPlayer = game.getCurrentPlayerColor(); // This method should return the current player's color
  boolean inCheck = game.isInCheck(currentPlayer);

  if (inCheck) {
      JOptionPane.showMessageDialog(this, currentPlayer + " is in check!");
  }
}

In this code, we've:

  • Determined the current player's color to check for specific game states relevant to them.
  • Checked if the current player is in check and, if so, displayed an alert.

v. Designing and Initializing Chess Squares

To visually represent the chessboard, each square is implemented as a customized button using the ChessSquareComponent class. 

This class not only differentiates squares by color for a checkerboard pattern but also manages the display of chess piece symbols.

import javax.swing.*;
import java.awt.*;

public class ChessSquareComponent extends JButton {
  private int row;
  private int col;

  public ChessSquareComponent(int row, int col) {
      this.row = row;
      this.col = col;
      initButton();
  }

  private void initButton() {
      // Set preferred button size for uniformity
      setPreferredSize(new Dimension(64, 64));

      // Set background color based on row and col for checkerboard effect
      if ((row + col) % 2 == 0) {
          setBackground(Color.LIGHT_GRAY);
      } else {
          setBackground(new Color(205, 133, 63));
      }

      // Ensure text (chess piece symbols) is centered
      setHorizontalAlignment(SwingConstants.CENTER);
      setVerticalAlignment(SwingConstants.CENTER);

      // Font settings can be adjusted for visual enhancement
      setFont(new Font("Serif", Font.BOLD, 36));
  }

  public void setPieceSymbol(String symbol, Color color) {
      this.setText(symbol);
      this.setForeground(color); // Adjust for better visibility against background
  }

  public void clearPieceSymbol() {
      this.setText("");
  }
}

In this code, we've:

  • Defined a constructor for ChessSquareComponent that initializes each square with its row and column positions.
  • Implemented initButton to set the square's size, background color (alternating for a checkerboard pattern), alignment of text, and font settings to optimally display the chess piece symbols.
  • Defined setPieceSymbol to display a chess piece's symbol on the square, with appropriate text color for visibility.
  • Defined clearPieceSymbol to remove any symbols from the square, useful for updating the board after moves.

vi. Enhancing Game Logic and Interaction

To provide a comprehensive user experience, we’ve added methods to the ChessGame class that manage the game state.

This includes resetting the game, determining the current player's color, and handling square selections for moving pieces

public ChessBoard getBoard() {
  return this.board;
}

public void resetGame() {
  this.board = new ChessBoard(); // Re-initialize the board
  this.whiteTurn = true; // Reset turn to white
}

public PieceColor getCurrentPlayerColor() {
  return whiteTurn ? PieceColor.WHITE : PieceColor.BLACK;
}

In this code, we’ve:

  • Added a getBoard method to provide access to the current board state, essential for the GUI to display the board.
  • Introduced resetGame to allow restarting the game, resetting the board to its initial state, and giving the turn back to White.
  • Implemented getCurrentPlayerColor to easily determine whose turn it is, simplifying turn-based logic.
private Position selectedPosition; // Tracks the currently selected piece's position

public boolean isPieceSelected() {
  return selectedPosition != null;
}

public boolean handleSquareSelection(int row, int col) {
  if (selectedPosition == null) {
      // Attempt to select a piece
      Piece selectedPiece = board.getPiece(row, col);
      if (selectedPiece != null
              && selectedPiece.getColor() == (whiteTurn ? PieceColor.WHITE : PieceColor.BLACK)) {
          selectedPosition = new Position(row, col);
          return false; // Indicate a piece was selected but not moved
      }
  } else {
      // Attempt to move the selected piece
      boolean moveMade = makeMove(selectedPosition, new Position(row, col));
      selectedPosition = null; // Reset selection regardless of move success
      return moveMade; // Return true if a move was successfully made
  }
  return false; // Return false if no piece was selected or move was not made
}

In this code, we've:

  • Added the isPieceSelected to check whether a player has selected their piece.
  • Developed the handleSquareSelection method for interactive gameplay by checking if a piece is currently selected and if not, attempting to select a piece. 
  • If a piece is selected, this method tries to move the selected piece to the new position.

Nice work! We've now successfully added a graphical user interface using Swing to our chess game that dynamically represents the game state.

We’ve also offered players an intuitive and engaging way to play chess right from their screens.

At this stage, we've laid the groundwork for player interactions within the GUI, and we've prepared methods to check the game state for check and checkmate conditions. 

Up next, we'll focus on refining player interactions and adding final touches to our chess game.

This will include additional functionalities to enhance the user experience, like providing visual feedback for valid moves and handling game resets. 

Keep up the excellent work, and let’s get after it!

Step 7: Handling Player Interactions

Our app looks great, so how about we add our finishing touches to get this chess game ready to be played?

Let’s turn our focus to enhancing player interactions with the game through the Swing GUI. 

We’ll provide clear visual feedback for legal moves, improve the game reset functionality, and ensure that players can easily understand the game state at a glance.

i. Visual Feedback for Legal Moves

Let's add visual feedback for players to understand possible moves for a selected piece. This involves highlighting potential moves when a piece is selected.

First, let’s add some new imports to the top of our ChessGameGui class.

import java.util.List; // Add this to use List

We’ll then write our new method to highlight legal moves for a certain piece.

private void highlightLegalMoves(Position position) {
  List<Position> legalMoves = game.getLegalMovesForPieceAt(position);
  for (Position move : legalMoves) {
      squares[move.getRow()][move.getColumn()].setBackground(Color.GREEN);
  }
}

In this code, we've:

  • Created a method to highlight squares where the selected piece can legally move.
  • Called a getLegalMovesForPieceAt method from our ChessGame class, which should return a list of legal move positions for a piece at a given position.
  • Changed the background color of each square corresponding to a legal move to green for easy visibility.

The eagle-eyed among you should also notice we’re calling a method from ChessGame that doesn’t exist. Don’t worry, we’ll handle getLegalMovesForPieceAt shortly!

To keep things neat, we’ll also introduce a method to remove any previous move highlights before showcasing new options.

private void clearHighlights() {
  for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
          squares[row][col].setBackground((row + col) % 2 == 0 ? Color.LIGHT_GRAY : new Color(205, 133, 63));
      }
  }
}

Next up, we’ll update our handleSquareClick method to call our new highlight methods.

private void handleSquareClick(int row, int col) {
      boolean moveResult = game.handleSquareSelection(row, col);
      clearHighlights();
      if (moveResult) {
          // If a move was made, refresh and check game state without highlighting new
          // moves
          refreshBoard();
          checkGameState();
          checkGameOver();
      } else if (game.isPieceSelected()) {
          // If no move was made but a piece is selected, highlight its legal moves
          highlightLegalMoves(new Position(row, col));
      }
      refreshBoard();

}

In this adjusted code, we've:

  • Cleared previous highlights at the start of each interaction.
  • Distinguished between move completion and piece selection, ensuring we only show legal move highlights when a piece is selected but not moved. 
  • Refreshed the board after clearing highlights and potentially highlighting new legal moves to keep the game state visually up to date.

ii. Resetting the Game

Next, let's add a menu option for players to reset the game at any point. This involves creating a new game state and updating the GUI accordingly.

private void addGameResetOption() {
  JMenuBar menuBar = new JMenuBar();
  JMenu gameMenu = new JMenu("Game");
  JMenuItem resetItem = new JMenuItem("Reset");
  resetItem.addActionListener(e -> resetGame());
  gameMenu.add(resetItem);
  menuBar.add(gameMenu);
  setJMenuBar(menuBar);
}

private void resetGame() {
  game.resetGame();
  refreshBoard();
}

In this code, we've:

  • Added a menu bar with an option to reset the game.
  • Defined a resetGame method to reset the game state and refresh the board, ensuring all pieces are returned to their starting positions and the game state is reinitialized.

We’ll also update our constructor to ensure our new reset option is initialized correctly.

public ChessGameGUI() {
  setTitle("Chess Game");
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  setLayout(new GridLayout(8, 8));
  initializeBoard();
  addGameResetOption();
  pack(); // Adjust window size to fit the chessboard
  setVisible(true);
}

iii. Game Conclusion and Replay Option

Lastly, let's provide feedback for game conclusion scenarios like checkmate or stalemate and offer players an option to start a new game.

private void checkGameOver() {
  if (game.isCheckmate(game.getCurrentPlayerColor())) {
      int response = JOptionPane.showConfirmDialog(this, "Checkmate! Would you like to play again?", "Game Over",
              JOptionPane.YES_NO_OPTION);
      if (response == JOptionPane.YES_OPTION) {
          resetGame();
      } else {
          System.exit(0);
      }
  }
}

In this code, we've:

  • Checked for a checkmate condition and displayed a dialog box informing the players of the game's conclusion.
  • Offered an option to play again, which resets the game if selected, or exits the application otherwise.

iv. Updating ChessGame to Highlight Possible Moves

Remember when I mentioned we were calling a method in the ChessGame class that’s not currently there?

Well, let’s handle that now!

We'll update the ChessGame class to calculate and highlight possible moves for the selected piece. 

import java.util.List;
import java.util.ArrayList;

public class ChessGame {
  // Existing ChessGame attributes and methods

  public List<Position> getLegalMovesForPieceAt(Position position) {
      Piece selectedPiece = board.getPiece(position.getRow(), position.getColumn());
      if (selectedPiece == null) return new ArrayList<>();

      List<Position> legalMoves = new ArrayList<>();
      switch (selectedPiece.getClass().getSimpleName()) {
          case "Pawn":
              addPawnMoves(position, selectedPiece.getColor(), legalMoves);
              break;
          case "Rook":
              addLineMoves(position, new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}, legalMoves);
              break;
          case "Knight":
              addSingleMoves(position, new int[][]{{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {-1, 2}, {1, -2}, {-1, -2}}, legalMoves);
              break;
          case "Bishop":
              addLineMoves(position, new int[][]{{1, 1}, {-1, -1}, {1, -1}, {-1, 1}}, legalMoves);
              break;
          case "Queen":
              addLineMoves(position, new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}}, legalMoves);
              break;
          case "King":
              addSingleMoves(position, new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}}, legalMoves);
              break;
      }
      return legalMoves;
  }
}

In this code, we've:

  • Developed a versatile method that calculates legal moves for any selected piece, enriching the game's strategy layer.
  • Employed a switch statement to handle different pieces' unique movement rules.
  • Generated a list of potential positions each piece can legally move to, offering players insight into possible actions.

We’ve also called a range of helper methods, so let’s define those now.

private void addLineMoves(Position position, int[][] directions, List<Position> legalMoves) {
  for (int[] d : directions) {
      Position newPos = new Position(position.getRow() + d[0], position.getColumn() + d[1]);
      while (isPositionOnBoard(newPos)) {
          if (board.getPiece(newPos.getRow(), newPos.getColumn()) == null) {
              legalMoves.add(new Position(newPos.getRow(), newPos.getColumn()));
              newPos = new Position(newPos.getRow() + d[0], newPos.getColumn() + d[1]);
          } else {
              if (board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != board.getPiece(position.getRow(), position.getColumn()).getColor()) {
                  legalMoves.add(newPos);
              }
              break;
          }
      }
  }
}

The addLineMoves method computes legal moves along straight lines or diagonals, crucial for pieces like Rooks, Bishops, and Queens. 

It iterates through specified directions, adding legal positions until a piece blocks the path or the board's edge is reached.

private void addSingleMoves(Position position, int[][] moves, List<Position> legalMoves) {
  for (int[] move : moves) {
      Position newPos = new Position(position.getRow() + move[0], position.getColumn() + move[1]);
      if (isPositionOnBoard(newPos) && (board.getPiece(newPos.getRow(), newPos.getColumn()) == null ||
          board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != board.getPiece(position.getRow(), position.getColumn()).getColor())) {
          legalMoves.add(newPos);
      }
  }
}

The addSingleMoves method handles movements for pieces like Knights and Kings, which move to a fixed number of positions. 

This function checks each potential move's legality, considering both unoccupied spaces and capturable opponent pieces.

private void addPawnMoves(Position position, PieceColor color, List<Position> legalMoves) {
  int direction = color == PieceColor.WHITE ? -1 : 1;
  // Standard single move
  Position newPos = new Position(position.getRow() + direction, position.getColumn());
  if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) == null) {
      legalMoves.add(newPos);
  }
  // Double move from starting position
  if ((color == PieceColor.WHITE && position.getRow() == 6) || (color == PieceColor.BLACK && position.getRow() == 1)) {
      newPos = new Position(position.getRow() + 2 * direction, position.getColumn());
      Position intermediatePos = new Position(position.getRow() + direction, position.getColumn());
      if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) == null && board.getPiece(intermediatePos.getRow(), intermediatePos.getColumn()) == null) {
          legalMoves.add(newPos);
      }
  }
  // Captures
  int[] captureCols = {position.getColumn() - 1, position.getColumn() + 1};
  for (int col : captureCols) {
      newPos = new Position(position.getRow() + direction, col);
      if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) != null &&
          board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != color) {
          legalMoves.add(newPos);
      }
  }
}

The addPawnMoves method specifically caters to Pawns, accounting for their initial double move, regular single step, and diagonal captures. 

This method intricately respects chess rules, enhancing the realism of our digital board.

Wow! Great work! We’ve really taken our chess game to the next level now! I don’t know about you, but I’m ready to enjoy playing our Java chess game!

Now, let’s push into the final stages of this tutorial by looking at testing.

Step 8: Testing Your Java Chess Game

Congratulations on developing your very own Java chess game! 

Now the core functionality, user interface, and interaction logic have been implemented, it's time to ensure that your game is polished, intuitive, and free from bugs. 

Testing is crucial to identify any issues that might detract from the user experience or game performance. 

Let's explore how you could approach testing your chess game, focusing on functionality, usability, and interface design.

i. Functionality Testing

  • Game Flow: Verify the game follows the correct flow, starting from the initial setup to the endgame scenarios, including checkmate.
  • Move Validation: Ensure all chess pieces move according to their legal movements.
  • Turn Management: Test that turns alternate correctly between players and that moves cannot be made out of turn.
  • Check and Checkmate Detection: Confirm the game accurately detects and announces checks and checkmates, preventing moves that would leave or place the king in check.

ii. Usability Testing

  • User Interface: Gather feedback on the design, layout, and overall user experience. Is the game intuitive to play? Are the controls and instructions clear?
  • Performance: Evaluate the game's responsiveness, especially when making moves, selecting pieces, and during check/checkmate scenarios.
  • Accessibility: Consider aspects like color contrasts, piece differentiation, and any provided accessibility options to ensure the game is playable by a wide audience.

iii. Interface Design Testing

  • Visual Consistency: Check that the visual elements of the game, including the chessboard, pieces, and any additional UI components, are consistently styled and clear.
  • Responsiveness: Test how the game interface adapts to different screen sizes or window resizing, ensuring that the game remains playable and visually coherent.
  • Feedback Mechanisms: Ensure the game provides clear feedback for user actions, such as highlighting possible moves and indicating checks.

When it comes to conducting these tests, much of the functionality and usability testing will be manual due to the interactive nature of your chess game.

That said, you might want to consider using automated unit tests for specific logic-heavy components, such as move validation and game state management.

I’d also highly encourage you to get a small group of real users to use the application and provide feedback on usability and any bugs they encounter.

And remember that testing should be an iterative process. After fixing issues, retest to ensure no new problems have been introduced and that the original issue has been resolved.

Now that you’ve reached this stage, other steps I’d encourage you to consider include:

Documentation and Sharing:

  • Consider drafting a README file if you plan to showcase your project on platforms like GitHub, detailing the project setup and any noteworthy features.
  • Share your project within developer communities, social media, or chess forums to engage with a broader audience and gather additional feedback.

Reflect and Plan Next Steps:

  • Reflect on the knowledge gained through this project and contemplate how these learnings can be applied to future endeavors.
  • Consider possible enhancements such as adding an AI opponent, online multiplayer functionality, or customizable chessboard and piece themes.
  • You should also think about expanding the game to include special moves like en passant, not to mention scenarios involving stalemates.

Great job on building and refining your Java chess game! Take a moment to appreciate your hard work!

Whether you're building this for fun, as a learning experience, or as a portfolio piece, you've developed valuable skills in programming, problem-solving, and user interface design.

Be proud of your work, share it with others, and consider what project you'll take on next!

Step 9: Final Touches and Packaging

After thorough testing and refinements, it's time to apply the final touches to your Java chess application and prepare it for distribution. 

This step is all about ensuring your application is polished, user-friendly, and ready for others to use.

i. Final Review and Refinement

  • Code Cleanup: Go through your code to remove any unused variables, methods, or imports. Ensure your code is well-commented and follows a consistent coding style for easy maintenance and readability.
  • UI Consistency Check: Review the GUI to ensure all elements align with the chosen design theme. Test the application on different screen sizes and resolutions to guarantee a consistent user experience.
  • Optimization: Focus on optimizing the game's performance, such as improving the responsiveness of the game mechanics and ensuring efficient memory use. This might involve refining the game's logic for move validation, piece selection, and UI updates.
  • Security Review: Conduct a final security review to ensure there are no vulnerabilities, such as unprotected sensitive data or susceptibility to injection attacks.

ii. Documentation

  • User Guide: Create a user guide that includes installation instructions, features overview, and usage tips. Include troubleshooting steps for common issues users might encounter.
  • Developer Documentation: Document your code to help future developers understand and contribute to the project. Consider using tools like Javadoc to generate professional-looking documentation.

iii. Packaging and Distribution

  • Packaging the Application: For desktop apps, package your Java application as an executable JAR file. Use tools like Launch4j or JPackage to bundle your app into native installers for Windows, macOS, and Linux. Ensure all libraries are included in the package, and test the installation process on different OS.
  • Distribution Platforms: Choose appropriate platforms to distribute your application, such as GitHub for open-source projects, or personal websites for broader distribution.
  • Web Application: Consider using Java Web Start (for older versions of Java) or creating applets if your application needs to run within a web browser, though modern security restrictions make desktop distribution more viable for most cases.

Next Steps & Further Learning

Congratulations on successfully building your own Java chess game application!

This is a significant achievement, but your learning journey doesn't stop here. There are many ways to further your skills in Java development. Let's explore some ideas:

Learn More About Java

  • Java Enhancements: Explore more advanced Java concepts and apply them to your app. Could advanced features like user profiles or sound effects be added? How about letting users add messages or even customize the UI?
  • Explore Frameworks: Experiment with Java frameworks to see how they can be used to build more dynamic and complex Java applications.

Join Online Communities and Collaborate

  • Engage in Forums: Participate in Java development forums and communities. Share your chat app, get feedback, and learn from others.
  • Contribute to Open Source: Consider making your chess game open-source and collaborate with others to improve it.

Keep Up with Trends and Best Practices

Stay updated with the latest trends in Java development. Subscribe to blogs like hackr.io, watch webinars, and join online courses.

Document and Share Your Learning Journey

  • Blog About Your Project: Write about your development process, challenges you faced, and how you overcame them. Share your blog with the developer community.
  • Share Your Code: Publish your code on platforms like GitHub. This not only showcases your work but also allows others to learn from your project.

Challenge Yourself Regularly

Take part in coding challenges or hackathons to sharpen your skills and learn new techniques.

And if you're hungry for more Java projects, check back regularly, as we’re constantly adding new step-by-step tutorials.

For example, have you taken a look at my tutorial on how to build a Java chat app

Java Chess Game Application Full Source Code

ChessBoard:

public class ChessBoard {
  private Piece[][] board;

  public ChessBoard() {
      this.board = new Piece[8][8]; // Chessboard is 8x8
      setupPieces();
  }

  public Piece[][] getBoard() {
      return board;
  }

  public Piece getPiece(int row, int column) {
      return board[row][column];
  }

  public void setPiece(int row, int column, Piece piece) {
      board[row][column] = piece;
      if (piece != null) {
          piece.setPosition(new Position(row, column));
      }
  }

  private void setupPieces() {
      // Place Rooks
      board[0][0] = new Rook(PieceColor.BLACK, new Position(0, 0));
      board[0][7] = new Rook(PieceColor.BLACK, new Position(0, 7));
      board[7][0] = new Rook(PieceColor.WHITE, new Position(7, 0));
      board[7][7] = new Rook(PieceColor.WHITE, new Position(7, 7));
      // Place Knights
      board[0][1] = new Knight(PieceColor.BLACK, new Position(0, 1));
      board[0][6] = new Knight(PieceColor.BLACK, new Position(0, 6));
      board[7][1] = new Knight(PieceColor.WHITE, new Position(7, 1));
      board[7][6] = new Knight(PieceColor.WHITE, new Position(7, 6));
      // Place Bishops
      board[0][2] = new Bishop(PieceColor.BLACK, new Position(0, 2));
      board[0][5] = new Bishop(PieceColor.BLACK, new Position(0, 5));
      board[7][2] = new Bishop(PieceColor.WHITE, new Position(7, 2));
      board[7][5] = new Bishop(PieceColor.WHITE, new Position(7, 5));
      // Place Queens
      board[0][3] = new Queen(PieceColor.BLACK, new Position(0, 3));
      board[7][3] = new Queen(PieceColor.WHITE, new Position(7, 3));
      // Place Kings
      board[0][4] = new King(PieceColor.BLACK, new Position(0, 4));
      board[7][4] = new King(PieceColor.WHITE, new Position(7, 4));
      // Place Pawns
      for (int i = 0; i < 8; i++) {
          board[1][i] = new Pawn(PieceColor.BLACK, new Position(1, i));
          board[6][i] = new Pawn(PieceColor.WHITE, new Position(6, i));
      }
  }

  public void movePiece(Position start, Position end) {
      if (board[start.getRow()][start.getColumn()] != null &&
              board[start.getRow()][start.getColumn()].isValidMove(end, board)) {

          board[end.getRow()][end.getColumn()] = board[start.getRow()][start.getColumn()];
          board[end.getRow()][end.getColumn()].setPosition(end);
          board[start.getRow()][start.getColumn()] = null;
      }
  }
}

ChessGame:

import java.util.List;
import java.util.ArrayList;

public class ChessGame {
  private ChessBoard board;
  private boolean whiteTurn = true; // White starts the game

  public ChessGame() {
      this.board = new ChessBoard();
  }

  public ChessBoard getBoard() {
      return this.board;
  }

  public void resetGame() {
      this.board = new ChessBoard();
      this.whiteTurn = true;
  }

  public PieceColor getCurrentPlayerColor() {
      return whiteTurn ? PieceColor.WHITE : PieceColor.BLACK;
  }

  private Position selectedPosition;

  public boolean isPieceSelected() {
      return selectedPosition != null;
  }

  public boolean handleSquareSelection(int row, int col) {
      if (selectedPosition == null) {
          Piece selectedPiece = board.getPiece(row, col);
          if (selectedPiece != null
                  && selectedPiece.getColor() == (whiteTurn ? PieceColor.WHITE : PieceColor.BLACK)) {
              selectedPosition = new Position(row, col);
              return false;
          }
      } else {
          boolean moveMade = makeMove(selectedPosition, new Position(row, col));
          selectedPosition = null;
          return moveMade;
      }
      return false;
  }

  public boolean makeMove(Position start, Position end) {
      Piece movingPiece = board.getPiece(start.getRow(), start.getColumn());
      if (movingPiece == null || movingPiece.getColor() != (whiteTurn ? PieceColor.WHITE : PieceColor.BLACK)) {
          return false;
      }

      if (movingPiece.isValidMove(end, board.getBoard())) {
          board.movePiece(start, end);
          whiteTurn = !whiteTurn;
          return true;
      }
      return false;
  }

  public boolean isInCheck(PieceColor kingColor) {
      Position kingPosition = findKingPosition(kingColor);
      for (int row = 0; row < board.getBoard().length; row++) {
          for (int col = 0; col < board.getBoard()[row].length; col++) {
              Piece piece = board.getPiece(row, col);
              if (piece != null && piece.getColor() != kingColor) {
                  if (piece.isValidMove(kingPosition, board.getBoard())) {
                      return true;
                  }
              }
          }
      }
      return false;
  }

  private Position findKingPosition(PieceColor color) {
      for (int row = 0; row < board.getBoard().length; row++) {
          for (int col = 0; col < board.getBoard()[row].length; col++) {
              Piece piece = board.getPiece(row, col);
              if (piece instanceof King && piece.getColor() == color) {
                  return new Position(row, col);
              }
          }
      }
      throw new RuntimeException("King not found, which should never happen.");
  }

  public boolean isCheckmate(PieceColor kingColor) {
      if (!isInCheck(kingColor)) {
          return false;
      }

      Position kingPosition = findKingPosition(kingColor);
      King king = (King) board.getPiece(kingPosition.getRow(), kingPosition.getColumn());

      for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
          for (int colOffset = -1; colOffset <= 1; colOffset++) {
              if (rowOffset == 0 && colOffset == 0) {
                  continue;
              }
              Position newPosition = new Position(kingPosition.getRow() + rowOffset,
                      kingPosition.getColumn() + colOffset);

              if (isPositionOnBoard(newPosition) && king.isValidMove(newPosition, board.getBoard())
                      && !wouldBeInCheckAfterMove(kingColor, kingPosition, newPosition)) {
                  return false;
              }
          }
      }
      return true;
  }

  private boolean isPositionOnBoard(Position position) {
      return position.getRow() >= 0 && position.getRow() < board.getBoard().length &&
              position.getColumn() >= 0 && position.getColumn() < board.getBoard()[0].length;
  }

  private boolean wouldBeInCheckAfterMove(PieceColor kingColor, Position from, Position to) {
      Piece temp = board.getPiece(to.getRow(), to.getColumn());
      board.setPiece(to.getRow(), to.getColumn(), board.getPiece(from.getRow(), from.getColumn()));
      board.setPiece(from.getRow(), from.getColumn(), null);

      boolean inCheck = isInCheck(kingColor);

      board.setPiece(from.getRow(), from.getColumn(), board.getPiece(to.getRow(), to.getColumn()));
      board.setPiece(to.getRow(), to.getColumn(), temp);

      return inCheck;
  }

  public List<Position> getLegalMovesForPieceAt(Position position) {
      Piece selectedPiece = board.getPiece(position.getRow(), position.getColumn());
      if (selectedPiece == null)
          return new ArrayList<>();

      List<Position> legalMoves = new ArrayList<>();
      switch (selectedPiece.getClass().getSimpleName()) {
          case "Pawn":
              addPawnMoves(position, selectedPiece.getColor(), legalMoves);
              break;
          case "Rook":
              addLineMoves(position, new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }, legalMoves);
              break;
          case "Knight":
              addSingleMoves(position, new int[][] { { 2, 1 }, { 2, -1 }, { -2, 1 }, { -2, -1 }, { 1, 2 }, { -1, 2 },
                      { 1, -2 }, { -1, -2 } }, legalMoves);
              break;
          case "Bishop":
              addLineMoves(position, new int[][] { { 1, 1 }, { -1, -1 }, { 1, -1 }, { -1, 1 } }, legalMoves);
              break;
          case "Queen":
              addLineMoves(position, new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 }, { 1, 1 }, { -1, -1 },
                      { 1, -1 }, { -1, 1 } }, legalMoves);
              break;
          case "King":
              addSingleMoves(position, new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 }, { 1, 1 }, { -1, -1 },
                      { 1, -1 }, { -1, 1 } }, legalMoves);
              break;
      }
      return legalMoves;
  }

  private void addLineMoves(Position position, int[][] directions, List<Position> legalMoves) {
      for (int[] d : directions) {
          Position newPos = new Position(position.getRow() + d[0], position.getColumn() + d[1]);
          while (isPositionOnBoard(newPos)) {
              if (board.getPiece(newPos.getRow(), newPos.getColumn()) == null) {
                  legalMoves.add(new Position(newPos.getRow(), newPos.getColumn()));
                  newPos = new Position(newPos.getRow() + d[0], newPos.getColumn() + d[1]);
              } else {
                  if (board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != board
                          .getPiece(position.getRow(), position.getColumn()).getColor()) {
                      legalMoves.add(newPos);
                  }
                  break;
              }
          }
      }
  }

  private void addSingleMoves(Position position, int[][] moves, List<Position> legalMoves) {
      for (int[] move : moves) {
          Position newPos = new Position(position.getRow() + move[0], position.getColumn() + move[1]);
          if (isPositionOnBoard(newPos) && (board.getPiece(newPos.getRow(), newPos.getColumn()) == null ||
                  board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != board
                          .getPiece(position.getRow(), position.getColumn()).getColor())) {
              legalMoves.add(newPos);
          }
      }
  }

  private void addPawnMoves(Position position, PieceColor color, List<Position> legalMoves) {
      int direction = color == PieceColor.WHITE ? -1 : 1;
      Position newPos = new Position(position.getRow() + direction, position.getColumn());
      if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) == null) {
          legalMoves.add(newPos);
      }

      if ((color == PieceColor.WHITE && position.getRow() == 6)
              || (color == PieceColor.BLACK && position.getRow() == 1)) {
          newPos = new Position(position.getRow() + 2 * direction, position.getColumn());
          Position intermediatePos = new Position(position.getRow() + direction, position.getColumn());
          if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) == null
                  && board.getPiece(intermediatePos.getRow(), intermediatePos.getColumn()) == null) {
              legalMoves.add(newPos);
          }
      }

      int[] captureCols = { position.getColumn() - 1, position.getColumn() + 1 };
      for (int col : captureCols) {
          newPos = new Position(position.getRow() + direction, col);
          if (isPositionOnBoard(newPos) && board.getPiece(newPos.getRow(), newPos.getColumn()) != null &&
                  board.getPiece(newPos.getRow(), newPos.getColumn()).getColor() != color) {
              legalMoves.add(newPos);
          }
      }
  }
}

ChessGameGui:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.List;

public class ChessGameGUI extends JFrame {
  private final ChessSquareComponent[][] squares = new ChessSquareComponent[8][8];
  private final ChessGame game = new ChessGame();

  private final Map<Class<? extends Piece>, String> pieceUnicodeMap = new HashMap<>() {
      {
          put(Pawn.class, "\u265F");
          put(Rook.class, "\u265C");
          put(Knight.class, "\u265E");
          put(Bishop.class, "\u265D");
          put(Queen.class, "\u265B");
          put(King.class, "\u265A");
      }
  };

  public ChessGameGUI() {
      setTitle("Chess Game");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLayout(new GridLayout(8, 8));
      initializeBoard();
      addGameResetOption();
      pack();
      setVisible(true);
  }

  private void initializeBoard() {
      for (int row = 0; row < squares.length; row++) {
          for (int col = 0; col < squares[row].length; col++) {
              final int finalRow = row;
              final int finalCol = col;
              ChessSquareComponent square = new ChessSquareComponent(row, col);
              square.addMouseListener(new MouseAdapter() {
                  @Override
                  public void mouseClicked(MouseEvent e) {
                      handleSquareClick(finalRow, finalCol);
                  }
              });
              add(square);
              squares[row][col] = square;
          }
      }
      refreshBoard();
  }

  private void refreshBoard() {
      ChessBoard board = game.getBoard();
      for (int row = 0; row < 8; row++) {
          for (int col = 0; col < 8; col++) {
              Piece piece = board.getPiece(row, col);
              if (piece != null) {
                  // If using Unicode symbols:
                  String symbol = pieceUnicodeMap.get(piece.getClass());
                  Color color = (piece.getColor() == PieceColor.WHITE) ? Color.WHITE : Color.BLACK;
                  squares[row][col].setPieceSymbol(symbol, color);
              } else {
                  squares[row][col].clearPieceSymbol();
              }
          }
      }
  }

  private void handleSquareClick(int row, int col) {
      boolean moveResult = game.handleSquareSelection(row, col);
      clearHighlights();
      if (moveResult) {
          refreshBoard();
          checkGameState();
          checkGameOver();
      } else if (game.isPieceSelected()) {
          highlightLegalMoves(new Position(row, col));
      }
      refreshBoard();
  }

  private void checkGameState() {
      PieceColor currentPlayer = game.getCurrentPlayerColor();
      boolean inCheck = game.isInCheck(currentPlayer);

      if (inCheck) {
          JOptionPane.showMessageDialog(this, currentPlayer + " is in check!");
      }
  }

  private void highlightLegalMoves(Position position) {
      List<Position> legalMoves = game.getLegalMovesForPieceAt(position);
      for (Position move : legalMoves) {
          squares[move.getRow()][move.getColumn()].setBackground(Color.GREEN);
      }
  }

  private void clearHighlights() {
      for (int row = 0; row < 8; row++) {
          for (int col = 0; col < 8; col++) {
              squares[row][col].setBackground((row + col) % 2 == 0 ? Color.LIGHT_GRAY : new Color(205, 133, 63));
          }
      }
  }

  private void addGameResetOption() {
      JMenuBar menuBar = new JMenuBar();
      JMenu gameMenu = new JMenu("Game");
      JMenuItem resetItem = new JMenuItem("Reset");
      resetItem.addActionListener(e -> resetGame());
      gameMenu.add(resetItem);
      menuBar.add(gameMenu);
      setJMenuBar(menuBar);
  }

  private void resetGame() {
      game.resetGame();
      refreshBoard();
  }

  private void checkGameOver() {
      if (game.isCheckmate(game.getCurrentPlayerColor())) {
          int response = JOptionPane.showConfirmDialog(this, "Checkmate! Would you like to play again?", "Game Over",
                  JOptionPane.YES_NO_OPTION);
          if (response == JOptionPane.YES_OPTION) {
              resetGame();
          } else {
              System.exit(0);
          }
      }
  }

  public static void main(String[] args) {
      SwingUtilities.invokeLater(ChessGameGUI::new);
  }
}

ChessSquareComponent:

import javax.swing.*;
import java.awt.*;

public class ChessSquareComponent extends JButton {
  private int row;
  private int col;

  public ChessSquareComponent(int row, int col) {
      this.row = row;
      this.col = col;
      initButton();
  }

  private void initButton() {
      setPreferredSize(new Dimension(64, 64));

      if ((row + col) % 2 == 0) {
          setBackground(Color.LIGHT_GRAY);
      } else {
          setBackground(new Color(205, 133, 63));
      }

      setHorizontalAlignment(SwingConstants.CENTER);
      setVerticalAlignment(SwingConstants.CENTER);
      setFont(new Font("Serif", Font.BOLD, 36));
  }

  public void setPieceSymbol(String symbol, Color color) {
      this.setText(symbol);
      this.setForeground(color);
  }

  public void clearPieceSymbol() {
      this.setText("");
  }
}

Position:

public class Position {
  private int row;
  private int column;

  public Position(int row, int column) {
      this.row = row;
      this.column = column;
  }

  public int getRow() {
      return row;
  }

  public int getColumn() {
      return column;
  }
}

Piece:

public abstract class Piece {
  protected Position position;
  protected PieceColor color;

  public Piece(PieceColor color, Position position) {
      this.color = color;
      this.position = position;
  }

  public PieceColor getColor() {
      return color;
  }

  public Position getPosition() {
      return position;
  }

  public void setPosition(Position position) {
      this.position = position;
  }

  public abstract boolean isValidMove(Position newPosition, Piece[][] board);
}

PieceColor:

public enum PieceColor {
  BLACK, WHITE;
}

Pawn:

public class Pawn extends Piece {
  public Pawn(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int forwardDirection = color == PieceColor.WHITE ? -1 : 1;
      int rowDiff = (newPosition.getRow() - position.getRow()) * forwardDirection;
      int colDiff = newPosition.getColumn() - position.getColumn();

      if (colDiff == 0 && rowDiff == 1 && board[newPosition.getRow()][newPosition.getColumn()] == null) {
          return true;
      }

      boolean isStartingPosition = (color == PieceColor.WHITE && position.getRow() == 6) ||
              (color == PieceColor.BLACK && position.getRow() == 1);
      if (colDiff == 0 && rowDiff == 2 && isStartingPosition
              && board[newPosition.getRow()][newPosition.getColumn()] == null) {
          int middleRow = position.getRow() + forwardDirection;
          if (board[middleRow][position.getColumn()] == null) {
              return true;
          }
      }

      if (Math.abs(colDiff) == 1 && rowDiff == 1 && board[newPosition.getRow()][newPosition.getColumn()] != null &&
              board[newPosition.getRow()][newPosition.getColumn()].color != this.color) {
          return true;
      }

      return false;
  }
}

Rook:

public class Rook extends Piece {
  public Rook(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      if (position.getRow() == newPosition.getRow()) {
          int columnStart = Math.min(position.getColumn(), newPosition.getColumn()) + 1;
          int columnEnd = Math.max(position.getColumn(), newPosition.getColumn());
          for (int column = columnStart; column < columnEnd; column++) {
              if (board[position.getRow()][column] != null) {
                  return false;
              }
          }
      } else if (position.getColumn() == newPosition.getColumn()) {
          int rowStart = Math.min(position.getRow(), newPosition.getRow()) + 1;
          int rowEnd = Math.max(position.getRow(), newPosition.getRow());
          for (int row = rowStart; row < rowEnd; row++) {
              if (board[row][position.getColumn()] != null) {
                  return false;
              }
          }
      } else {
          return false;
      }

      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (destinationPiece == null) {
          return true;
      } else if (destinationPiece.getColor() != this.getColor()) {
          return true;
      }

      return false;
  }
}

Knight:

public class Knight extends Piece {
  public Knight(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      if (newPosition.equals(this.position)) {
          return false;
      }

      int rowDiff = Math.abs(this.position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(this.position.getColumn() - newPosition.getColumn());

      boolean isValidLMove = (rowDiff == 2 && colDiff == 1) || (rowDiff == 1 && colDiff == 2);

      if (!isValidLMove) {
          return false;
      }

      Piece targetPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (targetPiece == null) {
          return true;
      } else {
          return targetPiece.getColor() != this.getColor();
      }
  }
}

Bishop:

public class Bishop extends Piece {
  public Bishop(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int rowDiff = Math.abs(position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(position.getColumn() - newPosition.getColumn());

      if (rowDiff != colDiff) {
          return false;
      }

      int rowStep = newPosition.getRow() > position.getRow() ? 1 : -1;
      int colStep = newPosition.getColumn() > position.getColumn() ? 1 : -1;
      int steps = rowDiff - 1;

      for (int i = 1; i <= steps; i++) {
          if (board[position.getRow() + i * rowStep][position.getColumn() + i * colStep] != null) {
              return false;
          }
      }

      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      if (destinationPiece == null) {
          return true;
      } else if (destinationPiece.getColor() != this.getColor()) {
          return true;
      }

      return false;
  }
}

Queen:

public class Queen extends Piece {
  public Queen(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      if (newPosition.equals(this.position)) {
          return false;
      }

      int rowDiff = Math.abs(newPosition.getRow() - this.position.getRow());
      int colDiff = Math.abs(newPosition.getColumn() - this.position.getColumn());

      boolean straightLine = this.position.getRow() == newPosition.getRow()
              || this.position.getColumn() == newPosition.getColumn();

      boolean diagonal = rowDiff == colDiff;

      if (!straightLine && !diagonal) {
          return false;
      }

      int rowDirection = Integer.compare(newPosition.getRow(), this.position.getRow());
      int colDirection = Integer.compare(newPosition.getColumn(), this.position.getColumn());

      int currentRow = this.position.getRow() + rowDirection;
      int currentCol = this.position.getColumn() + colDirection;
      while (currentRow != newPosition.getRow() || currentCol != newPosition.getColumn()) {
          if (board[currentRow][currentCol] != null) {
              return false;
          }
          currentRow += rowDirection;
          currentCol += colDirection;
      }

      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      return destinationPiece == null || destinationPiece.getColor() != this.getColor();
  }
}

King:

public class King extends Piece {
  public King(PieceColor color, Position position) {
      super(color, position);
  }

  @Override
  public boolean isValidMove(Position newPosition, Piece[][] board) {
      int rowDiff = Math.abs(position.getRow() - newPosition.getRow());
      int colDiff = Math.abs(position.getColumn() - newPosition.getColumn());

      boolean isOneSquareMove = rowDiff <= 1 && colDiff <= 1 && !(rowDiff == 0 && colDiff == 0);

      if (!isOneSquareMove) {
          return false;
      }

      Piece destinationPiece = board[newPosition.getRow()][newPosition.getColumn()];
      return destinationPiece == null || destinationPiece.getColor() != this.getColor();
  }
}

Wrapping Up

Building a Java chess game application is a great way to enhance your Java development skills and delve into creating interactive Java applications.

By creating this interactive Java chess game, you've navigated through various challenges, from designing an intuitive interface to implementing complex game logic and interactive gameplay.

In this tutorial, you’ve learned how to:

  • Utilize Java Swing to construct a visually appealing and functional chess game GUI.
  • Craft Java code to establish the chess game logic, including piece movement, capturing, and special moves like castling and en passant.
  • Dynamically update the Swing components to reflect the current state of the game, providing real-time feedback to player actions.
  • Handle user interactions through event listeners for piece selection, movement, and implementing game rules to ensure legal moves.
  • Incorporate advanced game features such as check and checkmate detection, turn management, and highlighting legal moves for selected pieces.

You now possess the essential tools and knowledge needed to further develop and expand this Java chess game. 

You can introduce more sophisticated features, such as AI opponents, online multiplayer functionality, custom piece sets and boards, or integrating additional chess variants and puzzles. 

Your journey into the world of Java doesn't end here. With these new skills, you're well-equipped to experiment with more complex Java projects, explore other aspects of Java, and continue building fun and interactive apps.

Have fun and happy coding!

Want to sharpen up your Java development skills in 2024? Check out:

Udemy's Top Rated Course: Java 17 Masterclass: Start Coding in 2024

By Robert Johns

Technical Editor for Hackr.io | 15+ Years in Python, Java, SQL, C++, C#, JavaScript, Ruby, PHP, .NET, MATLAB, HTML & CSS, and more... 10+ Years in Networking, Cloud, APIs, Linux | 5+ Years in Data Science | 2x PhDs in Structural & Blast Engineering

View all post by the author

Subscribe to our Newsletter for Articles, News, & Jobs.

Thanks for subscribing! Look out for our welcome email to verify your email and get our free newsletters.

Disclosure: Hackr.io is supported by its audience. When you purchase through links on our site, we may earn an affiliate commission.

In this article

Learn More

Please login to leave comments

Jim Markus

Anyone have ideas on how to add en passant?

3 weeks ago