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 java.util.ArrayDeque; import java.util.Iterator; /** * La classe AvalamBoard * * Représente le plateau et les règles du jeu Avalam. * Cette classe étend AbstractBoard (GameAPI) et fournit : * - la génération des coups (iterator) * - le test de légalité (isLegal) * - l’application d’un coup (doPly) * - la détection de fin de partie (isGameOver) * - le calcul du résultat (getResult) */ public class AvalamBoard extends AbstractBoard { //Attributs /** Taille du plateau Avalam (9x9). */ public static final int SIZE = 9; /** Hauteur maximale autorisée pour une tour après fusion. */ private static final int MAX_HEIGHT = 5; /** Grille du plateau : chaque case contient une tour (Tower) ou null si vide. */ private final Tower[][] grid; /** Indique si la partie est terminée (mémoïsation). */ private boolean gameOver = false; /** Résultat de la partie si elle est terminée (mémoïsation). */ private Result result = null; //Constructeur /** * Construit un plateau Avalam à partir d’une grille initiale et d’un joueur qui commence. * * @param initialGrid grille initiale (Tower ou null) * @param startingPlayer joueur qui commence (PLAYER1 ou PLAYER2) */ 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]; } /** * Construit un plateau Avalam à partir d’une grille initiale. * Par défaut, PLAYER1 commence. * * @param initialGrid grille initiale (Tower ou null) */ public AvalamBoard(Tower[][] initialGrid) { this(initialGrid, Player.PLAYER1); } /** * Constructeur interne utilisé par safeCopy(). * * @param grid grille à réutiliser * @param current joueur courant * @param gameOver état “partie terminée” * @param result résultat mémorisé */ private AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result) { super(current, new ArrayDeque<>()); this.grid = grid; this.gameOver = gameOver; this.result = result; } //Méthodes /** * Retourne la tour située à la position (row, col), ou null si hors bornes ou vide. * * @param row ligne * @param col colonne * @return tour présente ou null */ public Tower getTowerAt(int row, int col) { return inBounds(row, col) ? grid[row][col] : null; } /** * Teste si une position est à l’intérieur du plateau. * * @param r ligne * @param c colonne * @return true si (r,c) est dans [0..SIZE-1] */ private boolean inBounds(int r, int c) { return r >= 0 && r < SIZE && c >= 0 && c < SIZE; } /** * Teste si deux cases sont adjacentes (8-voisinage). * * @param r1 ligne source * @param c1 colonne source * @param r2 ligne destination * @param c2 colonne destination * @return true si les cases sont voisines et différentes */ 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)); } /** * Associe un joueur GameAPI à une couleur Avalam. * * @param p joueur (PLAYER1/PLAYER2) * @return couleur correspondante (YELLOW/RED) */ private Color colorForPlayer(Player p) { return (p == Player.PLAYER1 ? Color.YELLOW : Color.RED); } /** * Indique si la partie est terminée. * Ici : fin lorsque l’itérateur de coups légaux ne produit plus aucun coup. * * @return true si aucun coup n’est possible */ @Override public boolean isGameOver() { if (gameOver) return true; Iterator it = iterator(); if (it.hasNext()) return false; gameOver = true; return true; } /** * Retourne le résultat si la partie est terminée. * Règle utilisée ici : comparaison du nombre de tours contrôlées par chaque couleur. * * @return WIN / LOSS / DRAW ou null si partie non terminée */ @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; } /** * Teste si un coup est légal selon les règles implémentées : * - coup de type AvalamPly * - source/destination dans le plateau et différentes * - source et destination non null * - la tour source appartient au joueur courant (couleur du sommet) * - cases adjacentes * - couleurs différentes entre source et destination (règle de ce projet) * - hauteur finale <= MAX_HEIGHT * * @param c coup à tester * @return true si le coup est légal */ @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; return true; } /** * Applique un coup légal : * - fusion de la tour source sur la destination * - la case source devient vide * - passage au joueur suivant via super.doPly * * @param c coup à jouer */ @Override public void doPly(AbstractPly c) { 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); gameOver = false; result = null; } /** * Retourne un itérateur sur tous les coups légaux du joueur courant. * Génération brute : pour chaque case et chaque voisin (8 directions), on teste isLegal(). * * @return itérateur de coups possibles (AbstractPly) */ @Override public Iterator iterator() { java.util.List moves = new java.util.ArrayList<>(); Player cur = getCurrentPlayer(); 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; int nr = r + dr, nc = c + dc; AvalamPly p = new AvalamPly(cur, r, c, nr, nc); if (isLegal(p)) moves.add(p); } } } } return moves.iterator(); } /** * Retourne une copie “sûre” de l’état du plateau. * Ici, la grille est recopiée case par case (copie des références Tower). * * @return copie du plateau (IBoard) */ @Override public IBoard safeCopy() { Tower[][] newGrid = new Tower[SIZE][SIZE]; for (int r = 0; r < SIZE; r++) { for (int c = 0; c < SIZE; c++) { Tower t = grid[r][c]; if (t == null) { newGrid[r][c] = null; } else { // Copie profonde : on recrée une nouvelle Tower indépendante newGrid[r][c] = new Tower(t.getHeight(), t.getColor()); } } } // On conserve le joueur courant return new AvalamBoard(newGrid, getCurrentPlayer()); } }