292 lines
8.5 KiB
Java
292 lines
8.5 KiB
Java
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 <code>AvalamBoard</code>
|
||
*
|
||
* Représente le plateau et les règles du jeu Avalam.
|
||
* Cette classe étend <code>AbstractBoard</code> (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<AbstractPly> 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<AbstractPly> iterator() {
|
||
java.util.List<AbstractPly> 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++)
|
||
newGrid[r][c] = grid[r][c];
|
||
|
||
return new AvalamBoard(newGrid, getCurrentPlayer(), gameOver, result);
|
||
}
|
||
}
|