diff --git a/OOP_1A2_Project/bin/.gitignore b/OOP_1A2_Project/bin/.gitignore index 5c7ebe3..6d0c385 100644 --- a/OOP_1A2_Project/bin/.gitignore +++ b/OOP_1A2_Project/bin/.gitignore @@ -1,2 +1 @@ /backend/ -/windowInterface/ diff --git a/OOP_1A2_Project/bin/backend/AutoPlayer.class b/OOP_1A2_Project/bin/backend/AutoPlayer.class index fe9ba97..ecb0d55 100644 Binary files a/OOP_1A2_Project/bin/backend/AutoPlayer.class and b/OOP_1A2_Project/bin/backend/AutoPlayer.class differ diff --git a/OOP_1A2_Project/bin/backend/Board.class b/OOP_1A2_Project/bin/backend/Board.class index bdc69a0..c9b1346 100644 Binary files a/OOP_1A2_Project/bin/backend/Board.class and b/OOP_1A2_Project/bin/backend/Board.class differ diff --git a/OOP_1A2_Project/bin/backend/Game.class b/OOP_1A2_Project/bin/backend/Game.class index 4eba96a..cdb6b69 100644 Binary files a/OOP_1A2_Project/bin/backend/Game.class and b/OOP_1A2_Project/bin/backend/Game.class differ diff --git a/OOP_1A2_Project/bin/backend/Move.class b/OOP_1A2_Project/bin/backend/Move.class index ec997d7..37c1387 100644 Binary files a/OOP_1A2_Project/bin/backend/Move.class and b/OOP_1A2_Project/bin/backend/Move.class differ diff --git a/OOP_1A2_Project/bin/backend/Piece.class b/OOP_1A2_Project/bin/backend/Piece.class index 2b1301e..b5ca328 100644 Binary files a/OOP_1A2_Project/bin/backend/Piece.class and b/OOP_1A2_Project/bin/backend/Piece.class differ diff --git a/OOP_1A2_Project/src/backend/AutoPlayer.java b/OOP_1A2_Project/src/backend/AutoPlayer.java index a988a22..3beca93 100644 --- a/OOP_1A2_Project/src/backend/AutoPlayer.java +++ b/OOP_1A2_Project/src/backend/AutoPlayer.java @@ -1,17 +1,99 @@ package backend; +import java.util.ArrayList; +import java.util.List; + public class AutoPlayer { - - - /** - * returns the best Move to try on provided board for active player - * @param board - * @return - */ - public Move computeBestMove(Board board) { - - return null; - } - - + + private static final int MAX_DEPTH = 2; + + public Move computeBestMove(Board board) { + List legalMoves = getAllLegalMoves(board); + if (legalMoves.isEmpty()) return null; + + int bestScore = Integer.MIN_VALUE; + Move bestMove = null; + + for (Move move : legalMoves) { + Board tempBoard = new Board(board); + Move simulated = new Move(move.getFromX(), move.getFromY(), move.getToX(), move.getToY(), tempBoard); + tempBoard.playMove(simulated); + + int score = -negamax(tempBoard, MAX_DEPTH - 1, !board.isTurnWhite()); + if (score > bestScore) { + bestScore = score; + bestMove = move; + } + } + + return bestMove; + } + + private int negamax(Board board, int depth, boolean isWhite) { + if (depth == 0) { + return evaluate(board, isWhite); + } + + int max = Integer.MIN_VALUE; + for (Move move : getAllLegalMoves(board)) { + Board tempBoard = new Board(board); + Move simulated = new Move(move.getFromX(), move.getFromY(), move.getToX(), move.getToY(), tempBoard); + tempBoard.playMove(simulated); + int score = -negamax(tempBoard, depth - 1, !isWhite); + max = Math.max(max, score); + } + return max; + } + + private int evaluate(Board board, boolean isWhite) { + int score = 0; + + for (Piece p : board.getPieces()) { + int value = switch (p.getType()) { + case Pawn -> 1; + case Knight, Bishop -> 3; + case Rook -> 5; + case Queen -> 9; + case King -> 100; + }; + + int developmentBonus = 0; + + // Encourage developing minor pieces + if (p.getType() != PieceType.Pawn && p.getType() != PieceType.King) { + if ((p.isWhite() && p.getY() < 6) || (!p.isWhite() && p.getY() > 1)) { + developmentBonus += 1; + } + } + + // Encourage center control + if (p.getX() >= 2 && p.getX() <= 5 && p.getY() >= 2 && p.getY() <= 5) { + developmentBonus += 1; + } + + int pieceScore = value + developmentBonus; + score += (p.isWhite() == isWhite ? pieceScore : -pieceScore); + } + + return score; + } + + private List getAllLegalMoves(Board board) { + List legalMoves = new ArrayList<>(); + boolean isWhite = board.isTurnWhite(); + + for (Piece piece : board.getPieces()) { + if (piece.isWhite() == isWhite) { + List validMoves = new Move(0, 0, 0, 0, board).getValidMoves(piece, board); + for (Move.Position pos : validMoves) { + Move move = new Move(piece.getX(), piece.getY(), pos.x, pos.y, board); + if (move.isValid() && !move.putsOwnKingInCheck()) { + legalMoves.add(move); + } + } + } + } + + return legalMoves; + } } diff --git a/OOP_1A2_Project/src/backend/Board.java b/OOP_1A2_Project/src/backend/Board.java index 328735f..c64fdf3 100644 --- a/OOP_1A2_Project/src/backend/Board.java +++ b/OOP_1A2_Project/src/backend/Board.java @@ -20,11 +20,6 @@ public class Board { // Add Sound instance private Sound sound; - // En passant tracking - private Integer enPassantX = null; // File where en passant capture is possible - private Integer enPassantY = null; // Rank where the pawn that can be captured is located - private int enPassantTurn = -1; // Turn number when en passant became possible - public Board(int colNum, int lineNum) { this.width = colNum; this.height = lineNum; @@ -47,6 +42,15 @@ public class Board { public int getTurnNumber() { return turnNumber; } + public void playMove(Move move) { + if (move != null && move.isValid() && !move.putsOwnKingInCheck()) { + move.execute(); + System.out.println("AI plays: " + move.getFromX() + "," + move.getFromY() + " -> " + move.getToX() + "," + move.getToY()); + } else { + System.out.println("Invalid AI move."); + } + } + public boolean isTurnWhite() { return isWhiteTurn; @@ -118,7 +122,6 @@ public class Board { public void cleanBoard() { pieces.clear(); - clearEnPassant(); } public String toString() { @@ -234,13 +237,7 @@ public class Board { fileRep[y] = line; } - // Include en passant information in the file representation - String enPassantInfo = ""; - if (enPassantX != null && enPassantY != null) { - enPassantInfo = "," + enPassantX + "," + enPassantY + "," + enPassantTurn; - } - - fileRep[height] = (isWhiteTurn ? "W" : "B") + "," + turnNumber + enPassantInfo; + fileRep[height] = (isWhiteTurn ? "W" : "B") + "," + turnNumber; return fileRep; } @@ -294,23 +291,9 @@ public class Board { } else { this.turnNumber = 0; } - - // Parse en passant information - if (turnData.length >= 5) { - try { - this.enPassantX = Integer.parseInt(turnData[2].trim()); - this.enPassantY = Integer.parseInt(turnData[3].trim()); - this.enPassantTurn = Integer.parseInt(turnData[4].trim()); - } catch (NumberFormatException e) { - clearEnPassant(); - } - } else { - clearEnPassant(); - } } else { this.turnNumber = 0; this.isWhiteTurn = true; - clearEnPassant(); } } @@ -343,11 +326,6 @@ public class Board { this.highlightedPositions = new ArrayList<>(); this.highlightedPositions.addAll(board.highlightedPositions); - // Copy en passant state - this.enPassantX = board.enPassantX; - this.enPassantY = board.enPassantY; - this.enPassantTurn = board.enPassantTurn; - // Initialize board history for copy constructor this.boardHistory = new ArrayList<>(); @@ -378,17 +356,6 @@ public class Board { highlightedPositions.addAll(validMoves); } - public void playMove(Move move) { - if (move.isValid() && !move.putsOwnKingInCheck()) { - // SAVE STATE BEFORE MOVE - saveCurrentState(); - - move.execute(); - - // Play the move sound using instance method - sound.playMoveSound(); - } - } // UNDO METHODS USING ARRAYLIST private void saveCurrentState() { @@ -403,15 +370,12 @@ public class Board { // Remove it from history boardHistory.remove(boardHistory.size() - 1); + // Use existing methods to restore state this.width = previousBoard.width; this.height = previousBoard.height; this.turnNumber = previousBoard.turnNumber; this.isWhiteTurn = previousBoard.isWhiteTurn; - this.enPassantX = previousBoard.enPassantX; - this.enPassantY = previousBoard.enPassantY; - this.enPassantTurn = previousBoard.enPassantTurn; - // Use existing getPieces() method to restore pieces this.pieces.clear(); for (Piece p : previousBoard.getPieces()) { @@ -429,58 +393,10 @@ public class Board { return !boardHistory.isEmpty(); } + // Add cleanup method to properly dispose of sound resources public void cleanup() { if (sound != null) { sound.cleanup(); } } - - public void setEnPassant(int x, int y) { - this.enPassantX = x; - this.enPassantY = y; - this.enPassantTurn = this.turnNumber; - } - - - public void clearEnPassant() { - this.enPassantX = null; - this.enPassantY = null; - this.enPassantTurn = -1; - } - - public boolean canCaptureEnPassant(int x, int y) { - // En passant is only valid for one turn after the double pawn move - if (enPassantX == null || enPassantY == null) { - return false; - } - - // Check if this is the immediate next turn - if (turnNumber != enPassantTurn + 1) { - return false; - } - - // Check if the position matches - return enPassantX == x && enPassantY == y; - } - - public void handlePawnDoubleMove(int fromX, int fromY, int toX, int toY) { - Piece pawn = getPieceAt(toX, toY); - if (pawn != null && pawn.getType() == PieceType.Pawn) { - if (Math.abs(toY - fromY) == 2) { - setEnPassant(toX, toY); - } else { - clearEnPassant(); - } - } else { - clearEnPassant(); - } - } - - - public Enpassant.Position getEnPassantTarget() { - if (enPassantX != null && enPassantY != null && canCaptureEnPassant(enPassantX, enPassantY)) { - return new Enpassant.Position(enPassantX, enPassantY); - } - return null; - } } \ No newline at end of file diff --git a/OOP_1A2_Project/src/backend/Game.java b/OOP_1A2_Project/src/backend/Game.java index 4c64f70..ad38d0e 100644 --- a/OOP_1A2_Project/src/backend/Game.java +++ b/OOP_1A2_Project/src/backend/Game.java @@ -49,7 +49,9 @@ public class Game extends Thread { private void aiPlayerTurn() { if(isAITurn()) { - board.playMove(aiPlayer.computeBestMove(new Board(board))); + Move move = aiPlayer.computeBestMove(board); // Use the real board + board.playMove(move); + } } @@ -106,5 +108,6 @@ public class Game extends Thread { public void toggleAI(boolean isWhite) { this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0]; } + } diff --git a/OOP_1A2_Project/src/backend/Move.java b/OOP_1A2_Project/src/backend/Move.java index ada3231..7af41bb 100644 --- a/OOP_1A2_Project/src/backend/Move.java +++ b/OOP_1A2_Project/src/backend/Move.java @@ -12,14 +12,6 @@ public class Move { private Piece capturedPiece; private Board board; - // Castling-related fields - private CastlingHandler castlingHandler; - private CastlingHandler.CastlingMove castlingMove; - private boolean isCastling; - - // En passant-related fields - private Enpassant enpassantHandler; - private boolean isEnPassant; public Move(int fromX, int fromY, int toX, int toY, Board board) { this.fromX = fromX; @@ -29,15 +21,6 @@ public class Move { this.board = board; this.movingPiece = board.getPieceAt(fromX, fromY); this.capturedPiece = board.getPieceAt(toX, toY); - - // Initialize castling handler and check if this is a castling move - this.castlingHandler = new CastlingHandler(board); - this.castlingMove = castlingHandler.getCastlingMove(fromX, fromY, toX, toY); - this.isCastling = (castlingMove != null); - - // Initialize en passant handler and check if this is an en passant move - this.enpassantHandler = new Enpassant(board); - this.isEnPassant = enpassantHandler.isEnPassantValid(fromX, fromY, toX, toY); } public boolean isValid() { @@ -46,46 +29,20 @@ public class Move { return false; } - // Handle castling validation - if (isCastling) { - return castlingHandler.isCastlingValid(castlingMove); - } - - // Handle en passant validation - if (isEnPassant) { - return enpassantHandler.isEnPassantValid(fromX, fromY, toX, toY); - } - // Check if the move is in the list of valid moves for this piece List validMoves = getValidDestinations(movingPiece, board); - return containsPosition(validMoves, new Position(toX, toY)); + return validMoves.contains(new Position(toX, toY)); } public void execute() { - if (isCastling) { - // Execute castling move - castlingHandler.executeCastling(castlingMove); - } else if (isEnPassant) { - // Execute en passant capture - enpassantHandler.executeEnPassant(fromX, fromY, toX, toY); - } else { - // Remove any piece at the destination (normal capture) - if (capturedPiece != null) { - board.removePiece(toX, toY); - } - - // Move the piece (remove from original position, add to new position) - board.removePiece(fromX, fromY); - board.setPiece(movingPiece.isWhite(), movingPiece.getType(), toX, toY); + // Remove any piece at the destination (normal capture) + if (capturedPiece != null) { + board.removePiece(toX, toY); } - // Handle en passant state updates for pawn moves - if (movingPiece.getType() == PieceType.Pawn && !isEnPassant) { - board.handlePawnDoubleMove(fromX, fromY, toX, toY); - } else { - // Clear en passant for non-pawn moves - board.clearEnPassant(); - } + // Move the piece (remove from original position, add to new position) + board.removePiece(fromX, fromY); + board.setPiece(movingPiece.isWhite(), movingPiece.getType(), toX, toY); // Advance turn board.advanceTurn(); @@ -99,29 +56,15 @@ public class Move { Piece tempPiece = tempBoard.getPieceAt(fromX, fromY); if (tempPiece == null) return true; // Safety check - if (isCastling) { - // For castling, we need to check the castling move specifically - CastlingHandler tempHandler = new CastlingHandler(tempBoard); - CastlingHandler.CastlingMove tempCastling = tempHandler.getCastlingMove(fromX, fromY, toX, toY); - if (tempCastling != null) { - tempHandler.executeCastling(tempCastling); - } - } else if (isEnPassant) { - // For en passant, execute the en passant capture - Enpassant tempEnpassant = new Enpassant(tempBoard); - tempEnpassant.executeEnPassant(fromX, fromY, toX, toY); - } else { - // Remove any piece at destination (normal capture) - tempBoard.removePiece(toX, toY); - - // Move the piece - tempBoard.removePiece(fromX, fromY); - tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); - } + // Remove any piece at destination (normal capture) + tempBoard.removePiece(toX, toY); + + // Move the piece + tempBoard.removePiece(fromX, fromY); + tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); // Check if king is in check after move - CastlingHandler tempHandler = new CastlingHandler(tempBoard); - return tempHandler.isKingInCheck(tempPiece.isWhite()); + return isKingInCheck(tempBoard, tempPiece.isWhite()); } public boolean putsOpponentInCheck() { @@ -132,29 +75,15 @@ public class Move { Piece tempPiece = tempBoard.getPieceAt(fromX, fromY); if (tempPiece == null) return false; // Safety check - if (isCastling) { - // For castling, execute the castling move - CastlingHandler tempHandler = new CastlingHandler(tempBoard); - CastlingHandler.CastlingMove tempCastling = tempHandler.getCastlingMove(fromX, fromY, toX, toY); - if (tempCastling != null) { - tempHandler.executeCastling(tempCastling); - } - } else if (isEnPassant) { - // For en passant, execute the en passant capture - Enpassant tempEnpassant = new Enpassant(tempBoard); - tempEnpassant.executeEnPassant(fromX, fromY, toX, toY); - } else { - // Remove any piece at destination (normal capture) - tempBoard.removePiece(toX, toY); - - // Move the piece - tempBoard.removePiece(fromX, fromY); - tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); - } + // Remove any piece at destination (normal capture) + tempBoard.removePiece(toX, toY); + + // Move the piece + tempBoard.removePiece(fromX, fromY); + tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); // Check if opponent's king is in check after move - CastlingHandler tempHandler = new CastlingHandler(tempBoard); - return tempHandler.isKingInCheck(!tempPiece.isWhite()); + return isKingInCheck(tempBoard, !tempPiece.isWhite()); } public boolean putsOpponentInCheckmate() { @@ -170,25 +99,12 @@ public class Move { Piece tempPiece = tempBoard.getPieceAt(fromX, fromY); if (tempPiece == null) return false; // Safety check - if (isCastling) { - // For castling, execute the castling move - CastlingHandler tempHandler = new CastlingHandler(tempBoard); - CastlingHandler.CastlingMove tempCastling = tempHandler.getCastlingMove(fromX, fromY, toX, toY); - if (tempCastling != null) { - tempHandler.executeCastling(tempCastling); - } - } else if (isEnPassant) { - // For en passant, execute the en passant capture - Enpassant tempEnpassant = new Enpassant(tempBoard); - tempEnpassant.executeEnPassant(fromX, fromY, toX, toY); - } else { - // Remove any piece at destination (normal capture) - tempBoard.removePiece(toX, toY); - - // Move the piece - tempBoard.removePiece(fromX, fromY); - tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); - } + // Remove any piece at destination (normal capture) + tempBoard.removePiece(toX, toY); + + // Move the piece + tempBoard.removePiece(fromX, fromY); + tempBoard.setPiece(tempPiece.isWhite(), tempPiece.getType(), toX, toY); boolean opponentColor = !tempPiece.isWhite(); @@ -218,23 +134,23 @@ public class Move { switch (piece.getType()) { case Pawn: addPawnMoves(moves, piece, board); - break; + return moves; case Rook: addRookMoves(moves, piece, board); - break; + return moves; case Knight: addKnightMoves(moves, piece, board); - break; + return moves; case Bishop: addBishopMoves(moves, piece, board); - break; + return moves; case Queen: addRookMoves(moves, piece, board); addBishopMoves(moves, piece, board); - break; + return moves; case King: addKingMoves(moves, piece, board); - break; + return moves; } return moves; @@ -257,8 +173,33 @@ public class Move { } public boolean isKingInCheck(Board board, boolean isWhiteKing) { - CastlingHandler handler = new CastlingHandler(board); - return handler.isKingInCheck(isWhiteKing); + // Find the king's position + Piece king = null; + for (Piece p : board.getPieces()) { + if (p.getType() == PieceType.King && p.isWhite() == isWhiteKing) { + king = p; + return checkIfKingUnderAttack(board, isWhiteKing, king); + } + } + + return false; + } + + private boolean checkIfKingUnderAttack(Board board, boolean isWhiteKing, Piece king) { + // Check if any opponent piece can attack the king + for (Piece p : board.getPieces()) { + if (p.isWhite() == isWhiteKing) continue; // Skip pieces of same color + + // Get raw moves without check validation + List attackMoves = getValidDestinations(p, board); + + // If any piece can move to king's position, king is in check + if (attackMoves.contains(new Position(king.getX(), king.getY()))) { + return true; + } + } + + return false; } private void addPawnMoves(List validMoves, Piece piece, Board board) { @@ -266,7 +207,7 @@ public class Move { int y = piece.getY(); boolean isWhite = piece.isWhite(); - int direction = isWhite ? -1 : 1; // White pawns move up (decreasing y), black pawns move down (increasing y) + int direction = isWhite ? -1 : 1; // White pawns move up, black pawns move down // Forward movement int newY = y + direction; @@ -285,7 +226,7 @@ public class Move { } } - // Diagonal captures (regular captures) + // Diagonal captures for (int dx = -1; dx <= 1; dx += 2) { int newX = x + dx; if (newX >= 0 && newX < board.getWidth()) { @@ -296,29 +237,6 @@ public class Move { } } } - - // En passant captures - // Check if there's an opponent pawn adjacent to this pawn that can be captured via en passant - for (int dx = -1; dx <= 1; dx += 2) { // Check left and right - int adjacentX = x + dx; - if (adjacentX >= 0 && adjacentX < board.getWidth()) { - // Check if there's an opponent pawn at the same rank - Piece adjacentPiece = board.getPieceAt(adjacentX, y); - if (adjacentPiece != null && - adjacentPiece.getType() == PieceType.Pawn && - adjacentPiece.isWhite() != isWhite) { - - // Check if this pawn can be captured via en passant - if (board.canCaptureEnPassant(adjacentX, y)) { - // The capture square is diagonally forward from the capturing pawn - int captureY = y + direction; - if (captureY >= 0 && captureY < board.getHeight()) { - validMoves.add(new Position(adjacentX, captureY)); - } - } - } - } - } } private void addRookMoves(List validMoves, Piece piece, Board board) { @@ -428,8 +346,9 @@ public class Move { private void addKingMoves(List validMoves, Piece piece, Board board) { int x = piece.getX(); int y = piece.getY(); + boolean isWhite = piece.isWhite(); - // All adjacent squares (normal king moves) + // All adjacent squares for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; // Skip the current position @@ -440,30 +359,13 @@ public class Move { if (newX >= 0 && newX < board.getWidth() && newY >= 0 && newY < board.getHeight()) { Piece targetPiece = board.getPieceAt(newX, newY); - if (targetPiece == null || targetPiece.isWhite() != piece.isWhite()) { + if (targetPiece == null || targetPiece.isWhite() != isWhite) { // Empty square or can capture opponent's piece validMoves.add(new Position(newX, newY)); } } } } - - // Add castling moves - CastlingHandler handler = new CastlingHandler(board); - List castlingPositions = handler.getCastlingPositions(piece); - for (CastlingHandler.Position castlingPos : castlingPositions) { - validMoves.add(new Position(castlingPos.x, castlingPos.y)); - } - } - - // Helper method to check if a list contains a position - private boolean containsPosition(List positions, Position target) { - for (Position pos : positions) { - if (pos.x == target.x && pos.y == target.y) { - return true; - } - } - return false; } public class Position { @@ -475,6 +377,7 @@ public class Move { this.y = y; } + @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; @@ -482,4 +385,9 @@ public class Move { return x == position.x && y == position.y; } } -} \ No newline at end of file + public int getFromX() { return fromX; } + public int getFromY() { return fromY; } + public int getToX() { return toX; } + public int getToY() { return toY; } + +} diff --git a/OOP_1A2_Project/src/backend/Piece.java b/OOP_1A2_Project/src/backend/Piece.java index e224343..d08b469 100644 --- a/OOP_1A2_Project/src/backend/Piece.java +++ b/OOP_1A2_Project/src/backend/Piece.java @@ -32,4 +32,11 @@ public class Piece { this.x = x; this.y = y; } + public Piece(boolean isWhite, PieceType type, int x, int y) { + this.isWhite = isWhite; + this.type = type; + this.x = x; + this.y = y; + } + }