final deposit

This commit is contained in:
basile.blanchard 2025-04-22 15:49:04 +02:00
commit 91da7823ad
46 changed files with 1849 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

10
.classpath Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Project_2</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -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

BIN
bin/.DS_Store vendored Normal file

Binary file not shown.

BIN
bin/Main.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/backend/Board.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/backend/Game$1.class Normal file

Binary file not shown.

BIN
bin/backend/Game.class Normal file

Binary file not shown.

BIN
bin/backend/Move.class Normal file

Binary file not shown.

Binary file not shown.

BIN
bin/backend/Piece.class Normal file

Binary file not shown.

BIN
bin/backend/PieceType.class Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

9
default.board Normal file
View File

@ -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

BIN
pieces.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

19
src/Main.java Normal file
View File

@ -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);
}
}

View File

@ -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;
}
}

621
src/backend/Board.java Normal file
View File

@ -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<Position> 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<Piece> getPieces() {
ArrayList<Piece> 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();
}
}

166
src/backend/ChessTimer.java Normal file
View File

@ -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;
}
}

220
src/backend/Game.java Normal file
View File

@ -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<Piece> 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;
}
}

64
src/backend/Move.java Normal file
View File

@ -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;
}
}

View File

@ -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<Move> 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();
}
}

36
src/backend/Piece.java Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<myGame.getWidth();x++) {
for (int y=0; y<myGame.getHeight(); y++) {
boolean isSelect = myGame.isSelected(x,y);
boolean isHighlight = myGame.isHighlighted(x,y);
if(isSelect) {
g.setColor(Color.blue);
}
if(isHighlight) {
g.setColor(Color.yellow);
}
if((x+y)%2==1 || isSelect || isHighlight) {
g.fillRect(
Math.round(x*cellWidth),
Math.round(y*cellHeight),
Math.round(cellWidth),
Math.round(cellHeight)
);
}
if(isHighlight || isSelect) {
g.setColor(Color.white);
}
}
}
g.setColor(Color.gray);
for(int x=0; x<myGame.getWidth();x++) {
int graphX = Math.round(x*cellWidth);
g.drawLine(graphX, 0, graphX, this.getHeight());
}
for (int y=0; y<myGame.getHeight(); y++) {
int graphY = Math.round(y*cellHeight);
g.drawLine(0, graphY, this.getWidth(), graphY);
}
for (Piece piece : myGame.getPieces()) {
drawPiece(g,piece);
}
}
}
private void drawPiece(Graphics g, Piece piece) {
g.drawImage(
getChessPieceImageFromType(piece.getType(), piece.isWhite()),
MARGIN+(xCoordFromGame(piece.getX())),
MARGIN+(yCoordFromGame(piece.getY())),
null
);
}
private Image getChessPieceImageFromType(PieceType type, boolean isWhite) {
int x = spriteSheetPositionOfPieceType(type)*PIECE_WIDTH;
int y = PIECE_HEIGHT * (isWhite?1:0);
Image subImage = spriteSheet.getSubimage(x, y, PIECE_WIDTH, PIECE_HEIGHT);
return subImage.getScaledInstance(
Math.round(cellWidth())-2*MARGIN,
Math.round(cellHeight())-2*MARGIN, 0
);
}
private int spriteSheetPositionOfPieceType(PieceType type) {
return 5-type.ordinal();
}
private float cellWidth() {
return (float) this.getWidth()/ (float)myGame.getWidth();
}
private float cellHeight() {
return (float)this.getHeight()/ (float)myGame.getHeight();
}
private int xCoordFromGame(int x) {
return Math.round(x*cellWidth());
}
private int yCoordFromGame(int y) {
return Math.round(y*cellHeight());
}
public void togglePieceSelector() {
pieceSelectorMode = ! pieceSelectorMode;
}
public void toggleAdderMode() {
pieceAdderMode = ! pieceAdderMode;
}
public boolean isPieceSelectorMode() {
return pieceSelectorMode;
}
public boolean isPieceAdderMode() {
return pieceAdderMode;
}
}

View File

@ -0,0 +1,387 @@
package windowInterface;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import backend.ChessTimer;
import backend.Game;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.awt.event.ActionEvent;
import javax.swing.JList;
import javax.swing.AbstractListModel;
import javax.swing.JToggleButton;
import javax.swing.JRadioButton;
import javax.swing.JCheckBox;
public class MyInterface extends JFrame {
private static final long serialVersionUID = -6840815447618468846L;
private JPanel contentPane;
private JLabel turnLabel;
private JLabel borderLabel;
private JLabel speedLabel;
private JPanelChessBoard panelDraw;
private Game game;
private JLabel actionLabel;
private JCheckBox chckbxBlackAI;
private JCheckBox chckbxWhiteAI;
// Timer-related components
private JLabel whiteTimerLabel;
private JLabel blackTimerLabel;
private JCheckBox chckbxEnableTimer;
private JSpinner timeSpinner;
/**
* Create the frame.
*/
public MyInterface() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(10, 10, 650, 650);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
JPanel panelTop = new JPanel();
contentPane.add(panelTop, BorderLayout.NORTH);
JPanel panelRight = new JPanel();
contentPane.add(panelRight, BorderLayout.EAST);
panelRight.setLayout(new GridLayout(7, 1, 0, 5)); // Increased number of rows for timer controls
// Timer panel at the bottom
JPanel timerPanel = new JPanel();
timerPanel.setLayout(new GridLayout(2, 2, 5, 5));
contentPane.add(timerPanel, BorderLayout.SOUTH);
// White timer
JPanel whiteTimerPanel = new JPanel();
whiteTimerLabel = new JLabel("10:00");
whiteTimerLabel.setFont(new Font("Monospaced", Font.BOLD, 20));
whiteTimerLabel.setHorizontalAlignment(SwingConstants.CENTER);
whiteTimerPanel.add(new JLabel("White: "));
whiteTimerPanel.add(whiteTimerLabel);
timerPanel.add(whiteTimerPanel);
// Black timer
JPanel blackTimerPanel = new JPanel();
blackTimerLabel = new JLabel("10:00");
blackTimerLabel.setFont(new Font("Monospaced", Font.BOLD, 20));
blackTimerLabel.setHorizontalAlignment(SwingConstants.CENTER);
blackTimerPanel.add(new JLabel("Black: "));
blackTimerPanel.add(blackTimerLabel);
timerPanel.add(blackTimerPanel);
// Timer controls
JPanel timerControls = new JPanel();
timerControls.setLayout(new FlowLayout());
chckbxEnableTimer = new JCheckBox("Enable Timer");
chckbxEnableTimer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
toggleTimer();
}
});
timerControls.add(chckbxEnableTimer);
timerControls.add(new JLabel("Minutes:"));
timeSpinner = new JSpinner(new SpinnerNumberModel(10, 1, 60, 1));
timeSpinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
updateInitialTime();
}
});
timerControls.add(timeSpinner);
timerPanel.add(timerControls);
actionLabel = new JLabel("Waiting For Start");
panelTop.add(actionLabel);
JButton btnGo = new JButton("Start/Restart");
btnGo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicButtonStart();
}
});
panelTop.add(btnGo);
turnLabel = new JLabel("Turn : X");
panelTop.add(turnLabel);
JButton btnLoad = new JButton("Load File");
btnLoad.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicLoadFileButton();
}
});
panelRight.add(btnLoad);
JButton btnSave = new JButton("Save To File");
btnSave.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicSaveToFileButton();
}
});
panelRight.add(btnSave);
JButton btnAdder = new JButton("Add Piece");
btnAdder.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clickButtonAdder();
}
});
panelRight.add(btnAdder);
JButton btnPieceSelector = new JButton("Piece Select");
btnPieceSelector.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clickButtonSelector();
}
});
panelRight.add(btnPieceSelector);
JButton btnUndo = new JButton("Undo");
btnUndo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicUndoButton();
}
});
panelTop.add(btnUndo);
chckbxWhiteAI = new JCheckBox("WhiteAI");
chckbxWhiteAI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicAIToggle(true);
}
});
panelTop.add(chckbxWhiteAI);
chckbxBlackAI = new JCheckBox("BlackAI");
chckbxBlackAI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicAIToggle(false);
}
});
panelTop.add(chckbxBlackAI);
panelDraw = new JPanelChessBoard(this);
contentPane.add(panelDraw, BorderLayout.CENTER);
}
public void setStepBanner(String s) {
turnLabel.setText(s);
}
public void setBorderBanner(String s) {
borderLabel.setText(s);
}
public JPanelChessBoard getPanelDessin() {
return panelDraw;
}
public void instantiateSimu() {
if(game==null) {
game = new Game(this);
panelDraw.setGame(game);
game.start();
}
}
public void clicButtonStart() {
this.instantiateSimu();
game.setDefaultSetup();
// Reset and start the timer if enabled
if (game.isTimerEnabled()) {
game.startTimer();
}
}
public void clickButtonAdder() {
panelDraw.toggleAdderMode();
}
public void clickButtonSelector() {
panelDraw.togglePieceSelector();
}
private void clicUndoButton() {
if(game == null) {
System.out.println("error : can't undo while no game present");
} else {
game.undoLastMove();
}
}
public void clicAIToggle(boolean isWhite) {
if(game == null) {
System.out.println("error : can't activate AI while no game present");
if(isWhite) {
chckbxWhiteAI.setSelected(false);
}else {
chckbxBlackAI.setSelected(false);
}
} else {
game.toggleAI(isWhite);
}
}
/**
* Toggle timer on/off
*/
private void toggleTimer() {
if (game == null) {
System.out.println("error : can't toggle timer while no game present");
chckbxEnableTimer.setSelected(false);
} else {
boolean isEnabled = chckbxEnableTimer.isSelected();
game.setTimerEnabled(isEnabled);
if (isEnabled) {
game.startTimer();
} else {
game.stopTimer();
// Reset the displayed time
updateTimers(
((Integer)timeSpinner.getValue()) * 60 * 1000,
((Integer)timeSpinner.getValue()) * 60 * 1000
);
}
}
}
/**
* Update initial time when spinner changes
*/
private void updateInitialTime() {
if (game != null) {
int minutes = (Integer)timeSpinner.getValue();
game.setInitialTime(minutes);
// Update timer labels
long timeMillis = minutes * 60 * 1000;
whiteTimerLabel.setText(ChessTimer.formatTime(timeMillis));
blackTimerLabel.setText(ChessTimer.formatTime(timeMillis));
}
}
/**
* Update timer display
*/
public void updateTimers(long whiteTimeMillis, long blackTimeMillis) {
whiteTimerLabel.setText(ChessTimer.formatTime(whiteTimeMillis));
blackTimerLabel.setText(ChessTimer.formatTime(blackTimeMillis));
}
/**
* Handle time expiration
*/
public void timeExpired(boolean isWhiteExpired) {
String message = (isWhiteExpired ? "White" : "Black") + " player's time has expired. " +
(!isWhiteExpired ? "White" : "Black") + " wins!";
JOptionPane.showMessageDialog(this, message, "Game Over", JOptionPane.INFORMATION_MESSAGE);
}
public void clicLoadFileButton() {
Game loadedSim = new Game(this);
String fileName=SelectFile();
LinkedList<String> lines = new LinkedList<String>();
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");
}
}