commit 91da7823ad0e6b30e8eb87b738a56519e4fbafb3 Author: basile.blanchard Date: Tue Apr 22 15:49:04 2025 +0200 final deposit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..81d1c9e Binary files /dev/null and b/.DS_Store differ diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..463d5f9 --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..14798d9 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Project_2 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..9a7984b --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/bin/.DS_Store b/bin/.DS_Store new file mode 100644 index 0000000..dbc1dc9 Binary files /dev/null and b/bin/.DS_Store differ diff --git a/bin/Main.class b/bin/Main.class new file mode 100644 index 0000000..f5ea01d Binary files /dev/null and b/bin/Main.class differ diff --git a/bin/backend/AutoPlayer.class b/bin/backend/AutoPlayer.class new file mode 100644 index 0000000..c69dc03 Binary files /dev/null and b/bin/backend/AutoPlayer.class differ diff --git a/bin/backend/Board$Position.class b/bin/backend/Board$Position.class new file mode 100644 index 0000000..cd4c57a Binary files /dev/null and b/bin/backend/Board$Position.class differ diff --git a/bin/backend/Board.class b/bin/backend/Board.class new file mode 100644 index 0000000..379f562 Binary files /dev/null and b/bin/backend/Board.class differ diff --git a/bin/backend/ChessTimer$1.class b/bin/backend/ChessTimer$1.class new file mode 100644 index 0000000..1975124 Binary files /dev/null and b/bin/backend/ChessTimer$1.class differ diff --git a/bin/backend/ChessTimer$TimerUpdateListener.class b/bin/backend/ChessTimer$TimerUpdateListener.class new file mode 100644 index 0000000..0cb8f19 Binary files /dev/null and b/bin/backend/ChessTimer$TimerUpdateListener.class differ diff --git a/bin/backend/ChessTimer.class b/bin/backend/ChessTimer.class new file mode 100644 index 0000000..a590880 Binary files /dev/null and b/bin/backend/ChessTimer.class differ diff --git a/bin/backend/Game$1.class b/bin/backend/Game$1.class new file mode 100644 index 0000000..9057994 Binary files /dev/null and b/bin/backend/Game$1.class differ diff --git a/bin/backend/Game.class b/bin/backend/Game.class new file mode 100644 index 0000000..162fa6e Binary files /dev/null and b/bin/backend/Game.class differ diff --git a/bin/backend/Move.class b/bin/backend/Move.class new file mode 100644 index 0000000..c2730bd Binary files /dev/null and b/bin/backend/Move.class differ diff --git a/bin/backend/MoveHistory.class b/bin/backend/MoveHistory.class new file mode 100644 index 0000000..4170f59 Binary files /dev/null and b/bin/backend/MoveHistory.class differ diff --git a/bin/backend/Piece.class b/bin/backend/Piece.class new file mode 100644 index 0000000..65ae2a4 Binary files /dev/null and b/bin/backend/Piece.class differ diff --git a/bin/backend/PieceType.class b/bin/backend/PieceType.class new file mode 100644 index 0000000..c969af8 Binary files /dev/null and b/bin/backend/PieceType.class differ diff --git a/bin/windowInterface/JPanelChessBoard$1.class b/bin/windowInterface/JPanelChessBoard$1.class new file mode 100644 index 0000000..717b325 Binary files /dev/null and b/bin/windowInterface/JPanelChessBoard$1.class differ diff --git a/bin/windowInterface/JPanelChessBoard.class b/bin/windowInterface/JPanelChessBoard.class new file mode 100644 index 0000000..1090ea1 Binary files /dev/null and b/bin/windowInterface/JPanelChessBoard.class differ diff --git a/bin/windowInterface/MyInterface$1.class b/bin/windowInterface/MyInterface$1.class new file mode 100644 index 0000000..9c35eb6 Binary files /dev/null and b/bin/windowInterface/MyInterface$1.class differ diff --git a/bin/windowInterface/MyInterface$10.class b/bin/windowInterface/MyInterface$10.class new file mode 100644 index 0000000..bb3f514 Binary files /dev/null and b/bin/windowInterface/MyInterface$10.class differ diff --git a/bin/windowInterface/MyInterface$2.class b/bin/windowInterface/MyInterface$2.class new file mode 100644 index 0000000..9a02065 Binary files /dev/null and b/bin/windowInterface/MyInterface$2.class differ diff --git a/bin/windowInterface/MyInterface$3.class b/bin/windowInterface/MyInterface$3.class new file mode 100644 index 0000000..ed184d2 Binary files /dev/null and b/bin/windowInterface/MyInterface$3.class differ diff --git a/bin/windowInterface/MyInterface$4.class b/bin/windowInterface/MyInterface$4.class new file mode 100644 index 0000000..b2b7072 Binary files /dev/null and b/bin/windowInterface/MyInterface$4.class differ diff --git a/bin/windowInterface/MyInterface$5.class b/bin/windowInterface/MyInterface$5.class new file mode 100644 index 0000000..600ae77 Binary files /dev/null and b/bin/windowInterface/MyInterface$5.class differ diff --git a/bin/windowInterface/MyInterface$6.class b/bin/windowInterface/MyInterface$6.class new file mode 100644 index 0000000..2306f9d Binary files /dev/null and b/bin/windowInterface/MyInterface$6.class differ diff --git a/bin/windowInterface/MyInterface$7.class b/bin/windowInterface/MyInterface$7.class new file mode 100644 index 0000000..3843183 Binary files /dev/null and b/bin/windowInterface/MyInterface$7.class differ diff --git a/bin/windowInterface/MyInterface$8.class b/bin/windowInterface/MyInterface$8.class new file mode 100644 index 0000000..767e31c Binary files /dev/null and b/bin/windowInterface/MyInterface$8.class differ diff --git a/bin/windowInterface/MyInterface$9.class b/bin/windowInterface/MyInterface$9.class new file mode 100644 index 0000000..5e786c4 Binary files /dev/null and b/bin/windowInterface/MyInterface$9.class differ diff --git a/bin/windowInterface/MyInterface.class b/bin/windowInterface/MyInterface.class new file mode 100644 index 0000000..bd02fed Binary files /dev/null and b/bin/windowInterface/MyInterface.class differ diff --git a/default.board b/default.board new file mode 100644 index 0000000..8d83268 --- /dev/null +++ b/default.board @@ -0,0 +1,9 @@ +BR,BN,BB,BQ,BK,BB,BN,BR +BP,BP,BP,BP,BP,BP,BP,BP + , , , , , , , + , , , , , , , + , , , , , , , + , , , , , , , +WP,WP,WP,WP,WP,WP,WP,WP +WR,WN,WB,WQ,WK,WB,WN,WR +W diff --git a/pieces.png b/pieces.png new file mode 100644 index 0000000..01cc2f8 Binary files /dev/null and b/pieces.png differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..dbc1dc9 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..7732cc8 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,19 @@ +import backend.Board; +import backend.Move; +import backend.Piece; +import backend.PieceType; +import windowInterface.MyInterface; + +public class Main { + public static void main(String[] args) { + // testing : + Board testBoard = new Board(8, 8); + testBoard.populateBoard(); + System.out.println(testBoard.toString()); + + // launches graphical interface : + MyInterface mjf = new MyInterface(); + mjf.setVisible(true); + } + +} \ No newline at end of file diff --git a/src/backend/AutoPlayer.java b/src/backend/AutoPlayer.java new file mode 100644 index 0000000..3e67f52 --- /dev/null +++ b/src/backend/AutoPlayer.java @@ -0,0 +1,10 @@ +package backend; + +public class AutoPlayer { + + public Move computeBestMove(Board board) { + // This will be implemented in Part 4 + // For now, just return null or a placeholder move + return null; + } +} \ No newline at end of file diff --git a/src/backend/Board.java b/src/backend/Board.java new file mode 100644 index 0000000..9b876b0 --- /dev/null +++ b/src/backend/Board.java @@ -0,0 +1,621 @@ +package backend; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class Board { + private int width; + private int height; + private Piece[][] pieces; // 2D array to represent the chess board + private int turnNumber; + private boolean isWhiteTurn; + private int selectedX; + private int selectedY; + private boolean hasSelection; + private Set validMoves; // Set of valid positions for the selected piece + private MoveHistory moveHistory; // Added move history + + /** + * Position class to store x,y coordinates and enable using them in HashSet + */ + private static class Position { + private final int x; + private final int y; + + public Position(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Position position = (Position) obj; + return x == position.x && y == position.y; + } + + @Override + public int hashCode() { + return 31 * x + y; + } + } + + /** + * Constructor for creating a new chess board + * @param width Width of the board + * @param height Height of the board + */ + public Board(int width, int height) { + this.width = width; + this.height = height; + this.pieces = new Piece[width][height]; + this.turnNumber = 0; + this.isWhiteTurn = true; // White goes first in chess + this.hasSelection = false; + this.selectedX = -1; + this.selectedY = -1; + this.validMoves = new HashSet<>(); + this.moveHistory = new MoveHistory(); // Initialize move history + } + + /** + * @return Width of the chess board + */ + public int getWidth() { + return width; + } + + /** + * @return Height of the chess board + */ + public int getHeight() { + return height; + } + + /** + * @return Current turn number + */ + public int getTurnNumber() { + return turnNumber; + } + + /** + * @return True if it's white's turn, false if it's black's turn + */ + public boolean isTurnWhite() { + return isWhiteTurn; + } + + /** + * Places a piece of specified type and color at the given position + * @param isWhite True for white piece, false for black piece + * @param type Type of piece (Pawn, Rook, etc.) + * @param x X-coordinate + * @param y Y-coordinate + */ + public void setPiece(boolean isWhite, PieceType type, int x, int y) { + if (x >= 0 && x < width && y >= 0 && y < height) { + pieces[x][y] = new Piece(x, y, type, isWhite); + } + } + + /** + * Sets up the board with the standard chess starting position + */ + public void populateBoard() { + // Place pawns + for (int x = 0; x < width; x++) { + setPiece(true, PieceType.Pawn, x, 6); // White pawns + setPiece(false, PieceType.Pawn, x, 1); // Black pawns + } + + // Place Rooks + setPiece(true, PieceType.Rook, 0, 7); + setPiece(true, PieceType.Rook, 7, 7); + setPiece(false, PieceType.Rook, 0, 0); + setPiece(false, PieceType.Rook, 7, 0); + + // Place Knights + setPiece(true, PieceType.Knight, 1, 7); + setPiece(true, PieceType.Knight, 6, 7); + setPiece(false, PieceType.Knight, 1, 0); + setPiece(false, PieceType.Knight, 6, 0); + + // Place Bishops + setPiece(true, PieceType.Bishop, 2, 7); + setPiece(true, PieceType.Bishop, 5, 7); + setPiece(false, PieceType.Bishop, 2, 0); + setPiece(false, PieceType.Bishop, 5, 0); + + // Place Queens + setPiece(true, PieceType.Queen, 3, 7); + setPiece(false, PieceType.Queen, 3, 0); + + // Place Kings + setPiece(true, PieceType.King, 4, 7); + setPiece(false, PieceType.King, 4, 0); + + // Reset turn information + turnNumber = 0; + isWhiteTurn = true; + + // Clear selection and valid moves + clearSelection(); + moveHistory.clear(); // Clear move history when starting a new game + } + + /** + * Removes all pieces from the board + */ + public void cleanBoard() { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + pieces[x][y] = null; + } + } + turnNumber = 0; + isWhiteTurn = true; + clearSelection(); + moveHistory.clear(); // Clear move history when cleaning the board + } + + /** + * @return String representation of the board + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Turn: ").append(turnNumber).append(", ").append(isWhiteTurn ? "White" : "Black").append("\n"); + + for (int y = 0; y < height; y++) { + sb.append(8 - y).append(" "); + for (int x = 0; x < width; x++) { + if (pieces[x][y] == null) { + sb.append(((x + y) % 2 == 0) ? "." : " "); + } else { + Piece p = pieces[x][y]; + char c = p.getType().getSummary().charAt(0); + sb.append(p.isWhite() ? Character.toUpperCase(c) : Character.toLowerCase(c)); + } + sb.append(" "); + } + sb.append("\n"); + } + sb.append(" a b c d e f g h"); + return sb.toString(); + } + + /** + * @return List of all pieces currently on the board + */ + public ArrayList getPieces() { + ArrayList piecesList = new ArrayList<>(); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + if (pieces[x][y] != null) { + piecesList.add(pieces[x][y]); + } + } + } + return piecesList; + } + + /** + * Handles user clicks on the board + * @param x X-coordinate + * @param y Y-coordinate + */ + public void userTouch(int x, int y) { + if (x < 0 || x >= width || y < 0 || y >= height) { + return; // Out of bounds + } + + if (!hasSelection) { + // If no piece is selected yet, select the piece at the clicked position + if (pieces[x][y] != null && pieces[x][y].isWhite() == isWhiteTurn) { + // Select the piece and calculate valid moves + selectedX = x; + selectedY = y; + hasSelection = true; + calculateValidMoves(); + } + } else { + // If a piece is already selected + if (selectedX == x && selectedY == y) { + // If the same position is clicked again, unselect it + clearSelection(); + } else if (isHighlighted(x, y)) { + // Move the selected piece to the new position if it's a valid move + Piece selectedPiece = pieces[selectedX][selectedY]; + Piece capturedPiece = pieces[x][y]; // Store captured piece (null if no capture) + + // Create a Move object to record this move + Move move = new Move(selectedX, selectedY, x, y, selectedPiece, capturedPiece); + + // Move the piece (captures any piece at destination) + pieces[x][y] = selectedPiece; + pieces[selectedX][selectedY] = null; + + // Update the piece's position + selectedPiece.setPosition(x, y); + + // Record the move in history + moveHistory.recordMove(move); + + // Update turn information + turnNumber++; + isWhiteTurn = !isWhiteTurn; + + // Reset selection and valid moves + clearSelection(); + } else if (pieces[x][y] != null && pieces[x][y].isWhite() == isWhiteTurn) { + // If clicked on another piece of the same color, select it instead + selectedX = x; + selectedY = y; + hasSelection = true; + calculateValidMoves(); + } else { + // Clicked on an invalid position, unselect + clearSelection(); + } + } + } + + /** + * Clears selection and valid moves + */ + private void clearSelection() { + hasSelection = false; + selectedX = -1; + selectedY = -1; + validMoves.clear(); + } + + /** + * Calculates valid moves for the currently selected piece + */ + private void calculateValidMoves() { + validMoves.clear(); + + if (!hasSelection) return; + + Piece piece = pieces[selectedX][selectedY]; + if (piece == null) return; + + switch (piece.getType()) { + case Pawn: + calculatePawnMoves(piece); + break; + case Rook: + calculateRookMoves(piece); + break; + case Knight: + calculateKnightMoves(piece); + break; + case Bishop: + calculateBishopMoves(piece); + break; + case Queen: + calculateQueenMoves(piece); + break; + case King: + calculateKingMoves(piece); + break; + } + } + + /** + * Calculate valid moves for a pawn + * @param piece The pawn piece + */ + private void calculatePawnMoves(Piece piece) { + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + // Direction is -1 for white (moving up) and 1 for black (moving down) + int direction = isWhite ? -1 : 1; + int startingRow = isWhite ? 6 : 1; + + // Move forward one square + if (isInsideBoard(x, y + direction) && pieces[x][y + direction] == null) { + validMoves.add(new Position(x, y + direction)); + + // Move forward two squares from starting position + if (y == startingRow && isInsideBoard(x, y + 2 * direction) && pieces[x][y + 2 * direction] == null) { + validMoves.add(new Position(x, y + 2 * direction)); + } + } + + // Capture diagonally + for (int dx = -1; dx <= 1; dx += 2) { + int newX = x + dx; + int newY = y + direction; + + if (isInsideBoard(newX, newY) && pieces[newX][newY] != null && pieces[newX][newY].isWhite() != isWhite) { + validMoves.add(new Position(newX, newY)); + } + } + } + + /** + * Calculate valid moves for a rook + * @param piece The rook piece + */ + private void calculateRookMoves(Piece piece) { + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + // Four directions: horizontal and vertical + int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + + for (int[] dir : directions) { + int dx = dir[0]; + int dy = dir[1]; + + for (int step = 1; step < Math.max(width, height); step++) { + int newX = x + dx * step; + int newY = y + dy * step; + + if (!isInsideBoard(newX, newY)) break; // Out of bounds + + if (pieces[newX][newY] == null) { + // Empty square, can move here + validMoves.add(new Position(newX, newY)); + } else { + // Square has a piece + if (pieces[newX][newY].isWhite() != isWhite) { + // Can capture opponent's piece + validMoves.add(new Position(newX, newY)); + } + break; // Can't move past a piece + } + } + } + } + + /** + * Calculate valid moves for a knight + * @param piece The knight piece + */ + private void calculateKnightMoves(Piece piece) { + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + // All possible knight moves + int[][] moves = { + {-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, + {1, -2}, {1, 2}, {2, -1}, {2, 1} + }; + + for (int[] move : moves) { + int newX = x + move[0]; + int newY = y + move[1]; + + if (isInsideBoard(newX, newY) && (pieces[newX][newY] == null || pieces[newX][newY].isWhite() != isWhite)) { + validMoves.add(new Position(newX, newY)); + } + } + } + + /** + * Calculate valid moves for a bishop + * @param piece The bishop piece + */ + private void calculateBishopMoves(Piece piece) { + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + // Four diagonal directions + int[][] directions = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}}; + + for (int[] dir : directions) { + int dx = dir[0]; + int dy = dir[1]; + + for (int step = 1; step < Math.max(width, height); step++) { + int newX = x + dx * step; + int newY = y + dy * step; + + if (!isInsideBoard(newX, newY)) break; // Out of bounds + + if (pieces[newX][newY] == null) { + // Empty square, can move here + validMoves.add(new Position(newX, newY)); + } else { + // Square has a piece + if (pieces[newX][newY].isWhite() != isWhite) { + // Can capture opponent's piece + validMoves.add(new Position(newX, newY)); + } + break; // Can't move past a piece + } + } + } + } + + /** + * Calculate valid moves for a queen + * @param piece The queen piece + */ + private void calculateQueenMoves(Piece piece) { + // Queen combines rook and bishop movement + calculateRookMoves(piece); + calculateBishopMoves(piece); + } + + /** + * Calculate valid moves for a king + * @param piece The king piece + */ + private void calculateKingMoves(Piece piece) { + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + // All 8 directions around the king + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; // Skip the current position + + int newX = x + dx; + int newY = y + dy; + + if (isInsideBoard(newX, newY) && (pieces[newX][newY] == null || pieces[newX][newY].isWhite() != isWhite)) { + validMoves.add(new Position(newX, newY)); + } + } + } + } + + /** + * Check if coordinates are inside the board + * @param x X-coordinate + * @param y Y-coordinate + * @return True if inside board, false otherwise + */ + private boolean isInsideBoard(int x, int y) { + return x >= 0 && x < width && y >= 0 && y < height; + } + + /** + * Checks if a position is currently selected + * @param x X-coordinate + * @param y Y-coordinate + * @return True if the position is selected, false otherwise + */ + public boolean isSelected(int x, int y) { + return hasSelection && selectedX == x && selectedY == y; + } + + /** + * Checks if a position is highlighted as a valid move + * @param x X-coordinate + * @param y Y-coordinate + * @return True if the position is highlighted, false otherwise + */ + public boolean isHighlighted(int x, int y) { + return validMoves.contains(new Position(x, y)); + } + + /** + * Undoes the last move made + */ + public void undoLastMove() { + if (!moveHistory.canUndo()) { + return; // No moves to undo + } + + // Get the last move + Move lastMove = moveHistory.getLastMove(); + + // Get the moving piece (which is now at the destination) + Piece movingPiece = pieces[lastMove.getToX()][lastMove.getToY()]; + + if (movingPiece == null) { + // Something went wrong, the piece isn't where it should be + return; + } + + // Move the piece back to its original position + pieces[lastMove.getFromX()][lastMove.getFromY()] = movingPiece; + movingPiece.setPosition(lastMove.getFromX(), lastMove.getFromY()); + + // If a piece was captured, put it back + if (lastMove.getCapturedPiece() != null) { + Piece capturedPiece = lastMove.getCapturedPiece(); + pieces[lastMove.getToX()][lastMove.getToY()] = capturedPiece; + } else { + // Otherwise, clear the destination square + pieces[lastMove.getToX()][lastMove.getToY()] = null; + } + + // Revert turn information + turnNumber--; + isWhiteTurn = !isWhiteTurn; + + // Clear any selection and highlighting + clearSelection(); + } + + /** + * Constructor for loading a board from a file + * @param array Array of strings representing the board + */ + public Board(String[] array) { + this(8, 8); // Default to standard 8x8 board + // The rest will be implemented in part 3 + } + + /** + * Constructor for copying a board + * @param board Board to copy + */ + public Board(Board board) { + this(board.getWidth(), board.getHeight()); + + this.turnNumber = board.getTurnNumber(); + this.isWhiteTurn = board.isTurnWhite(); + + // Copy pieces + for (Piece piece : board.getPieces()) { + this.setPiece(piece.isWhite(), piece.getType(), piece.getX(), piece.getY()); + } + } + + /** + * Placeholder for saving the board to a file + * @return Array of strings representing the board + */ + public String[] toFileRep() { + // This will be implemented in part 3 + return null; + } + + /** + * Plays a move on the board + * @param move Move to play + */ + public void playMove(Move move) { + if (move == null) { + return; + } + + int fromX = move.getFromX(); + int fromY = move.getFromY(); + int toX = move.getToX(); + int toY = move.getToY(); + + // Make sure there's a piece at the starting position + if (pieces[fromX][fromY] == null) { + return; + } + + // Store the piece that might be captured + Piece capturedPiece = pieces[toX][toY]; + + // Update the move object with the actual pieces + move = new Move(fromX, fromY, toX, toY, pieces[fromX][fromY], capturedPiece); + + // Make the move + pieces[toX][toY] = pieces[fromX][fromY]; + pieces[fromX][fromY] = null; + + // Update the piece's position + pieces[toX][toY].setPosition(toX, toY); + + // Record the move + moveHistory.recordMove(move); + + // Update turn information + turnNumber++; + isWhiteTurn = !isWhiteTurn; + + // Clear any selection + clearSelection(); + } +} \ No newline at end of file diff --git a/src/backend/ChessTimer.java b/src/backend/ChessTimer.java new file mode 100644 index 0000000..7aba328 --- /dev/null +++ b/src/backend/ChessTimer.java @@ -0,0 +1,166 @@ +package backend; + +import java.util.Timer; +import java.util.TimerTask; + +public class ChessTimer { + private long whiteTimeMillis; // Time remaining for white player in milliseconds + private long blackTimeMillis; // Time remaining for black player in milliseconds + private long lastUpdateTime; // Time when the timer was last updated + private boolean isRunning; // Is the timer currently running? + private boolean isWhiteTurn; // Is it white's turn? + private Timer timer; // Java util timer to handle periodic updates + private TimerUpdateListener listener; // Listener to notify UI when time changes + + /** + * Interface for notifying time updates + */ + public interface TimerUpdateListener { + void onTimeUpdate(long whiteTimeMillis, long blackTimeMillis); + void onTimeExpired(boolean isWhiteExpired); // Called when a player's time runs out + } + + /** + * Constructor with initial time in minutes + * @param initialTimeMinutes Initial time for both players in minutes + * @param listener Listener to receive time updates + */ + public ChessTimer(int initialTimeMinutes, TimerUpdateListener listener) { + this.whiteTimeMillis = initialTimeMinutes * 60 * 1000; + this.blackTimeMillis = initialTimeMinutes * 60 * 1000; + this.isRunning = false; + this.isWhiteTurn = true; // White starts in chess + this.listener = listener; + } + + /** + * Start the timer + */ + public void start() { + if (!isRunning) { + isRunning = true; + lastUpdateTime = System.currentTimeMillis(); + + // Schedule periodic updates (every 100ms) + timer = new Timer(true); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + updateTime(); + } + }, 0, 100); + } + } + + /** + * Stop the timer + */ + public void stop() { + if (isRunning) { + isRunning = false; + updateTime(); // Update one last time + + if (timer != null) { + timer.cancel(); + timer = null; + } + } + } + + /** + * Reset the timer to the initial values + * @param initialTimeMinutes New initial time in minutes + */ + public void reset(int initialTimeMinutes) { + stop(); + this.whiteTimeMillis = initialTimeMinutes * 60 * 1000; + this.blackTimeMillis = initialTimeMinutes * 60 * 1000; + this.isWhiteTurn = true; + notifyTimeUpdate(); + } + + /** + * Switch the active player + * @param isWhiteTurn True if it's now white's turn + */ + public void switchTurn(boolean isWhiteTurn) { + if (isRunning) { + updateTime(); // Update time for the player who just completed their move + } + this.isWhiteTurn = isWhiteTurn; + notifyTimeUpdate(); + } + + /** + * Update the time based on elapsed time + */ + private void updateTime() { + if (!isRunning) return; + + long currentTime = System.currentTimeMillis(); + long elapsedTime = currentTime - lastUpdateTime; + lastUpdateTime = currentTime; + + // Deduct time from the active player + if (isWhiteTurn) { + whiteTimeMillis -= elapsedTime; + if (whiteTimeMillis <= 0) { + whiteTimeMillis = 0; + stop(); + if (listener != null) { + listener.onTimeExpired(true); + } + } + } else { + blackTimeMillis -= elapsedTime; + if (blackTimeMillis <= 0) { + blackTimeMillis = 0; + stop(); + if (listener != null) { + listener.onTimeExpired(false); + } + } + } + + notifyTimeUpdate(); + } + + /** + * Notify the listener about time updates + */ + private void notifyTimeUpdate() { + if (listener != null) { + listener.onTimeUpdate(whiteTimeMillis, blackTimeMillis); + } + } + + /** + * Get white player's remaining time in milliseconds + */ + public long getWhiteTimeMillis() { + return whiteTimeMillis; + } + + /** + * Get black player's remaining time in milliseconds + */ + public long getBlackTimeMillis() { + return blackTimeMillis; + } + + /** + * Format milliseconds as "mm:ss" + */ + public static String formatTime(long timeMillis) { + int seconds = (int) (timeMillis / 1000) % 60; + int minutes = (int) (timeMillis / (60 * 1000)); + return String.format("%02d:%02d", minutes, seconds); + } + + /** + * Check if the timer is currently running + */ + public boolean isRunning() { + return isRunning; + } +} \ No newline at end of file diff --git a/src/backend/Game.java b/src/backend/Game.java new file mode 100644 index 0000000..ab05009 --- /dev/null +++ b/src/backend/Game.java @@ -0,0 +1,220 @@ +package backend; + +import windowInterface.MyInterface; + +public class Game extends Thread { + + private AutoPlayer aiPlayer; + private Board board; + private ChessTimer chessTimer; // Add chess timer + private MyInterface mjf; + private int COL_NUM = 8; + private int LINE_NUM = 8; + private int loopDelay = 250; + boolean[] activationAIFlags; + private int initialTimeMinutes = 10; // Default to 10 minutes per player + private boolean timerEnabled = false; // Flag to enable/disable timer + + public Game(MyInterface mjfParam) { + mjf = mjfParam; + board = new Board(COL_NUM, LINE_NUM); + loopDelay = 250; + LINE_NUM = 8; + COL_NUM = 8; + activationAIFlags = new boolean[2]; + aiPlayer = new AutoPlayer(); + + // Initialize the timer with a listener + chessTimer = new ChessTimer(initialTimeMinutes, new ChessTimer.TimerUpdateListener() { + @Override + public void onTimeUpdate(long whiteTimeMillis, long blackTimeMillis) { + // Update UI with new time values + mjf.updateTimers(whiteTimeMillis, blackTimeMillis); + } + + @Override + public void onTimeExpired(boolean isWhiteExpired) { + // Handle time expiration - game over + mjf.timeExpired(isWhiteExpired); + } + }); + } + + public int getWidth() { + return board.getWidth(); + } + + public int getHeight() { + return board.getHeight(); + } + + // Added getBoard method to access the board + public Board getBoard() { + return this.board; + } + + public void run() { + while(true) { + aiPlayerTurn(); + mjf.update(board.getTurnNumber(), board.isTurnWhite()); + try { + Thread.sleep(loopDelay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private boolean isAITurn() { + return activationAIFlags[board.isTurnWhite()?1:0]; + } + + private void aiPlayerTurn() { + if(isAITurn()) { + // Before AI makes a move + boolean currentTurn = board.isTurnWhite(); + + board.playMove(aiPlayer.computeBestMove(new Board(board))); + + // After AI makes a move, switch the timer + if (timerEnabled && currentTurn != board.isTurnWhite()) { + chessTimer.switchTurn(board.isTurnWhite()); + } + } + } + + public void clickCoords(int x, int y) { + int width = this.getWidth(); + int height = this.getHeight(); + if(0>x || 0>y || x>width || y>height) { + System.out.println("Click out of bounds"); + return; + } + if(!isAITurn()) { + // Before user makes a move + boolean currentTurn = board.isTurnWhite(); + + board.userTouch(x, y); + + // After user makes a move, check if turn switched and update timer + if (timerEnabled && currentTurn != board.isTurnWhite()) { + chessTimer.switchTurn(board.isTurnWhite()); + } + } + } + + public void setPiece(boolean isWhite, PieceType type, int x, int y) { + board.setPiece(isWhite, type, x, y); + } + + public String[] getFileRepresentation() { + return board.toFileRep(); + } + + public void setLoopDelay(int delay) { + this.loopDelay = delay; + } + + public void setDefaultSetup() { + board.cleanBoard(); + board.populateBoard(); + + // Reset the timer when starting a new game + if (timerEnabled) { + chessTimer.reset(initialTimeMinutes); + } + } + + public void setBoard(String[] array) { + board = new Board(array); + + // Reset the timer when loading a new board + if (timerEnabled) { + chessTimer.reset(initialTimeMinutes); + } + } + + public Iterable getPieces() { + return board.getPieces(); + } + + public boolean isSelected(int x, int y) { + return board.isSelected(x, y); + } + + public boolean isHighlighted(int x, int y) { + return board.isHighlighted(x, y); + } + + public void undoLastMove() { + // Store current turn before undo + boolean currentTurn = board.isTurnWhite(); + + board.undoLastMove(); + + // After undo, check if turn switched and update timer + if (timerEnabled && currentTurn != board.isTurnWhite()) { + chessTimer.switchTurn(board.isTurnWhite()); + } + } + + public void toggleAI(boolean isWhite) { + this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0]; + } + + // Timer-related methods + + /** + * Start the chess timer + */ + public void startTimer() { + if (timerEnabled) { + chessTimer.start(); + } + } + + /** + * Stop the chess timer + */ + public void stopTimer() { + if (timerEnabled) { + chessTimer.stop(); + } + } + + /** + * Enable or disable the timer feature + */ + public void setTimerEnabled(boolean enabled) { + this.timerEnabled = enabled; + if (enabled) { + chessTimer.reset(initialTimeMinutes); + } else { + chessTimer.stop(); + } + } + + /** + * Check if timer is enabled + */ + public boolean isTimerEnabled() { + return timerEnabled; + } + + /** + * Set the initial time in minutes for both players + */ + public void setInitialTime(int minutes) { + this.initialTimeMinutes = minutes; + if (timerEnabled) { + chessTimer.reset(minutes); + } + } + + /** + * Get the current chess timer + */ + public ChessTimer getChessTimer() { + return chessTimer; + } +} \ No newline at end of file diff --git a/src/backend/Move.java b/src/backend/Move.java new file mode 100644 index 0000000..a44958f --- /dev/null +++ b/src/backend/Move.java @@ -0,0 +1,64 @@ +package backend; + +public class Move { + private int fromX; + private int fromY; + private int toX; + private int toY; + private Piece movingPiece; + private Piece capturedPiece; + + /** + * Constructor for a move + * @param fromX Starting X-coordinate + * @param fromY Starting Y-coordinate + * @param toX Destination X-coordinate + * @param toY Destination Y-coordinate + * @param movingPiece The piece being moved + * @param capturedPiece The piece being captured (null if no capture) + */ + public Move(int fromX, int fromY, int toX, int toY, Piece movingPiece, Piece capturedPiece) { + this.fromX = fromX; + this.fromY = fromY; + this.toX = toX; + this.toY = toY; + this.movingPiece = movingPiece; + this.capturedPiece = capturedPiece; + } + + // Basic constructor for now - can be expanded later + public Move() { + this.fromX = -1; + this.fromY = -1; + this.toX = -1; + this.toY = -1; + this.movingPiece = null; + this.capturedPiece = null; + } + + // Getters + public int getFromX() { return fromX; } + public int getFromY() { return fromY; } + public int getToX() { return toX; } + public int getToY() { return toY; } + public Piece getMovingPiece() { return movingPiece; } + public Piece getCapturedPiece() { return capturedPiece; } + + // Setters if needed + public void setFromX(int fromX) { this.fromX = fromX; } + public void setFromY(int fromY) { this.fromY = fromY; } + public void setToX(int toX) { this.toX = toX; } + public void setToY(int toY) { this.toY = toY; } + public void setMovingPiece(Piece movingPiece) { this.movingPiece = movingPiece; } + public void setCapturedPiece(Piece capturedPiece) { this.capturedPiece = capturedPiece; } + + @Override + public String toString() { + if (movingPiece == null) { + return "Empty move"; + } + String from = (char)('a' + fromX) + "" + (8 - fromY); + String to = (char)('a' + toX) + "" + (8 - toY); + return movingPiece.getType().getSummary() + from + "-" + to; + } +} \ No newline at end of file diff --git a/src/backend/MoveHistory.java b/src/backend/MoveHistory.java new file mode 100644 index 0000000..c90b527 --- /dev/null +++ b/src/backend/MoveHistory.java @@ -0,0 +1,55 @@ +package backend; + +import java.util.Stack; + +/** + * Class to maintain a history of moves made during the game + * and enable undo functionality + */ +public class MoveHistory { + private Stack moveStack; + + /** + * Constructor for MoveHistory + */ + public MoveHistory() { + moveStack = new Stack<>(); + } + + /** + * Records a move in the history + * + * @param move The move to record + */ + public void recordMove(Move move) { + moveStack.push(move); + } + + /** + * Gets the last move made and removes it from history + * + * @return The last move made, or null if no moves have been made + */ + public Move getLastMove() { + if (moveStack.isEmpty()) { + return null; + } + return moveStack.pop(); + } + + /** + * Checks if there are moves to undo + * + * @return True if there are moves in the history, false otherwise + */ + public boolean canUndo() { + return !moveStack.isEmpty(); + } + + /** + * Clears the move history + */ + public void clear() { + moveStack.clear(); + } +} \ No newline at end of file diff --git a/src/backend/Piece.java b/src/backend/Piece.java new file mode 100644 index 0000000..ef34f35 --- /dev/null +++ b/src/backend/Piece.java @@ -0,0 +1,36 @@ +package backend; + +public class Piece { + private int x; + private int y; + private PieceType type; + private boolean isWhite; + + public Piece(int x, int y, PieceType type, boolean isWhite) { + this.x = x; + this.y = y; + this.type = type; + this.isWhite = isWhite; + } + + public PieceType getType() { + return type; + } + + public boolean isWhite() { + return isWhite; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public void setPosition(int x, int y) { + this.x = x; + this.y = y; + } +} \ No newline at end of file diff --git a/src/backend/PieceType.java b/src/backend/PieceType.java new file mode 100644 index 0000000..382a02a --- /dev/null +++ b/src/backend/PieceType.java @@ -0,0 +1,26 @@ +package backend; + +public enum PieceType { + Pawn("Pawn", "p"), + Rook("Rook", "r"), + Knight("Knight", "n"), + Bishop("Bishop", "b"), + Queen("Queen", "q"), + King("King", "k"); + + private final String name; + private final String summary; + + PieceType(String name, String summary) { + this.name = name; + this.summary = summary; + } + + public String getName() { + return name; + } + + public String getSummary() { + return summary; + } +} \ No newline at end of file diff --git a/src/windowInterface/JPanelChessBoard.java b/src/windowInterface/JPanelChessBoard.java new file mode 100644 index 0000000..ad260b1 --- /dev/null +++ b/src/windowInterface/JPanelChessBoard.java @@ -0,0 +1,196 @@ +package windowInterface; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JPanel; + +import backend.Game; +import backend.Piece; +import backend.PieceType; + +public class JPanelChessBoard extends JPanel { + + private static final long serialVersionUID = 1L; + private Game myGame; + private MyInterface interfaceGlobal; + private BufferedImage spriteSheet; + private int PIECE_WIDTH = 16; //in spritesheet + private int PIECE_HEIGHT = 16; //in spritesheet + private int MARGIN = 6; + + private boolean pieceSelectorMode; + private boolean selectedPieceIsWhite; + private PieceType selectedPieceType; + private boolean pieceAdderMode; + + public JPanelChessBoard(MyInterface itf) { + super(); + myGame = null; + interfaceGlobal = itf; + selectedPieceIsWhite = true; + selectedPieceType = PieceType.Pawn; + pieceSelectorMode = false; + try { + spriteSheet = ImageIO.read(new File("pieces.png")); + } catch (IOException e) { + e.printStackTrace(); + } + pieceSelectorMode = false; + pieceAdderMode = false; + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent me) { + // System.out.println(me); + if(pieceSelectorMode) { + int x = Math.round(me.getX()/cellWidth()); + selectedPieceType = PieceType.values()[5-x]; + selectedPieceIsWhite = (me.getY() > cellHeight()); + pieceSelectorMode = false; + } else { + if(myGame == null) { + interfaceGlobal.instantiateSimu(); + } + int x = (me.getX()*myGame.getWidth())/getWidth(); + int y = (me.getY()*myGame.getHeight())/getHeight(); + if(pieceAdderMode) { + //TODO + myGame.setPiece(selectedPieceIsWhite,selectedPieceType, x, y); + pieceAdderMode = false; + } else { + myGame.clickCoords(x,y); + } + } + repaint(); + } + }); + } + + + public void setGame(Game simu) { + myGame = simu; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + this.setBackground(Color.black); + if(pieceSelectorMode) { + g.drawImage( + spriteSheet, + 0, + 0, + Math.round(5*cellWidth()), + Math.round(2*cellHeight()), + null + ); + return; + } + if (myGame != null) { + // Draw Interface from state of simulator + float cellWidth = cellWidth(); + float cellHeight = cellHeight(); + + g.setColor(Color.white); + for(int x=0; x lines = new LinkedList(); + if (fileName.length()>0) { + try { + BufferedReader fileContent = new BufferedReader(new FileReader(fileName)); + String line = fileContent.readLine(); + int colorID = 0; + while (line != null) { + lines.add(line); + line = fileContent.readLine(); + } + loadedSim.setBoard(Arrays.stream(lines.toArray()).map(Object::toString).toArray(String[]::new)); + fileContent.close(); + } catch (Exception e) { + e.printStackTrace(); + } + game = loadedSim; + panelDraw.setGame(game); + this.repaint(); + } + } + + public void clicSaveToFileButton() { + String fileName=SelectFile(); + if (fileName.length()>0) { + String[] content = game.getFileRepresentation(); + writeFile(fileName, content); + } + } + + public String SelectFile() { + String s; + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new java.io.File(".")); + chooser.setDialogTitle("Choose a file"); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setAcceptAllFileFilterUsed(true); + if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + s=chooser.getSelectedFile().toString(); + } else { + System.out.println("No Selection "); + s=""; + } + return s; + } + + public void writeFile(String fileName, String[] content) { + FileWriter csvWriter; + try { + csvWriter = new FileWriter(fileName); + for (String row : content) { + csvWriter.append(row); + csvWriter.append("\n"); + } + csvWriter.flush(); + csvWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void update(int turnCount, boolean turnIsWhite) { + turnLabel.setText("Turn : "+turnCount+", "+ (turnIsWhite?"White":"Black")); + actionLabel.setText(panelDraw.isPieceAdderMode()?"Adding Piece": + (panelDraw.isPieceSelectorMode()?"Selecting Piece to Add": + "Playing")); + this.repaint(); + } + + public void eraseLabels() { + this.setStepBanner("Turn : X"); + } +} \ No newline at end of file