remove static methods

This commit is contained in:
cleme 2025-04-23 12:49:21 +02:00
parent c1210f2141
commit 2133960585
4 changed files with 133 additions and 214 deletions

View File

@ -97,7 +97,10 @@ public class Board {
private void setHighlightedPositions(Piece piece) {
this.highlightedPositions.clear();
if (piece != null) {
Set<String> validMoves = Move.calculateValidMoves(this, piece);
// Create a temporary Move object to call calculateValidMoves
// We can use dummy coordinates and null for pieceCaptured since they aren't used in calculateValidMoves
Move tempMove = new Move(0, 0, 0, 0, piece, null);
Set<String> validMoves = tempMove.calculateValidMoves(this, piece);
this.highlightedPositions.addAll(validMoves);
}
}

View File

@ -1,35 +1,22 @@
package backend;
import windowInterface.MyInterface; // Assurez-vous que ce chemin d'importation est correct
import windowInterface.MyInterface;
/**
* Représente la logique principale du jeu d'échecs.
* Gère l'état du plateau (Board), les joueurs (humain/IA), le déroulement des tours,
* et interagit avec l'interface utilisateur (MyInterface).
* Fonctionne dans un thread séparé pour permettre le jeu de l'IA sans bloquer l'UI.
*/
public class Game extends Thread {
// Dépendances
private final Board board; // L'état actuel de l'échiquier (final car l'instance Board ne change pas, seul son état interne)
private final AutoPlayer aiPlayer; // Le moteur d'IA
private final MyInterface mjf; // Référence à l'interface graphique pour les mises à jour
private final Board board;
private final AutoPlayer aiPlayer;
private final MyInterface mjf;
// Constantes de configuration
private static final int DEFAULT_WIDTH = 8;
private static final int DEFAULT_HEIGHT = 8;
private static final int DEFAULT_LOOP_DELAY = 250; // ms entre les vérifications de l'IA
private static final int DEFAULT_LOOP_DELAY = 250;
// État de contrôle du jeu
private int loopDelay; // Délai actuel de la boucle (peut être modifié)
private int loopDelay;
private final boolean[] activationAIFlags; // Index 0 pour Noir (isWhite=false), Index 1 pour Blanc (isWhite=true)
/**
* Constructeur de la partie.
* Initialise le plateau, l'IA, les flags de contrôle et la configuration initiale.
*
* @param mjfParam Référence à l'interface graphique pour pouvoir la mettre à jour. Doit être non-null.
*/
public Game(MyInterface mjfParam) {
if (mjfParam == null) {
throw new IllegalArgumentException("MyInterface parameter cannot be null.");
@ -49,197 +36,131 @@ public class Game extends Thread {
System.out.println(this.board.toString());
}
// --- Getters délégués au Board ---
/** @return La largeur du plateau. */
public int getWidth() {
return board.getWidth();
}
/** @return La hauteur du plateau. */
public int getHeight() {
return board.getHeight();
}
/**
* @return Une collection itérable des pièces actuellement sur le plateau.
* Renvoie une copie pour éviter les modifications externes non contrôlées.
*/
public Iterable<Piece> getPieces() {
return board.getPieces();
}
/**
* Vérifie si la case (x, y) correspond à la pièce actuellement sélectionnée.
* @param x Coordonnée x.
* @param y Coordonnée y.
* @return true si la pièce à (x,y) est sélectionnée, false sinon.
*/
public boolean isSelected(int x, int y) {
return board.isSelected(x, y);
}
/**
* Vérifie si la case (x, y) est mise en surbrillance comme destination possible.
* @param x Coordonnée x.
* @param y Coordonnée y.
* @return true si la case (x,y) est en surbrillance, false sinon.
*/
public boolean isHighlighted(int x, int y) {
return board.isHighlighted(x, y);
}
/** @return true si c'est le tour des blancs, false si c'est le tour des noirs. */
//return true si c'est le tour des blancs, false si c'est le tour des noirs.
public boolean isWhiteTurn() {
return board.isTurnWhite();
}
/** @return Le numéro du tour actuel (commence à 0 ou 1 selon l'implémentation de Board). */
public int getTurnNumber() {
return board.getTurnNumber();
}
// --- Logique principale du Thread ---
/**
* Boucle principale du jeu exécutée dans le thread.
* Vérifie périodiquement si c'est au tour de l'IA de jouer,
* met à jour l'interface graphique et attend.
*/
@Override
public void run() {
System.out.println("Game thread started.");
while (!Thread.currentThread().isInterrupted()) { // Continue tant que le thread n'est pas interrompu
while (!Thread.currentThread().isInterrupted()) {
try {
// 1. Gérer le tour de l'IA si applicable
aiPlayerTurn();
// 2. Mettre à jour l'interface graphique
// (Numéro de tour, à qui le tour, et potentiellement repaint)
// On le fait à chaque itération pour refléter l'état même si aucun coup n'est joué.
mjf.update(board.getTurnNumber(), board.isTurnWhite());
// Note: mjf.update() devrait idéalement déclencher un repaint dans l'UI.
// 3. Pause avant la prochaine vérification
mjf.update(board.getTurnNumber(), board.isTurnWhite());
Thread.sleep(loopDelay);
} catch (InterruptedException e) {
System.err.println("Game thread interrupted during sleep or operation.");
Thread.currentThread().interrupt(); // Important: Rétablir le statut d'interruption
// Sortir de la boucle car le thread doit s'arrêter
Thread.currentThread().interrupt();
} catch (Exception e) {
// Capturer d'autres exceptions pour la robustesse
System.err.println("Unexpected error in game loop: " + e.getMessage());
e.printStackTrace();
// Optionnel : Mettre une pause plus longue pour éviter de spammer les logs en cas d'erreur répétée
try { Thread.sleep(5000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
}
}
System.out.println("Game thread finished.");
}
/**
* Vérifie si le joueur dont c'est le tour est actuellement contrôlé par l'IA.
*
* @return true si c'est le tour de l'IA, false si c'est un tour humain.
*/
private boolean isAITurn() {
int playerIndex = board.isTurnWhite() ? 1 : 0; // 1 pour Blanc, 0 pour Noir
return activationAIFlags[playerIndex];
}
/**
* Si c'est le tour de l'IA, lui demande de calculer un coup et le joue sur le plateau.
*/
private void aiPlayerTurn() {
if (isAITurn()) {
String colorTurn = board.isTurnWhite() ? "White" : "Black";
System.out.println("AI's turn (" + colorTurn + "). Computing move...");
// Créer une copie *indépendante* du plateau pour l'analyse par l'IA.
// Ceci est crucial pour que l'IA puisse explorer des coups sans modifier l'état réel du jeu.
Board boardCopy = new Board(board); // Utilise le constructeur de copie de Board
// Demander à l'IA de calculer le meilleur coup sur la copie
Board boardCopy = new Board(board);
Move bestMove = aiPlayer.computeBestMove(boardCopy);
// Si l'IA retourne un coup (elle n'est pas échec et mat ou pat)
if (bestMove != null) {
System.out.println("AI computed move: " + bestMove.getPieceMoved().getType() +
" from (" + bestMove.getFromX() + "," + bestMove.getFromY() +
") to (" + bestMove.getToX() + "," + bestMove.getToY() + ")");
// Jouer le coup sur le plateau *principal*
board.playMove(bestMove);
// playMove met à jour l'état interne du board (pièces, tour, etc.)
// L'UI sera mise à jour à la prochaine itération de run()
} else {
// L'IA n'a trouvé aucun coup (peut indiquer échec et mat, pat, ou une erreur)
System.err.println("AI returned null move! Possible stalemate, checkmate, or error in AI for " + colorTurn + ".");
// Optionnel: Désactiver l'IA ou afficher un message de fin de partie.
// Pour l'instant, le jeu continue (ou se bloque si c'est vraiment mat/pat).
}
}
}
// --- Gestion des interactions utilisateur ---
/**
* Gère un clic de l'utilisateur sur la case (x, y) de l'échiquier.
* Vérifie si le clic est dans les limites et si ce n'est pas au tour de l'IA.
* Si les conditions sont remplies, délègue toute la logique d'interaction
* (sélection, désélection, déplacement) à la méthode `userTouch` du `Board`.
*
* @param x Coordonnée x (colonne) de la case cliquée.
* @param y Coordonnée y (ligne) de la case cliquée.
*/
public void clickCoords(int x, int y) {
// System.out.println("Game received click at (" + x + "," + y + ")"); // Debug
// 1. Ignorer si clic hors limites
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
System.out.println("Click ignored: out of bounds.");
return;
}
// 2. Ignorer si c'est le tour de l'IA (le joueur humain ne peut pas interagir)
if (isAITurn()) {
System.out.println("Click ignored: AI's turn.");
return;
}
// 3. Si c'est un tour humain et clic dans les limites, déléguer au Board
// Board.userTouch gère la sélection, la mise en surbrillance, la désélection et le jeu du coup.
board.userTouch(x, y);
// L'état du jeu (sélection, highlights, pièces) peut avoir changé.
// La boucle `run` mettra à jour l'interface graphique.
// Si une mise à jour VISUELLE immédiate est cruciale après chaque clic humain :
// mjf.update(board.getTurnNumber(), board.isTurnWhite()); // Forcer update + repaint
}
// --- Méthodes de Contrôle et Configuration ---
/**
* Active ou désactive l'IA pour la couleur spécifiée.
*
* @param isWhite true pour contrôler l'IA blanche, false pour l'IA noire.
*/
public void toggleAI(boolean isWhite) {
int playerIndex = isWhite ? 1 : 0;
activationAIFlags[playerIndex] = !activationAIFlags[playerIndex]; // Bascule l'état
String color = isWhite ? "White" : "Black";
String status = activationAIFlags[playerIndex] ? "enabled" : "disabled";
System.out.println("AI for " + color + " player is now " + status + ".");
// Si on active l'IA et que c'est son tour, elle jouera à la prochaine itération de run()
}
/**
* Modifie le délai (en millisecondes) entre les itérations de la boucle du thread.
* Un délai plus court rend l'IA plus réactive (si elle calcule vite),
* un délai plus long économise des ressources CPU.
*
* @param delay Le nouveau délai en millisecondes (doit être positif).
*/
public void setLoopDelay(int delay) {
if (delay > 0) {
this.loopDelay = delay;
@ -249,10 +170,7 @@ public class Game extends Thread {
}
}
/**
* Réinitialise le plateau à la configuration de départ standard des échecs.
* Efface l'état actuel et remet les pièces à leur place initiale.
*/
public void setDefaultSetup() {
System.out.println("Setting default board setup...");
board.cleanBoard(); // Nettoie le plateau (pièces, sélection, highlights, tour)
@ -263,90 +181,51 @@ public class Game extends Thread {
mjf.update(board.getTurnNumber(), board.isTurnWhite());
}
// --- Méthodes déléguées au Board (pour manipulation externe, sauvegarde/chargement, etc.) ---
/**
* Place directement une pièce sur le plateau.
* Attention : Utiliser principalement pour le débogage ou la mise en place de positions spécifiques.
* Écrase toute pièce existante à cet endroit.
*
* @param isWhite Couleur de la pièce (true=blanc).
* @param type Type de la pièce (Pawn, Rook...).
* @param x Coordonnée x.
* @param y Coordonnée y.
*/
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
board.setPiece(isWhite, type, x, y);
// Mettre à jour l'interface pour voir le changement
mjf.update(board.getTurnNumber(), board.isTurnWhite());
}
/**
* Récupère une représentation du plateau sous forme de tableau de chaînes de caractères.
* Utile pour la sauvegarde ou le débogage.
* (Nécessite que la méthode `toFileRep` soit implémentée dans la classe Board).
*
* @return Un tableau de String représentant l'état du plateau, ou null si non supporté.
*/
public String[] getFileRepresentation() {
// TODO: Implémenter Board.toFileRep() pour que cela fonctionne.
System.out.println("Getting file representation from board...");
return board.toFileRep();
}
/**
* Configure l'état du plateau à partir d'une représentation sous forme de tableau de chaînes.
* Remplace complètement l'état actuel du plateau.
* (Nécessite que le constructeur `Board(String[] array)` soit implémenté).
*
* @param boardData Le tableau de String décrivant le nouvel état du plateau.
*/
public void setBoard(String[] boardData) {
System.out.println("Setting board state from string array...");
// TODO: Implémenter le constructeur Board(String[] array) pour que cela fonctionne.
// Ceci remplacera l'instance actuelle du board ou modifiera son état interne.
// Si on remplace l'instance, il faut s'assurer que la référence 'board' est mise à jour.
// Exemple (si le constructeur existe) :
// this.board = new Board(boardData);
try {
// Tentative de créer un nouveau board (si le constructeur existe)
// Note: Ceci ne fonctionnera que si 'board' n'est PAS final.
// Si 'board' est final, il faudrait une méthode dans Board pour charger l'état.
// Exemple: this.board.loadFromArray(boardData);
System.err.println("setBoard(String[]) is not fully implemented yet (requires Board constructor or load method).");
// Pour l'instant, on ne fait rien pour éviter une erreur si Board(String[]) n'existe pas
// this.board = new Board(boardData);
} catch (Exception e) {
System.err.println("Failed to set board from string array: " + e.getMessage());
e.printStackTrace();
}
// Dans tous les cas, mettre à jour l'interface
mjf.update(this.board.getTurnNumber(), this.board.isTurnWhite());
}
/**
* Tente d'annuler le dernier coup joué.
* La possibilité d'annuler dépend de si le `Board` gère un historique des coups.
* (Nécessite que la méthode `undoLastMove` soit implémentée dans la classe Board).
*/
public void undoLastMove() {
// Optionnel: Ajouter une logique pour empêcher l'annulation pendant le tour de l'IA
// if (isAITurn()) {
// System.out.println("Undo ignored: AI is thinking or has just played.");
// return;
// }
System.out.println("Requesting undo last move...");
// TODO: Implémenter Board.undoLastMove() pour que cela fonctionne.
boolean success = board.undoLastMove(); // Supposons que undoLastMove renvoie un booléen
// TODO:
boolean success = board.undoLastMove();
if (success) {
System.out.println("Last move undone successfully.");
System.out.println(this.board.toString()); // Afficher le nouvel état
System.out.println(this.board.toString());
} else {
System.out.println("Undo failed (no history or cannot undo).");
}
// Mettre à jour l'interface quelle que soit l'issue
mjf.update(board.getTurnNumber(), board.isTurnWhite());
}

View File

@ -30,7 +30,7 @@ public class Move {
public Piece getPieceMoved() { return pieceMoved; }
public Piece getPieceCaptured() { return pieceCaptured; }
public static Set<String> calculateValidMoves(Board board, Piece piece) {
public Set<String> calculateValidMoves(Board board, Piece piece) {
Set<String> validMoves = new HashSet<>();
if (piece == null || board == null) {
@ -64,19 +64,19 @@ public class Move {
return validMoves;
}
private static void addMove(int x, int y, Set<String> moves) {
private void addMove(int x, int y, Set<String> moves) {
moves.add(x + "," + y);
}
static boolean isValidPosition(Board board, int x, int y) {
boolean isValidPosition(Board board, int x, int y) {
return x >= 0 && x < board.getWidth() && y >= 0 && y < board.getHeight();
}
private static Piece getPieceAt(Board board, int x, int y) {
private Piece getPieceAt(Board board, int x, int y) {
return board.getPieceAt(x, y);
}
private static void calculateSlidingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves, int[][] directions) {
private void calculateSlidingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves, int[][] directions) {
for (int[] dir : directions) {
int dx = dir[0];
int dy = dir[1];
@ -102,7 +102,7 @@ public class Move {
}
}
private static void calculatePawnMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
private void calculatePawnMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
int forwardDirection = piece.isWhite() ? -1 : 1;
int startRow = piece.isWhite() ? 6 : 1;
@ -129,16 +129,16 @@ public class Move {
}
}
// V<>rifier la capture en passant
// Vérifier la capture en passant
int enPassantTargetX = board.getEnPassantTargetX();
int enPassantTargetY = board.getEnPassantTargetY();
if (enPassantTargetX != -1 && enPassantTargetY != -1) { // Si une cible EP existe
// V<>rifier si la cible est sur la case diagonale avant
if (enPassantTargetY == oneStepY) { // La cible doit <20>tre sur la rang<6E>e devant le pion
if (Math.abs(x - enPassantTargetX) == 1) { // La cible doit <20>tre sur une colonne adjacente
// V<>rifier qu'il y a bien un pion adverse <20> capturer <20> c<>t<EFBFBD> de nous (sur la case EP virtuelle)
// Le pion <20> capturer est sur (enPassantTargetX, y)
// Vérifier si la cible est sur la case diagonale avant
if (enPassantTargetY == oneStepY) { // La cible doit être sur la rangée devant le pion
if (Math.abs(x - enPassantTargetX) == 1) { // La cible doit être sur une colonne adjacente
// rifier qu'il y a bien un pion adverse à capturer à côté de nous (sur la case EP virtuelle)
// Le pion à capturer est sur (enPassantTargetX, y)
Piece adjacentPawn = getPieceAt(board, enPassantTargetX, y);
if (adjacentPawn != null && adjacentPawn.getType() == PieceType.Pawn && adjacentPawn.isWhite() != piece.isWhite()) {
addMove(enPassantTargetX, enPassantTargetY, validMoves);
@ -149,7 +149,7 @@ public class Move {
}
}
private static void calculateKnightMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
private void calculateKnightMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
int[][] knightMoves = {
{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2},
{1, -2}, {1, 2}, {2, -1}, {2, 1}
@ -166,7 +166,7 @@ public class Move {
}
}
private static void calculateKingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
private void calculateKingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue;
@ -183,42 +183,77 @@ public class Move {
}
}
// Ajouter calcul du roque ici
calculateCastlingMoves(board, piece, x, y, validMoves);
calculateCastlingMoves1(board, piece, x, y, validMoves);
}
private static void calculateCastlingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
private void calculateCastlingMoves1(Board board, Piece piece, int x, int y, Set<String> validMoves) {
if (piece.getType() != PieceType.King || piece.hasMoved()) {
return;
}
// Roque c<>t<EFBFBD> roi
// Roque côté roi
if (canCastleKingside(board, piece, x, y)) {
addMove(x + 2, y, validMoves);
}
// Roque c<>t<EFBFBD> dame
// Roque côté dame
if (canCastleQueenside(board, piece, x, y)) {
addMove(x - 2, y, validMoves);
}
}
private static boolean canCastleKingside(Board board, Piece king, int x, int y) {
private void calculateCastlingMoves(Board board, Piece piece, int x, int y, Set<String> validMoves) {
// Castling is only possible for a king
if (piece.getType() != PieceType.King) {
return;
}
// The king must not have moved
if (piece.hasMoved()) {
return;
}
// Roque côté roi (kingside castling)
if (canCastleKingside(board, piece, x, y)) {
addMove(x + 2, y, validMoves);
}
// Roque côté dame (queenside castling)
if (canCastleQueenside(board, piece, x, y)) {
addMove(x - 2, y, validMoves);
}
}
private boolean canCastleKingside(Board board, Piece king, int x, int y) {
Piece rook = board.getPieceAt(x + 3, y);
return rook != null && rook.getType() == PieceType.Rook && !rook.hasMoved() &&
board.getPieceAt(x + 1, y) == null && board.getPieceAt(x + 2, y) == null &&
// The rook must exist, be a rook, and not have moved
// The squares between the king and rook must be empty
// The king must not be in check
return rook != null &&
rook.getType() == PieceType.Rook &&
!rook.hasMoved() &&
board.getPieceAt(x + 1, y) == null &&
board.getPieceAt(x + 2, y) == null &&
!isKingInCheck(board, king.isWhite());
}
private static boolean canCastleQueenside(Board board, Piece king, int x, int y) {
private boolean canCastleQueenside(Board board, Piece king, int x, int y) {
Piece rook = board.getPieceAt(x - 4, y);
return rook != null && rook.getType() == PieceType.Rook && !rook.hasMoved() &&
board.getPieceAt(x - 1, y) == null && board.getPieceAt(x - 2, y) == null && board.getPieceAt(x - 3, y) == null &&
// The rook must exist, be a rook, and not have moved
// The squares between the king and rook must be empty
// The king must not be in check
return rook != null &&
rook.getType() == PieceType.Rook &&
!rook.hasMoved() &&
board.getPieceAt(x - 1, y) == null &&
board.getPieceAt(x - 2, y) == null &&
board.getPieceAt(x - 3, y) == null &&
!isKingInCheck(board, king.isWhite());
}
private static boolean isKingInCheck(Board board, boolean isWhite) {
// Impl<70>mentez la logique pour v<>rifier si le roi est en <20>chec
// Cela n<>cessite de v<>rifier tous les mouvements possibles des pi<70>ces adverses
private boolean isKingInCheck(Board board, boolean isWhite) {
// Implémentez la logique pour rifier si le roi est en échec
// Cela nécessite de rifier tous les mouvements possibles des pièces adverses
return false;
}

View File

@ -38,7 +38,9 @@ public class Piece {
public boolean canMoveTo(int toX, int toY, Board board) {
if (board == null) return false;
if (this.x == toX && this.y == toY) return false;
if (!Move.isValidPosition(board, toX, toY)) return false;
// Create a temporary Move object to call isValidPosition
Move tempMove = new Move(this.x, this.y, toX, toY, this, null);
if (!tempMove.isValidPosition(board, toX, toY)) return false;
Piece pieceAtDestination = board.getPieceAt(toX, toY);
@ -63,7 +65,7 @@ public class Piece {
case King:
return dx <= 1 && dy <= 1;
default:
System.err.println("Erreur: Type de pi<70>ce inconnu dans canMoveTo: " + this.type);
System.err.println("Erreur: Type de pièce inconnu dans canMoveTo: " + this.type);
return false;
}
}