From 1921b523c695ced2a44e3261cc2353ce9b2f34ca Mon Sep 17 00:00:00 2001 From: dick Date: Tue, 25 Nov 2025 16:02:23 -0500 Subject: [PATCH] Jeu jouable avec fin de parti --- fr/iut_fbleau/Avalam/AvalamBoard.java | 609 ++++-------------- fr/iut_fbleau/Avalam/AvalamPly.java | 131 ++-- fr/iut_fbleau/Avalam/AvalamWindow.java | 131 +++- fr/iut_fbleau/Avalam/Color.java | 27 +- fr/iut_fbleau/Avalam/Tower.java | 73 ++- fr/iut_fbleau/Avalam/logic/BoardLoader.java | 40 +- fr/iut_fbleau/Avalam/logic/GameState.java | 70 -- fr/iut_fbleau/Avalam/logic/ScoreManager.java | 37 -- fr/iut_fbleau/Avalam/logic/TurnManager.java | 35 - fr/iut_fbleau/Avalam/ui/BoardView.java | 118 ++-- .../Avalam/ui/InteractionController.java | 179 +++-- 11 files changed, 537 insertions(+), 913 deletions(-) delete mode 100644 fr/iut_fbleau/Avalam/logic/GameState.java delete mode 100644 fr/iut_fbleau/Avalam/logic/ScoreManager.java delete mode 100644 fr/iut_fbleau/Avalam/logic/TurnManager.java diff --git a/fr/iut_fbleau/Avalam/AvalamBoard.java b/fr/iut_fbleau/Avalam/AvalamBoard.java index 329b1cc..2a0decc 100644 --- a/fr/iut_fbleau/Avalam/AvalamBoard.java +++ b/fr/iut_fbleau/Avalam/AvalamBoard.java @@ -2,524 +2,171 @@ package fr.iut_fbleau.Avalam; import fr.iut_fbleau.GameAPI.AbstractBoard; import fr.iut_fbleau.GameAPI.AbstractPly; +import fr.iut_fbleau.GameAPI.IBoard; import fr.iut_fbleau.GameAPI.Player; import fr.iut_fbleau.GameAPI.Result; -import fr.iut_fbleau.GameAPI.IBoard; -import java.util.Iterator; -import java.util.ArrayList; -import java.util.Deque; import java.util.ArrayDeque; -import java.util.NoSuchElementException; +import java.util.Iterator; -/** - * Implémentation du plateau de jeu Avalam conforme à l'API AbstractBoard. - * - *

Cette classe gère : - *

- * - *

Le plateau est représenté par une grille où chaque case contient : - *

- * - * @author AMARY Aurelien, DICK Adrien, FELIX-VIMALARATNAM Patrick, RABAN Hugo - * @version 1.0 - */ public class AvalamBoard extends AbstractBoard { - /** Hauteur maximale d'une tour (règle d'Avalam : 5 pions maximum) */ - private int max_height = 5; - /** Résultat de la partie (null si la partie n'est pas terminée) */ - private Result result; - /** Indique si la partie est terminée */ - private boolean gameOver; - /** Taille du plateau (9x9) */ - private int array_length = 9; - /** - * Grille du plateau de jeu. - * Chaque ArrayList<Integer> représente une tour, où chaque Integer est un joueur - * (1=PLAYER1, 2=PLAYER2). null représente une case vide (trou). - */ - private ArrayList[][] grid = new ArrayList[this.array_length][this.array_length]; + public static final int SIZE = 9; + private static final int MAX_HEIGHT = 5; - /** - * Constructeur par défaut. - * Charge le plateau depuis le fichier par défaut (fr/iut_fbleau/Res/Plateau.txt). - */ - public AvalamBoard(){ - // Charger depuis le fichier par défaut - this("fr/iut_fbleau/Res/Plateau.txt"); + private final Tower[][] grid; + private boolean gameOver = false; + private Result result = null; + + public AvalamBoard(Tower[][] initialGrid, Player startingPlayer) { + super(startingPlayer, new ArrayDeque<>()); + this.grid = new Tower[SIZE][SIZE]; + + for (int r = 0; r < SIZE; r++) + for (int c = 0; c < SIZE; c++) + this.grid[r][c] = initialGrid[r][c]; } - /** - * Constructeur avec chargement depuis un fichier. - * - * @param filePath Chemin vers le fichier contenant la configuration du plateau - */ - public AvalamBoard(String filePath) { - super(Player.PLAYER1, new ArrayDeque<>()); - - // Initialisation de la grille - for (int i = 0; i < array_length; i++) { - for (int j = 0; j < array_length; j++) { - grid[i][j] = null; - } - } - - // Charger depuis le fichier - Tower[][] towerGrid = fr.iut_fbleau.Avalam.logic.BoardLoader.loadFromFile(filePath); - for (int i = 0; i < array_length; i++) { - for (int j = 0; j < array_length; j++) { - if (towerGrid[i][j] != null) { - grid[i][j] = new ArrayList<>(); - // Convertir Tower en ArrayList - Color color = towerGrid[i][j].getColor(); - int playerValue = (color == Color.COLOR1) ? 1 : 2; - for (int k = 0; k < towerGrid[i][j].getHeight(); k++) { - grid[i][j].add(playerValue); - } - } - } - } - - this.gameOver = false; - this.result = null; - updateGameState(); + public AvalamBoard(Tower[][] initialGrid) { + this(initialGrid, Player.PLAYER1); + } + + private AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result) { + super(current, new ArrayDeque<>()); + this.grid = grid; + this.gameOver = gameOver; + this.result = result; + } + + public Tower getTowerAt(int row, int col) { + return inBounds(row, col) ? grid[row][col] : null; + } + + private boolean inBounds(int r, int c) { + return r >= 0 && r < SIZE && c >= 0 && c < SIZE; + } + + private boolean areAdjacent(int r1, int c1, int r2, int c2) { + int dr = Math.abs(r1 - r2); + int dc = Math.abs(c1 - c2); + return (dr <= 1 && dc <= 1 && !(dr == 0 && dc == 0)); + } + + private Color colorForPlayer(Player p) { + return (p == Player.PLAYER1 ? Color.YELLOW : Color.RED); } - /** - * Vérifie si la partie est terminée. - * - * @return true si la partie est terminée (plus aucun coup possible) - */ @Override public boolean isGameOver() { - return this.gameOver; - } + if (gameOver) return true; - /** - * Retourne le résultat de la partie. - * - * @return Le résultat (WIN/LOSS/DRAW du point de vue de PLAYER1), ou null si la partie n'est pas terminée - */ - @Override - public Result getResult() { - return this.result; - } + Iterator it = iterator(); + if (it.hasNext()) return false; + + gameOver = true; + return true; + } + + @Override + public Result getResult() { + if (!isGameOver()) return null; + if (result != null) return result; + + int yellow = 0; + int red = 0; + + for (int r = 0; r < SIZE; r++) + for (int c = 0; c < SIZE; c++) { + Tower t = grid[r][c]; + if (t == null) continue; + + if (t.getColor() == Color.YELLOW) yellow++; + else if (t.getColor() == Color.RED) red++; + } + + if (yellow > red) result = Result.WIN; + else if (yellow < red) result = Result.LOSS; + else result = Result.DRAW; + + return result; + } + + @Override + public boolean isLegal(AbstractPly c) { + if (!(c instanceof AvalamPly)) return false; + AvalamPly p = (AvalamPly) c; + + int xF = p.getXFrom(), yF = p.getYFrom(); + int xT = p.getXTo(), yT = p.getYTo(); + + if (!inBounds(xF, yF) || !inBounds(xT, yT)) return false; + if (xF == xT && yF == yT) return false; + + Tower src = grid[xF][yF]; + Tower dst = grid[xT][yT]; + if (src == null || dst == null) return false; + + if (src.getColor() != colorForPlayer(getCurrentPlayer())) return false; + if (!areAdjacent(xF, yF, xT, yT)) return false; + if (src.getColor() == dst.getColor()) return false; + if (src.getHeight() + dst.getHeight() > MAX_HEIGHT) return false; - /** - * Vérifie si les coordonnées sont dans les limites du plateau. - * - * @param x Coordonnée X - * @param y Coordonnée Y - * @return true si les coordonnées sont valides (dans [0, 8]) - */ - public boolean inRange(int x, int y) { - return x >= 0 && x < array_length && y >= 0 && y < array_length; - } - - /** - * Vérifie si deux cases sont voisines (horizontal, vertical ou diagonal). - * - * @param x1 Coordonnée X de la première case - * @param y1 Coordonnée Y de la première case - * @param x2 Coordonnée X de la deuxième case - * @param y2 Coordonnée Y de la deuxième case - * @return true si les deux cases sont voisines (distance de 1 case maximum) - */ - private boolean isNeighbor(int x1, int y1, int x2, int y2) { - int dx = Math.abs(x1 - x2); - int dy = Math.abs(y1 - y2); - return (dx <= 1 && dy <= 1) && !(dx == 0 && dy == 0); - } - - /** - * Retourne la hauteur d'une tour (nombre de pions). - * - * @param x Coordonnée X de la case - * @param y Coordonnée Y de la case - * @return La hauteur de la tour (0 si la case est vide) - */ - private int getTowerHeight(int x, int y) { - if (grid[x][y] == null) { - return 0; - } - return grid[x][y].size(); - } - - /** - * Retourne le joueur propriétaire d'une tour (celui du sommet). - * - * @param x Coordonnée X de la case - * @param y Coordonnée Y de la case - * @return Le joueur propriétaire (PLAYER1 ou PLAYER2), ou null si la case est vide - */ - private Player getTowerOwner(int x, int y) { - if (grid[x][y] == null || grid[x][y].isEmpty()) { - return null; - } - int topPlayer = grid[x][y].get(grid[x][y].size() - 1); - return (topPlayer == 1) ? Player.PLAYER1 : Player.PLAYER2; - } - - /** - * Vérifie si un coup est légal selon les règles d'Avalam. - * - *

Un coup est légal si : - *

    - *
  • Le jeu n'est pas terminé
  • - *
  • Le coup est un AvalamPly
  • - *
  • C'est le tour du bon joueur
  • - *
  • Les coordonnées sont dans les limites du plateau
  • - *
  • La case source n'est pas vide
  • - *
  • La case destination n'est pas vide (pas de trou)
  • - *
  • La destination est voisine de la source
  • - *
  • La hauteur totale après déplacement ne dépasse pas max_height (5)
  • - *
- * - * @param c Le coup à vérifier - * @return true si le coup est légal, false sinon - */ - @Override - public boolean isLegal(AbstractPly c) { - if (this.gameOver) { - return false; - } - - if (!(c instanceof AvalamPly)) { - return false; - } - - AvalamPly coup = (AvalamPly) c; - - // Vérifier que c'est le bon joueur - if (coup.getPlayer() != getCurrentPlayer()) { - return false; - } - - int xFrom = coup.getXFrom(); - int yFrom = coup.getYFrom(); - int xTo = coup.getXTo(); - int yTo = coup.getYTo(); - - // Vérifier que les coordonnées sont dans les limites - if (!inRange(xFrom, yFrom) || !inRange(xTo, yTo)) { - return false; - } - - // Règle : On ne peut pas déplacer depuis une case vide - if (grid[xFrom][yFrom] == null || grid[xFrom][yFrom].isEmpty()) { - return false; - } - - // Règle : On ne peut pas poser sur une case vide (trou) - if (grid[xTo][yTo] == null) { - return false; - } - - // Règle : La destination doit être voisine - if (!isNeighbor(xFrom, yFrom, xTo, yTo)) { - return false; - } - - // Règle : On déplace toute la pile - int heightFrom = getTowerHeight(xFrom, yFrom); - int heightTo = getTowerHeight(xTo, yTo); - - // Règle : La hauteur totale après déplacement ne doit pas dépasser max_height - if (heightFrom + heightTo > max_height) { - return false; - } - return true; } - /** - * Applique un coup sur le plateau. - * - *

Le coup déplace toute la tour de la case source vers la case destination. - * La méthode met également à jour l'historique et change de joueur via l'appel à super.doPly(). - * - * @param c Le coup à appliquer - * @throws IllegalStateException si le coup n'est pas légal - */ @Override public void doPly(AbstractPly c) { - if (!isLegal(c)) { - throw new IllegalStateException("Coup illégal : " + c); - } - - AvalamPly coup = (AvalamPly) c; - int xFrom = coup.getXFrom(); - int yFrom = coup.getYFrom(); - int xTo = coup.getXTo(); - int yTo = coup.getYTo(); - - // Stocker la hauteur de la source avant le déplacement (pour undo) - int sourceHeight = getTowerHeight(xFrom, yFrom); - coup.setSourceHeight(sourceHeight); - - // Déplacer toute la pile de la source vers la destination - // On empile les pions de la source sur ceux de la destination - ArrayList sourceTower = grid[xFrom][yFrom]; - ArrayList destTower = grid[xTo][yTo]; - - // Ajouter tous les pions de la source à la destination - for (Integer pion : sourceTower) { - destTower.add(pion); - } - - // Vider la case source - grid[xFrom][yFrom] = null; - - // Appeler la méthode parente pour gérer l'historique et changer de joueur + if (!isLegal(c)) throw new IllegalArgumentException("Coup illégal : " + c); + + AvalamPly p = (AvalamPly) c; + + int xF = p.getXFrom(), yF = p.getYFrom(); + int xT = p.getXTo(), yT = p.getYTo(); + + Tower src = grid[xF][yF]; + Tower dst = grid[xT][yT]; + + dst.mergeTower(src); + grid[xF][yF] = null; + super.doPly(c); - - // Mettre à jour l'état du jeu - updateGameState(); + + gameOver = false; + result = null; } - /** - * Annule le dernier coup joué. - * - *

Restaure l'état du plateau avant le dernier coup. - * La méthode met également à jour l'historique et change de joueur via l'appel à super.undoPly(). - * - * @throws IllegalStateException si aucun coup n'a été joué ou si la hauteur source n'est pas définie - */ - @Override - public void undoPly() { - AbstractPly lastPly = getLastPlyFromHistory(); - if (lastPly == null) { - throw new IllegalStateException("Aucun coup à annuler"); - } - - AvalamPly coup = (AvalamPly) lastPly; - int xFrom = coup.getXFrom(); - int yFrom = coup.getYFrom(); - int xTo = coup.getXTo(); - int yTo = coup.getYTo(); - int sourceHeight = coup.getSourceHeight(); - - if (sourceHeight < 0) { - throw new IllegalStateException("Impossible d'annuler : hauteur source non définie"); - } - - // La destination contient maintenant : destination_avant + source - // On doit retirer les derniers sourceHeight pions pour reconstruire la source - ArrayList destTower = grid[xTo][yTo]; - ArrayList sourceTower = new ArrayList<>(); - - // Retirer les derniers pions de la destination (ceux qui venaient de la source) - for (int i = 0; i < sourceHeight; i++) { - if (destTower.isEmpty()) { - throw new IllegalStateException("Erreur lors de l'annulation : tour destination vide"); - } - sourceTower.add(0, destTower.remove(destTower.size() - 1)); - } - - // Remettre la tour source à sa place - grid[xFrom][yFrom] = sourceTower; - - // Si la destination est vide après annulation, la mettre à null - if (destTower.isEmpty()) { - grid[xTo][yTo] = null; - } - - // Appeler la méthode parente - super.undoPly(); - - // Mettre à jour l'état du jeu - updateGameState(); - } - - /** - * Met à jour l'état du jeu (gameOver et result). - * - *

Vérifie s'il reste des coups possibles. Si aucun coup n'est possible : - *

    - *
  • Marque la partie comme terminée (gameOver = true)
  • - *
  • Calcule le score de chaque joueur (nombre de tours possédées)
  • - *
  • Détermine le résultat (WIN/LOSS/DRAW du point de vue de PLAYER1)
  • - *
- */ - private void updateGameState() { - // Vérifier s'il y a encore des coups possibles - boolean hasLegalMove = false; - Iterator it = iterator(); - if (it.hasNext()) { - hasLegalMove = true; - } - - if (!hasLegalMove) { - this.gameOver = true; - // Calculer le résultat - int scorePlayer1 = 0; - int scorePlayer2 = 0; - - for (int i = 0; i < array_length; i++) { - for (int j = 0; j < array_length; j++) { - Player owner = getTowerOwner(i, j); - if (owner == Player.PLAYER1) { - scorePlayer1++; - } else if (owner == Player.PLAYER2) { - scorePlayer2++; - } - } - } - - // Result est du point de vue de PLAYER1 - if (scorePlayer1 > scorePlayer2) { - this.result = Result.WIN; - } else if (scorePlayer1 < scorePlayer2) { - this.result = Result.LOSS; - } else { - this.result = Result.DRAW; - } - } else { - this.gameOver = false; - this.result = null; - } - } - - /** - * Retourne un itérateur sur tous les coups légaux possibles pour le joueur actuel. - * - *

Cet itérateur est utilisé par les bots pour explorer les coups possibles. - * - * @return Un itérateur sur tous les coups légaux - */ @Override public Iterator iterator() { - return new LegalMovesIterator(); - } + java.util.List moves = new java.util.ArrayList<>(); - /** - * Itérateur sur tous les coups légaux possibles. - * - *

Parcourt toutes les cases source possibles et trouve toutes les destinations valides - * pour chaque source, en respectant les règles d'Avalam. - */ - private class LegalMovesIterator implements Iterator { - private int currentX = 0; - private int currentY = 0; - private int currentDestX = -1; - private int currentDestY = -1; - private AbstractPly nextMove = null; + Player cur = getCurrentPlayer(); - public LegalMovesIterator() { - findNextMove(); - } + for (int r = 0; r < SIZE; r++) { + for (int c = 0; c < SIZE; c++) { + for (int dr = -1; dr <= 1; dr++) { + for (int dc = -1; dc <= 1; dc++) { + if (dr == 0 && dc == 0) continue; - @Override - public boolean hasNext() { - return nextMove != null; - } + int nr = r + dr, nc = c + dc; + AvalamPly p = new AvalamPly(cur, r, c, nr, nc); - @Override - public AbstractPly next() { - if (nextMove == null) { - throw new NoSuchElementException(); - } - AbstractPly move = nextMove; - findNextMove(); - return move; - } - - private void findNextMove() { - nextMove = null; - - // Parcourir toutes les cases source possibles - while (currentX < array_length) { - // Si on a une case source valide - if (grid[currentX][currentY] != null && !grid[currentX][currentY].isEmpty()) { - // Chercher une destination valide - while (currentDestX < array_length) { - currentDestY++; - if (currentDestY >= array_length) { - currentDestY = 0; - currentDestX++; - } - - if (currentDestX >= array_length) { - break; - } - - // Vérifier si ce mouvement est légal - if (isNeighbor(currentX, currentY, currentDestX, currentDestY) && - grid[currentDestX][currentDestY] != null && - getTowerHeight(currentX, currentY) + getTowerHeight(currentDestX, currentDestY) <= max_height) { - - nextMove = new AvalamPly(getCurrentPlayer(), currentX, currentY, currentDestX, currentDestY); - return; - } + if (isLegal(p)) moves.add(p); } } - - // Passer à la case suivante - currentY++; - if (currentY >= array_length) { - currentY = 0; - currentX++; - } - currentDestX = -1; - currentDestY = -1; } } + + return moves.iterator(); } - /** - * Crée une copie indépendante du plateau. - * - *

Cette méthode est utilisée pour empêcher la triche : les bots reçoivent une copie - * du plateau et ne peuvent pas modifier l'état original. - * - * @return Une copie indépendante du plateau - */ @Override public IBoard safeCopy() { - AvalamBoard copy = new AvalamBoard(true); - // Réinitialiser l'état sans charger depuis le fichier - copy.gameOver = this.gameOver; - copy.result = this.result; - copy.max_height = this.max_height; - - // Copier la grille - for (int i = 0; i < array_length; i++) { - for (int j = 0; j < array_length; j++) { - if (grid[i][j] != null) { - copy.grid[i][j] = new ArrayList<>(grid[i][j]); - } else { - copy.grid[i][j] = null; - } - } - } - - return copy; - } - - /** - * Constructeur privé pour safeCopy(). - * Crée un plateau vide sans charger depuis un fichier. - * - * @param empty Paramètre inutilisé, présent uniquement pour différencier ce constructeur - */ - private AvalamBoard(boolean empty) { - super(Player.PLAYER1, new ArrayDeque<>()); - // Initialisation de la grille vide - for (int i = 0; i < array_length; i++) { - for (int j = 0; j < array_length; j++) { - grid[i][j] = null; - } - } - this.gameOver = false; - this.result = null; + Tower[][] newGrid = new Tower[SIZE][SIZE]; + + for (int r = 0; r < SIZE; r++) + for (int c = 0; c < SIZE; c++) + newGrid[r][c] = grid[r][c]; + + return new AvalamBoard(newGrid, getCurrentPlayer(), gameOver, result); } } diff --git a/fr/iut_fbleau/Avalam/AvalamPly.java b/fr/iut_fbleau/Avalam/AvalamPly.java index 80b1317..b337d31 100644 --- a/fr/iut_fbleau/Avalam/AvalamPly.java +++ b/fr/iut_fbleau/Avalam/AvalamPly.java @@ -4,126 +4,77 @@ import fr.iut_fbleau.GameAPI.AbstractPly; import fr.iut_fbleau.GameAPI.Player; /** - * Représente un coup dans le jeu Avalam. - * Un coup consiste à déplacer une tour de la position (xFrom, yFrom) - * vers la position (xTo, yTo). - * - * @author AMARY Aurelien, DICK Adrien, FELIX-VIMALARATNAM Patrick, RABAN Hugo - * @version 1.0 + * Représente un coup (ply) dans le jeu Avalam. + * + * Un coup est défini par : + *

    + *
  • un joueur (PLAYER1 ou PLAYER2)
  • + *
  • une position source (xFrom, yFrom)
  • + *
  • une position destination (xTo, yTo)
  • + *
+ * + * Ces coordonnées seront utilisées par AvalamBoard pour : + *
    + *
  • vérifier la légalité du coup
  • + *
  • fusionner les tours concernées
  • + *
  • mettre à jour la grille
  • + *
+ * + * Cette classe ne contient aucune logique de vérification : tout est délégué + * à AvalamBoard.isLegal() et AvalamBoard.doPly(). */ public class AvalamPly extends AbstractPly { - /** Coordonnée X de la case source */ - private int xFrom; - /** Coordonnée Y de la case source */ - private int yFrom; - /** Coordonnée X de la case destination */ - private int xTo; - /** Coordonnée Y de la case destination */ - private int yTo; - /** Hauteur de la tour source (utilisée pour l'annulation de coup) */ - private int sourceHeight; + + /** Coordonnées source */ + private final int xFrom; + private final int yFrom; + + /** Coordonnées destination */ + private final int xTo; + private final int yTo; /** - * Constructeur par défaut d'un coup Avalam. - * La hauteur de la source sera définie automatiquement lors de l'application du coup. - * - * @param joueur Le joueur qui effectue le coup - * @param xFrom Coordonnée X de la case source - * @param yFrom Coordonnée Y de la case source - * @param xTo Coordonnée X de la case destination - * @param yTo Coordonnée Y de la case destination + * Constructeur principal. + * + * @param player joueur qui joue le coup + * @param xFrom ligne d'origine + * @param yFrom colonne d'origine + * @param xTo ligne de destination + * @param yTo colonne de destination */ - public AvalamPly(Player joueur, int xFrom, int yFrom, int xTo, int yTo) { - super(joueur); + public AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo) { + super(player); this.xFrom = xFrom; this.yFrom = yFrom; this.xTo = xTo; this.yTo = yTo; - this.sourceHeight = -1; // Sera défini lors du doPly } - /** - * Constructeur avec hauteur de la source spécifiée. - * Utilisé principalement pour l'annulation de coups. - * - * @param joueur Le joueur qui effectue le coup - * @param xFrom Coordonnée X de la case source - * @param yFrom Coordonnée Y de la case source - * @param xTo Coordonnée X de la case destination - * @param yTo Coordonnée Y de la case destination - * @param sourceHeight Hauteur de la tour source avant le déplacement - */ - public AvalamPly(Player joueur, int xFrom, int yFrom, int xTo, int yTo, int sourceHeight) { - super(joueur); - this.xFrom = xFrom; - this.yFrom = yFrom; - this.xTo = xTo; - this.yTo = yTo; - this.sourceHeight = sourceHeight; - } - - /** - * Retourne la coordonnée X de la case source. - * - * @return La coordonnée X de la case source - */ + /** Ligne d'origine */ public int getXFrom() { return xFrom; } - /** - * Retourne la coordonnée Y de la case source. - * - * @return La coordonnée Y de la case source - */ + /** Colonne d'origine */ public int getYFrom() { return yFrom; } - /** - * Retourne la coordonnée X de la case destination. - * - * @return La coordonnée X de la case destination - */ + /** Ligne de destination */ public int getXTo() { return xTo; } - /** - * Retourne la coordonnée Y de la case destination. - * - * @return La coordonnée Y de la case destination - */ + /** Colonne de destination */ public int getYTo() { return yTo; } - /** - * Retourne la hauteur de la tour source. - * - * @return La hauteur de la tour source, ou -1 si non définie - */ - public int getSourceHeight() { - return sourceHeight; - } - - /** - * Définit la hauteur de la tour source. - * Cette méthode est appelée automatiquement lors de l'application du coup. - * - * @param height La hauteur de la tour source - */ - public void setSourceHeight(int height) { - this.sourceHeight = height; - } - @Override public String toString() { return "AvalamPly{" + - "joueur=" + getPlayer() + - ", from=(" + xFrom + "," + yFrom + ")" + - ", to=(" + xTo + "," + yTo + ")" + + "player=" + getPlayer() + + ", (" + xFrom + "," + yFrom + ") -> (" + xTo + "," + yTo + ")" + '}'; } } - diff --git a/fr/iut_fbleau/Avalam/AvalamWindow.java b/fr/iut_fbleau/Avalam/AvalamWindow.java index 1b8a46f..662356d 100644 --- a/fr/iut_fbleau/Avalam/AvalamWindow.java +++ b/fr/iut_fbleau/Avalam/AvalamWindow.java @@ -1,42 +1,139 @@ package fr.iut_fbleau.Avalam; -import fr.iut_fbleau.Avalam.logic.*; -import fr.iut_fbleau.Avalam.ui.*; +import fr.iut_fbleau.Avalam.logic.BoardLoader; +import fr.iut_fbleau.Avalam.ui.BoardView; +import fr.iut_fbleau.Avalam.ui.ScoreView; +import fr.iut_fbleau.Avalam.ui.TurnView; +import fr.iut_fbleau.GameAPI.Player; +import fr.iut_fbleau.GameAPI.Result; import javax.swing.*; import java.awt.*; /** * Fenêtre principale du jeu Avalam. + * + * Elle contient : + * - le plateau (BoardView) + * - l'affichage du score + * - l'affichage du joueur courant + * + * Elle interagit directement avec AvalamBoard (moteur du jeu). */ public class AvalamWindow extends JFrame { + /** Moteur du jeu (API GameAPI) */ + private AvalamBoard board; + + /** Vues graphiques */ + private ScoreView scoreView; + private TurnView turnView; + private BoardView boardView; + public AvalamWindow() { - super("Avalam - Plateau Graphique"); + super("Avalam"); - setSize(750, 800); - setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new BorderLayout()); - Tower[][] grid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt"); - GameState gs = new GameState(grid); - ScoreManager sm = new ScoreManager(); + // ---------------------------------------------------------- + // Chargement du plateau initial depuis Plateau.txt + // ---------------------------------------------------------- + Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt"); + board = new AvalamBoard(initialGrid); // PLAYER1 commence - int y = sm.count(Color.COLOR1, grid); - int r = sm.count(Color.COLOR2, grid); + // ---------------------------------------------------------- + // PANNEAU SCORE + TOUR + // ---------------------------------------------------------- + JPanel topPanel = new JPanel(new GridLayout(2, 1)); + topPanel.setBackground(new java.awt.Color(200, 200, 200)); - ScoreView scoreView = new ScoreView(y, r); - TurnView turnView = new TurnView("Tour du joueur : Jaune"); + scoreView = new ScoreView( + computeScore(Color.YELLOW), + computeScore(Color.RED) + ); - BoardView boardView = new BoardView(gs); + turnView = new TurnView(turnMessage()); - JPanel top = new JPanel(new GridLayout(2,1)); - top.add(scoreView); - top.add(turnView); + topPanel.add(scoreView); + topPanel.add(turnView); + + add(topPanel, BorderLayout.NORTH); + + // ---------------------------------------------------------- + // PLATEAU (BoardView) + // ---------------------------------------------------------- + boardView = new BoardView(board, this::onBoardUpdated); - add(top, BorderLayout.NORTH); add(boardView, BorderLayout.CENTER); + pack(); + setResizable(false); + setLocationRelativeTo(null); setVisible(true); } + + /* ================================================================ + * MISES À JOUR D’APRÈS LE MOTEUR + * ================================================================ */ + + /** + * Appelé automatiquement après chaque coup via BoardView → controller → board. + */ + public void onBoardUpdated() { + + // Mise à jour du score et du joueur courant + scoreView.updateScores( + computeScore(Color.YELLOW), + computeScore(Color.RED) + ); + + turnView.setTurn(turnMessage()); + + // Détection de fin de partie + if (board.isGameOver()) { + Result res = board.getResult(); + + String msg; + + switch (res) { + case WIN -> msg = "Le joueur jaune a gagné !"; + case LOSS -> msg = "Le joueur rouge a gagné !"; + case DRAW -> msg = "Égalité !"; + default -> msg = "Fin de partie."; + } + + JOptionPane.showMessageDialog(this, msg, "Partie terminée", + JOptionPane.INFORMATION_MESSAGE); + } + } + + + /* ================================================================ + * OUTILS + * ================================================================ */ + + /** + * Calcule le score d'une couleur : nombre de tours contrôlées. + */ + private int computeScore(Color c) { + int score = 0; + for (int r = 0; r < AvalamBoard.SIZE; r++) { + for (int col = 0; col < AvalamBoard.SIZE; col++) { + Tower t = board.getTowerAt(r, col); + if (t != null && t.getColor() == c) { + score++; + } + } + } + return score; + } + + /** + * Message du joueur dont c'est le tour. + */ + private String turnMessage() { + return "Tour du joueur : " + + (board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge"); + } } diff --git a/fr/iut_fbleau/Avalam/Color.java b/fr/iut_fbleau/Avalam/Color.java index 1d3414a..5eedab1 100644 --- a/fr/iut_fbleau/Avalam/Color.java +++ b/fr/iut_fbleau/Avalam/Color.java @@ -1,28 +1,23 @@ package fr.iut_fbleau.Avalam; -/** - * L'énumération Color représente la couleur du sommet d'une tour Avalam. - * - * COLOR1 : couleur du Joueur Jaune - * COLOR2 : couleur du Joueur Rouge - * - * Les couleurs Swing associées peuvent être modifiées ici pour changer tout le jeu. - * - * @version 3.0 - */ public enum Color { - COLOR1(java.awt.Color.YELLOW), // Joueur Jaune - COLOR2(java.awt.Color.RED); // Joueur Rouge + YELLOW(255, 220, 0), + RED(200, 40, 40); - /** Couleur Swing associée */ private final java.awt.Color swingColor; - Color(java.awt.Color col) { - this.swingColor = col; + Color(int r, int g, int b) { + this.swingColor = new java.awt.Color(r, g, b); } public java.awt.Color getSwingColor() { - return this.swingColor; + return swingColor; + } + + public fr.iut_fbleau.GameAPI.Player toPlayer() { + return (this == YELLOW ? + fr.iut_fbleau.GameAPI.Player.PLAYER1 : + fr.iut_fbleau.GameAPI.Player.PLAYER2); } } diff --git a/fr/iut_fbleau/Avalam/Tower.java b/fr/iut_fbleau/Avalam/Tower.java index bfecdd7..a160bc3 100644 --- a/fr/iut_fbleau/Avalam/Tower.java +++ b/fr/iut_fbleau/Avalam/Tower.java @@ -1,41 +1,50 @@ -package fr.iut_fbleau.Avalam ; +package fr.iut_fbleau.Avalam; /** -* La classe Tower stocke la couleur de son pion haut et la hauteur de la tour. -* -* @version 1.0 -* @author Aurélien -*/ + * Représente une tour dans le jeu Avalam. + * + * Une tour possède : + * - la couleur de son sommet + * - sa hauteur (nombre de pions) + */ public class Tower { - //Attributs - private Color color ; - private byte height = 1 ; - //Constructeur - public Tower(Color color) { - this.color = color ; - } + private Color color; + private int height; - //Méthodes - public Color getColor() { - return this.color ; - } + /** Nouvelle tour de hauteur 1 */ + public Tower(Color color) { + this.color = color; + this.height = 1; + } - public byte getHeight() { - return this.height ; - } + /** Tour avec couleur et hauteur existantes */ + public Tower(Color color, int height) { + this.color = color; + this.height = height; + } - /** - * Méthode qui empile une autre tour sur l'objet sur lequel le méthode est appelée. - * Aucune vérification de hauteur n'est effectuée. - */ - public void mergeTower(Tower tower) { - this.color = tower.getColor(); - this.height += tower.getHeight(); - } + public Color getColor() { + return color; + } - @Override - public String toString() { - return "" ; - } + public int getHeight() { + return height; + } + + /** + * Fusionne this (destination) avec src (source). + * La source monte sur la destination → + * - la couleur du sommet devient celle de src + * - la hauteur s’additionne + */ + public void mergeTower(Tower src) { + this.color = src.color; + this.height = this.height + src.height; + } + + @Override + public String toString() { + return color + "(" + height + ")"; + } } diff --git a/fr/iut_fbleau/Avalam/logic/BoardLoader.java b/fr/iut_fbleau/Avalam/logic/BoardLoader.java index 55000c7..51794ff 100644 --- a/fr/iut_fbleau/Avalam/logic/BoardLoader.java +++ b/fr/iut_fbleau/Avalam/logic/BoardLoader.java @@ -2,51 +2,35 @@ package fr.iut_fbleau.Avalam.logic; import fr.iut_fbleau.Avalam.Color; import fr.iut_fbleau.Avalam.Tower; + import java.io.*; /** - * La classe BoardLoader est responsable du chargement d'un plateau Avalam - * depuis un fichier texte externe (généralement Plateau.txt). - * - * Le fichier doit contenir une matrice 9×9 de valeurs numériques séparées par des virgules : - * - 0 : case vide (trou) - * - 1 : pion appartenant au joueur COLOR1 (Jaune) - * - 2 : pion appartenant au joueur COLOR2 (Rouge) - * - * Aucun contrôle de cohérence avancé n'est effectué, le chargeur se contente - * d’interpréter les valeurs comme demandé. - * - * @author - * @version 1.0 + * Charge un plateau Avalam depuis un fichier texte. + * Format attendu : matrice 9×9 de 0,1,2 séparés par virgule. */ public class BoardLoader { - /** - * Charge un plateau Avalam depuis un fichier. - * - * @param path chemin du fichier plateau. - * @return un tableau 9×9 contenant des objets Tower ou null. - */ - public static Tower[][] loadFromFile(String path) { - + public static Tower[][] loadFromFile(String file) { Tower[][] grid = new Tower[9][9]; - try (BufferedReader br = new BufferedReader(new FileReader(new File(path)))) { + try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; int row = 0; while ((line = br.readLine()) != null && row < 9) { - String[] parts = line.split(","); + String[] vals = line.split(","); for (int col = 0; col < 9; col++) { - int v = Integer.parseInt(parts[col]); + int v = Integer.parseInt(vals[col].trim()); - // Interprétation des valeurs - if (v == 1) grid[row][col] = new Tower(Color.COLOR1); - else if (v == 2) grid[row][col] = new Tower(Color.COLOR2); - else grid[row][col] = null; // Case vide + switch (v) { + case 1 -> grid[row][col] = new Tower(Color.YELLOW); + case 2 -> grid[row][col] = new Tower(Color.RED); + default -> grid[row][col] = null; + } } row++; } diff --git a/fr/iut_fbleau/Avalam/logic/GameState.java b/fr/iut_fbleau/Avalam/logic/GameState.java deleted file mode 100644 index e595dcc..0000000 --- a/fr/iut_fbleau/Avalam/logic/GameState.java +++ /dev/null @@ -1,70 +0,0 @@ -package fr.iut_fbleau.Avalam.logic; - -import fr.iut_fbleau.Avalam.Color; -import fr.iut_fbleau.Avalam.Tower; - -/** - * La classe GameState représente l'état courant du jeu Avalam : - * - la grille 9×9 - * - le joueur dont c'est le tour - * - * Elle ne contient aucune logique de déplacement ni de vérification des règles ; - * son rôle est uniquement de stocker et de fournir l'état du jeu. - * - * @author - * @version 1.0 - */ -public class GameState { - - /** Grille du plateau : 9×9 tours ou cases vides. */ - private Tower[][] grid; - - /** Joueur dont c’est le tour (COLOR1 ou COLOR2). */ - private Color currentPlayer = Color.COLOR1; - - /** - * Constructeur. - * - * @param grid la grille initiale du plateau. - */ - public GameState(Tower[][] grid) { - this.grid = grid; - } - - /** - * Retourne la tour présente à la position (r,c), ou null. - */ - public Tower get(int r, int c) { - return grid[r][c]; - } - - /** - * Retourne la grille complète. - */ - public Tower[][] getGrid() { - return grid; - } - - /** - * Retourne le joueur dont c'est le tour. - */ - public Color getCurrentPlayer() { - return currentPlayer; - } - - /** - * Change le joueur courant : COLOR1 → COLOR2 → COLOR1. - */ - public void switchPlayer() { - currentPlayer = (currentPlayer == Color.COLOR1) ? Color.COLOR2 : Color.COLOR1; - } - - /** - * Vérifie si une position (r,c) est dans les limites du plateau. - * - * @return vrai si la position est comprise dans un plateau 9×9. - */ - public boolean isInside(int r, int c) { - return r >= 0 && r < 9 && c >= 0 && c < 9; - } -} diff --git a/fr/iut_fbleau/Avalam/logic/ScoreManager.java b/fr/iut_fbleau/Avalam/logic/ScoreManager.java deleted file mode 100644 index 72955ab..0000000 --- a/fr/iut_fbleau/Avalam/logic/ScoreManager.java +++ /dev/null @@ -1,37 +0,0 @@ -package fr.iut_fbleau.Avalam.logic; - -import fr.iut_fbleau.Avalam.Color; -import fr.iut_fbleau.Avalam.Tower; - -/** - * La classe ScoreManager gère le calcul des scores - * selon les règles officielles d’Avalam : - * - * Un joueur gagne 1 point par tour dont le sommet (couleur) lui appartient, - * indépendamment de la hauteur de la tour. - * - * Cette classe n’a aucune responsabilité autre que compter. - * - * @author - * @version 1.0 - */ -public class ScoreManager { - - /** - * Compte le nombre de tours dont le sommet appartient à la couleur donnée. - * - * @param c couleur du joueur (COLOR1 ou COLOR2) - * @param grid grille 9×9 contenant des tours ou null - * @return score du joueur - */ - public static int count(Color c, Tower[][] grid) { - int score = 0; - - for (int i = 0; i < 9; i++) - for (int j = 0; j < 9; j++) - if (grid[i][j] != null && grid[i][j].getColor() == c) - score++; - - return score; - } -} diff --git a/fr/iut_fbleau/Avalam/logic/TurnManager.java b/fr/iut_fbleau/Avalam/logic/TurnManager.java deleted file mode 100644 index 5f37453..0000000 --- a/fr/iut_fbleau/Avalam/logic/TurnManager.java +++ /dev/null @@ -1,35 +0,0 @@ -package fr.iut_fbleau.Avalam.logic; - -import fr.iut_fbleau.Avalam.Color; - -/** - * La classe TurnManager gère le déroulement des tours d’Avalam. - * - * Son rôle est simple : - * - identifier le joueur dont c’est le tour - * - passer au joueur suivant - * - * Elle ne contient pas de logique de mouvement, ni de validation. - * - * @author - * @version 1.0 - */ -public class TurnManager { - - /** Joueur dont c'est le tour (COLOR1 commence la partie). */ - private Color current = Color.COLOR1; - - /** - * Retourne le joueur actuel. - */ - public Color getPlayer() { - return current; - } - - /** - * Passe au joueur suivant. - */ - public void next() { - current = (current == Color.COLOR1) ? Color.COLOR2 : Color.COLOR1; - } -} diff --git a/fr/iut_fbleau/Avalam/ui/BoardView.java b/fr/iut_fbleau/Avalam/ui/BoardView.java index b5d5e9c..dc445c3 100644 --- a/fr/iut_fbleau/Avalam/ui/BoardView.java +++ b/fr/iut_fbleau/Avalam/ui/BoardView.java @@ -1,84 +1,118 @@ package fr.iut_fbleau.Avalam.ui; -import fr.iut_fbleau.Avalam.logic.GameState; +import fr.iut_fbleau.Avalam.AvalamBoard; +import fr.iut_fbleau.Avalam.Tower; import javax.swing.*; +import java.awt.*; /** - * La classe BoardView représente l'affichage complet du plateau Avalam. - * Elle s’appuie sur une architecture en couches (layered pane) pour séparer proprement : + * BoardView est la vue principale du plateau Avalam. * - *
    - *
  • HighlightLayer : les cases jouables mises en surbrillance
  • - *
  • PieceLayer : les pions affichés sous forme de boutons ronds
  • - *
+ * Elle gère : + * - l’affichage des tours (PieceLayer) + * - l’affichage des coups possibles (HighlightLayer) + * - les clics via InteractionController * - * La vue ne contient pas la logique de jeu : elle la délègue entièrement - * à un InteractionController. - * - * @author - * @version 1.0 + * Toute la logique de jeu est déléguée au moteur AvalamBoard + * et au contrôleur InteractionController. */ public class BoardView extends JLayeredPane { - /** Taille des pions affichés. */ - private final int size = 50; + /** Référence au moteur Avalam */ + private AvalamBoard board; - /** Distance entre deux cases de la grille. */ - private final int spacing = 70; - - /** Décalages pour centrer l'affichage. */ - private final int xBase = 60, yBase = 60; - - /** Accès à l'état du jeu. */ - private GameState state; - - /** Couche d'affichage des ronds verts. */ + /** Couche d’affichage des rond verts */ private HighlightLayer highlightLayer; - /** Couche d'affichage des pions. */ + /** Couche d’affichage des tours */ private PieceLayer pieceLayer; - /** Gestionnaire d'interactions utilisateur. */ + /** Contrôleur des interactions */ private InteractionController controller; + /** Décalages et dimensions pour l'affichage */ + private final int size = 50; + private final int spacing = 70; + private final int xBase = 60; + private final int yBase = 60; + + /** Callback vers AvalamWindow pour mises à jour (score, tour, fin de partie) */ + private Runnable boardUpdateCallback; + /** * Constructeur de la vue du plateau. * - * @param state l'état du jeu Avalam + * @param board moteur Avalam utilisé pour afficher la grille + * @param boardUpdateCallback callback appelé après chaque coup */ - public BoardView(GameState state) { - this.state = state; + public BoardView(AvalamBoard board, Runnable boardUpdateCallback) { + this.board = board; + this.boardUpdateCallback = boardUpdateCallback; + setLayout(null); - controller = new InteractionController(state); + // Contrôleur + this.controller = new InteractionController(board, this); + // Couche Highlight highlightLayer = new HighlightLayer(xBase, yBase, spacing, size); - pieceLayer = new PieceLayer(); - add(highlightLayer, JLayeredPane.DEFAULT_LAYER); + + // Couche des pièces + pieceLayer = new PieceLayer(); add(pieceLayer, JLayeredPane.PALETTE_LAYER); + setPreferredSize(new Dimension(800, 800)); + refresh(); } /** - * Met à jour l'affichage des couches en fonction de l'état actuel du jeu. + * Appelée par InteractionController quand un coup est joué. + * Permet à AvalamWindow de rafraîchir : + * - scores + * - affichage du joueur courant + * - détection fin de partie + */ + public void onBoardUpdated() { + if (boardUpdateCallback != null) { + boardUpdateCallback.run(); + } + } + + /** + * Met à jour toutes les couches visuelles. */ public void refresh() { + // Mise à jour des pièces pieceLayer.displayGrid( - state.getGrid(), - xBase, yBase, spacing, size, - - // Callback appelé lorsqu’un pion est cliqué - (r, c) -> { - controller.onPieceClicked(r, c); - highlightLayer.setLegalMoves(controller.getLegalMoves()); - highlightLayer.repaint(); - } + boardGrid(), + xBase, yBase, spacing, size, + (r, c) -> controller.onPieceClicked(r, c) ); + // Mise à jour des highlights + highlightLayer.setLegalMoves(controller.getLegalMoves()); + highlightLayer.repaint(); + pieceLayer.repaint(); + repaint(); + } + + /** + * Renvoie la grille de tours depuis AvalamBoard. + */ + private Tower[][] boardGrid() { + Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE]; + + for (int r = 0; r < AvalamBoard.SIZE; r++) { + for (int c = 0; c < AvalamBoard.SIZE; c++) { + grid[r][c] = board.getTowerAt(r, c); + } + } + + return grid; } } diff --git a/fr/iut_fbleau/Avalam/ui/InteractionController.java b/fr/iut_fbleau/Avalam/ui/InteractionController.java index f532679..42c7436 100644 --- a/fr/iut_fbleau/Avalam/ui/InteractionController.java +++ b/fr/iut_fbleau/Avalam/ui/InteractionController.java @@ -1,116 +1,165 @@ package fr.iut_fbleau.Avalam.ui; +import fr.iut_fbleau.Avalam.AvalamBoard; +import fr.iut_fbleau.Avalam.AvalamPly; import fr.iut_fbleau.Avalam.Tower; -import fr.iut_fbleau.Avalam.logic.GameState; -import java.awt.*; +import fr.iut_fbleau.GameAPI.AbstractPly; +import fr.iut_fbleau.GameAPI.Player; + +import java.awt.Point; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** - * La classe InteractionController gère entièrement - * la logique d'interaction de l'utilisateur : + * Le contrôleur gère toute l'interaction entre l'utilisateur et le moteur Avalam. * - *
    - *
  • Sélection d'un pion
  • - *
  • Annulation de la sélection
  • - *
  • Calcul des cases de déplacement légales
  • - *
+ * Son rôle : + * - gérer la sélection d’une tour + * - générer les coups légaux via l’API (iterator) + * - valider un déplacement (isLegal) + * - appliquer un coup (doPly) + * - mettre à jour le plateau (via refresh demandé au BoardView) * - * Aucun affichage n'est réalisé ici ; la vue (BoardView) récupère - * simplement les données calculées pour les afficher. - * - * @author - * @version 1.0 + * IMPORTANT : ce contrôleur n’affiche rien. Il envoie les infos à BoardView. */ public class InteractionController { - private GameState state; + private AvalamBoard board; - /** Position du pion sélectionné (ou -1 s'il n'y en a pas). */ - private int selectedRow = -1, selectedCol = -1; + /** Position sélectionnée (-1 si aucune) */ + private int selectedRow = -1; + private int selectedCol = -1; - /** Liste des déplacements possibles depuis la sélection. */ + /** Liste des coups légaux (en Point) autour de la sélection */ private List legalMoves = new ArrayList<>(); - /** - * Constructeur. - * - * @param state état du jeu à manipuler - */ - public InteractionController(GameState state) { - this.state = state; + /** Référence à la vue pour la rafraîchir après déplacements */ + private BoardView view; + + public InteractionController(AvalamBoard board, BoardView view) { + this.board = board; + this.view = view; } - /** - * Retourne les cases jouables calculées. - */ + /** Retourne les cases jouables (pour HighlightLayer). */ public List getLegalMoves() { return legalMoves; } /** - * Gère le clic sur un pion donné. - * - * @param r ligne de la case cliquée - * @param c colonne de la case cliquée + * Appelé lorsqu’un pion est cliqué dans BoardView. + * Gère : + * - sélection d’une tour + * - désélection + * - tentative de déplacement (si clique sur un highlight) */ public void onPieceClicked(int r, int c) { - Tower t = state.get(r, c); - if (t == null) return; - - // Annulation si on reclique la sélection + // Si on clique la même case ⇒ désélection if (r == selectedRow && c == selectedCol) { - selectedRow = -1; - selectedCol = -1; - legalMoves.clear(); + clearSelection(); + view.refresh(); return; } - // Interdiction de jouer le pion adverse - if (t.getColor() != state.getCurrentPlayer()) { - selectedRow = -1; - selectedCol = -1; - legalMoves.clear(); + // Si aucune sélection : on sélectionne un pion appartenant au joueur courant + Tower t = board.getTowerAt(r, c); + + if (t != null && t.getColor().toPlayer() == board.getCurrentPlayer()) { + selectTower(r, c); + view.refresh(); return; } + // Sinon : tentons de jouer un coup (déplacement) + if (selectedRow != -1 && selectedCol != -1) { + tryMove(r, c); + } + } + + /* ------------------------------------------------------------------------- + * SÉLECTION D’UNE TOUR + * ---------------------------------------------------------------------- */ + + private void selectTower(int r, int c) { selectedRow = r; selectedCol = c; - computeLegalMoves(); } + private void clearSelection() { + selectedRow = -1; + selectedCol = -1; + legalMoves.clear(); + } + /** - * Calcule toutes les cases accessibles à partir du pion sélectionné. + * Identifie les destinations possibles depuis la tour sélectionnée. + * Utilise l’API officielle : board.iterator() */ private void computeLegalMoves() { - legalMoves.clear(); - Tower[][] grid = state.getGrid(); - Tower src = grid[selectedRow][selectedCol]; - int h = src.getHeight(); + Iterator it = board.iterator(); - int[] d = {-1, 0, 1}; + while (it.hasNext()) { + AbstractPly p = it.next(); - for (int dr : d) { - for (int dc : d) { + if (!(p instanceof AvalamPly)) continue; + AvalamPly ap = (AvalamPly) p; - if (dr == 0 && dc == 0) continue; - - int nr = selectedRow + dr; - int nc = selectedCol + dc; - - if (!state.isInside(nr, nc)) continue; - - Tower dest = grid[nr][nc]; - if (dest == null) continue; - if (h + dest.getHeight() > 5) continue; - - legalMoves.add(new Point(nr, nc)); + // On ne garde que les coups dont la source correspond à la sélection + if (ap.getXFrom() == selectedRow && ap.getYFrom() == selectedCol) { + legalMoves.add(new Point(ap.getXTo(), ap.getYTo())); } } } + + /* ------------------------------------------------------------------------- + * TENTATIVE DE DÉPLACEMENT + * ---------------------------------------------------------------------- */ + + /** + * Tente d’exécuter un déplacement vers (r,c) si c’est un coup légal. + */ + private void tryMove(int r, int c) { + // Vérifier si (r,c) est une destination légale + boolean isLegalDest = false; + for (Point p : legalMoves) { + if (p.x == r && p.y == c) { + isLegalDest = true; + break; + } + } + if (!isLegalDest) { + clearSelection(); // clic ailleurs = désélection + view.refresh(); + return; + } + + // Construire le coup + Player cur = board.getCurrentPlayer(); + AvalamPly ply = new AvalamPly(cur, selectedRow, selectedCol, r, c); + + // Vérifier via l’API + if (board.isLegal(ply)) { + + // Appliquer via l’API + board.doPly(ply); + + // Réinitialiser la sélection + clearSelection(); + + // Recalcul du score + joueur courant + fin de partie (handled in window) + view.onBoardUpdated(); + + } else { + // Coup impossible (rare, mais pour sécurité) + clearSelection(); + } + + view.refresh(); + } }