Java chess game with AI

Learn how to build a simple java chess game with AI from scratch. This tutorial covers the basics of chess, the implementation of a chess board and AI opponent using the minimax algorithm, and provides a starting point for further improvements and additions.

Introduction to Chess game with AI

Creating a chess AI involves simulating a game where the AI evaluates potential moves and chooses the best one based on a given strategy. In our case, we’ll use the Minimax algorithm, a classic approach in game theory that assumes both players will play optimally to minimize the possible loss.

Minimax works by evaluating the potential future states of the game board, up to a certain depth, and choosing the move that maximizes the AI’s chances of winning while minimizing the opponent’s opportunities.

Board Representation

The board is an 8×8 grid where each cell can either be empty or contain a chess piece. The initial board setup mirrors a real chess game, with pieces arranged in their standard positions. This setup involves:

  • 8×8 Array: The board is represented by a 2D array, where each element holds a character representing a piece or an empty square.
  • Piece Symbols: Pieces are denoted by characters (e.g., ‘K’ for King, ‘Q’ for Queen). Uppercase letters represent white pieces, and lowercase letters represent black pieces.
java chess game

Code Structure of java Chess Game

Our implementation consists of three main components:

  1. Board class: Represents the chess board and handles piece movements, validation, and printing.
  2. ChessAI class: Implements the AI opponent using the minimax algorithm with alpha-beta pruning.
  3. main function: Orchestrates the game flow, handling user input and AI moves.

Printing the Board

To visualize the board, we need a function that outputs the board’s current state to the console. This function:

  • Displays Rows and Columns: Adds labels for columns (‘a’ to ‘h’) and rows (8 to 1) to mimic a real chessboard layout.
  • Iterates Over the Grid: Loops through the array and prints each piece or empty square in its corresponding position.
    void print() const {
        std::cout << "  a b c d e f g h\n";
        for (int i = 0; i < BOARD_SIZE; ++i) {
            std::cout << 8 - i << " ";
            for (int j = 0; j < BOARD_SIZE; ++j) {
                std::cout << board[i][j] << ' ';
            }
            std::cout << 8 - i << "\n";
        }
        std::cout << "  a b c d e f g h\n";
    }
};

Move Representation

A move in chess involves changing the position of a piece from one square to another. This is represented by a structure that contains:

  • Starting Coordinates: The initial position of the piece on the board.
  • Ending Coordinates: The target position where the piece is to be moved.

Move Validation

Before applying a move, it must be validated to ensure it’s within the rules of chess. This involves checking:

  • Boundaries: Ensuring that both the start and end positions are within the 8×8 grid.
  • Piece Presence: Verifying that the starting position actually contains a piece and the move adheres to the specific piece’s movement rules (e.g., a rook can only move in straight lines).
bool isValidMove(const Move& move) const {
    return move.startX >= 0 && move.startX < 8 && move.startY >= 0 && move.startY < 8 &&
           move.endX >= 0 && move.endX < 8 && move.endY >= 0 && move.endY < 8 &&
           board[move.startY][move.startX] != '.';
}

void applyMove(const Move& move) {
    board[move.endY][move.endX] = board[move.startY][move.startX];
    board[move.startY][move.startX] = '.';
}

void undoMove(const Move& move, char capturedPiece) {
    board[move.startY][move.startX] = board[move.endY][move.endX];
    board[move.endY][move.endX] = capturedPiece;
}

char pieceAt(int x, int y) const {
    return board[y][x];
}

Move Application and Reversal

When a move is validated, the board is updated by:

  • Changing Positions: Moving the piece to the new position and clearing the starting position.
  • Handling Captures: If a move captures an opponent’s piece, the piece is removed from the board.

To support the minimax algorithm (discussed later), the ability to undo moves is also crucial. This means keeping track of the previous state and restoring it if necessary.

Generating Possible Moves

A critical part of the AI involves generating all possible moves from a given board state. This involves:

  • Iterating Over the Board: Checking each square for a piece.
  • Piece Movement Rules: For each piece, generating potential moves based on its movement capabilities (e.g., a bishop moves diagonally).

Chess AI Using Minimax Algorithm

The core of the AI’s decision-making process is the minimax algorithm. This algorithm simulates future moves to evaluate the best possible outcome.

  • Minimax Concept: The AI considers all possible moves, assuming that the opponent will always play optimally. The goal is to minimize the opponent’s advantage while maximizing the AI’s own position.
  • Depth: The algorithm explores moves up to a certain depth (number of future moves considered). The deeper the search, the more computationally intensive it becomes but generally leads to better decisions.
  • Evaluation Function: A function that assigns a score to a board position, helping the AI determine which moves are favorable. This function can consider factors like material balance (value of pieces) and positional advantages.

Alpha-Beta Pruning

To enhance the efficiency of the minimax algorithm, alpha-beta pruning is used:

  • Pruning: It eliminates the need to explore moves that won’t affect the final decision. This reduces the number of possible moves the AI needs to evaluate, speeding up the decision-making process.
  • Alpha and Beta Values: These values represent the minimum and maximum bounds for possible outcomes, helping to prune branches of the game tree that won’t yield better results.

NOTE

You can get it’s code from GitHub Repository

BY clicking on this button you can download the Zip File

User Interaction

The game allows a player to input moves via the console:

  • Move Parsing: The player’s move is read as a string and converted into coordinates. This move is then validated and applied to the board if it’s legal.
  • AI Move: The AI calculates its best move using the minimax algorithm and updates the board accordingly.

Game Loop

The game runs in a continuous loop, alternating between the human player and the AI:

  • Print Board: Display the current board state.
  • Player Move: Prompt the human player for a move, validate and apply it.
  • AI Move: Calculate and apply the AI’s move.
  • Check for Endgame: After each move, check if the game has reached a checkmate or stalemate situation.

OUTPUT of chess game of java

chess game with AI

Conclusion

Building a chess game with AI in C++ is a challenging and rewarding project. By understanding the basics of chess and implementing a simple game with AI, we can gain insights into game development and AI algorithms. This implementation provides a solid foundation for further improvements and additions, allowing us to create a more comprehensive and engaging chess game.

Source Code of java Chess Game with AI

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

enum Player { HUMAN, AI }

class Move {
    int startX, startY, endX, endY;

    Move(int startX, int startY, int endX, int endY) {
        this.startX = startX;
        this.startY = startY;
        this.endX = endX;
        this.endY = endY;
    }
}

class Board {
    private static final int BOARD_SIZE = 8;
    private char[][] board;

    public Board() {
        String initialBoard =
                "rnbqkbnr" +
                        "pppppppp" +
                        "........" +
                        "........" +
                        "........" +
                        "........" +
                        "PPPPPPPP" +
                        "RNBQKBNR";

        board = new char[BOARD_SIZE][BOARD_SIZE];
        for (int i = 0; i < 64; i++) {
            board[i / BOARD_SIZE][i % BOARD_SIZE] = initialBoard.charAt(i);
        }
    }

    public void print() {
        System.out.println("  a b c d e f g h");
        for (int i = 0; i < BOARD_SIZE; i++) {
            System.out.print((8 - i) + " ");
            for (int j = 0; j < BOARD_SIZE; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println(8 - i);
        }
        System.out.println("  a b c d e f g h");
    }

    public boolean isValidMove(Move move) {
        return move.startX >= 0 && move.startX < BOARD_SIZE &&
                move.startY >= 0 && move.startY < BOARD_SIZE &&
                move.endX >= 0 && move.endX < BOARD_SIZE &&
                move.endY >= 0 && move.endY < BOARD_SIZE &&
                board[move.startY][move.startX] != '.';
    }

    public void applyMove(Move move) {
        board[move.endY][move.endX] = board[move.startY][move.startX];
        board[move.startY][move.startX] = '.';
    }

    public void undoMove(Move move, char capturedPiece) {
        board[move.startY][move.startX] = board[move.endY][move.endX];
        board[move.endY][move.endX] = capturedPiece;
    }

    public char pieceAt(int x, int y) {
        return board[y][x];
    }

    public List<Move> generateMoves() {
        List<Move> moves = new ArrayList<>();
        for (int y = 0; y < BOARD_SIZE; y++) {
            for (int x = 0; x < BOARD_SIZE; x++) {
                if (board[y][x] != '.') {
                    for (int dy = -1; dy <= 1; dy++) {
                        for (int dx = -1; dx <= 1; dx++) {
                            int ny = y + dy;
                            int nx = x + dx;
                            if (ny >= 0 && ny < BOARD_SIZE && nx >= 0 && nx < BOARD_SIZE) {
                                moves.add(new Move(x, y, nx, ny));
                            }
                        }
                    }
                }
            }
        }
        return moves;
    }
}

class ChessAI {
    public Move findBestMove(Board board, int depth) {
        Move bestMove = null;
        int bestValue = Integer.MIN_VALUE;

        for (Move move : board.generateMoves()) {
            char capturedPiece = board.pieceAt(move.endX, move.endY);
            board.applyMove(move);
            int moveValue = minimax(board, depth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE, false);
            board.undoMove(move, capturedPiece);
            if (moveValue > bestValue) {
                bestMove = move;
                bestValue = moveValue;
            }
        }

        return bestMove;
    }

    private int evaluate(Board board) {
        return 0; // Simplified evaluation
    }

    private int minimax(Board board, int depth, int alpha, int beta, boolean maximizingPlayer) {
        if (depth == 0) {
            return evaluate(board);
        }

        if (maximizingPlayer) {
            int maxEval = Integer.MIN_VALUE;
            for (Move move : board.generateMoves()) {
                char capturedPiece = board.pieceAt(move.endX, move.endY);
                board.applyMove(move);
                int eval = minimax(board, depth - 1, alpha, beta, false);
                board.undoMove(move, capturedPiece);
                maxEval = Math.max(maxEval, eval);
                alpha = Math.max(alpha, eval);
                if (beta <= alpha) {
                    break;
                }
            }
            return maxEval;
        } else {
            int minEval = Integer.MAX_VALUE;
            for (Move move : board.generateMoves()) {
                char capturedPiece = board.pieceAt(move.endX, move.endY);
                board.applyMove(move);
                int eval = minimax(board, depth - 1, alpha, beta, true);
                board.undoMove(move, capturedPiece);
                minEval = Math.min(minEval, eval);
                beta = Math.min(beta, eval);
                if (beta <= alpha) {
                    break;
                }
            }
            return minEval;
        }
    }
}

 class ChessGame {
    public static void main(String[] args) {
        Board board = new Board();
        ChessAI ai = new ChessAI();
        Player currentPlayer = Player.HUMAN;
        Scanner scanner = new Scanner(System.in);

        while (true) {
            board.print();

            if (currentPlayer == Player.HUMAN) {
                System.out.print("Enter your move (e.g., e2e4): ");
                String moveStr = scanner.nextLine();
                Move move = parseMove(moveStr);
                if (board.isValidMove(move)) {
                    board.applyMove(move);
                    currentPlayer = Player.AI;
                } else {
                    System.out.println("Invalid move!");
                }
            } else {
                Move bestMove = ai.findBestMove(board, 3);
                board.applyMove(bestMove);
                System.out.println("AI move: " + (char) (bestMove.startX + 'a') + (8 - bestMove.startY)
                        + (char) (bestMove.endX + 'a') + (8 - bestMove.endY));
                currentPlayer = Player.HUMAN;
            }
        }
    }

    private static Move parseMove(String moveStr) {
        return new Move(
                moveStr.charAt(0) - 'a',
                8 - (moveStr.charAt(1) - '0'),
                moveStr.charAt(2) - 'a',
                8 - (moveStr.charAt(3) - '0')
        );
    }
}

Leave a comment