Jeu jouable avec fin de parti

This commit is contained in:
2025-11-25 16:02:23 -05:00
parent d23aeb266f
commit 1921b523c6
11 changed files with 537 additions and 913 deletions

View File

@@ -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 <code>BoardView</code> représente l'affichage complet du plateau Avalam.
* Elle sappuie sur une architecture en couches (layered pane) pour séparer proprement :
* BoardView est la vue principale du plateau Avalam.
*
* <ul>
* <li><b>HighlightLayer</b> : les cases jouables mises en surbrillance</li>
* <li><b>PieceLayer</b> : les pions affichés sous forme de boutons ronds</li>
* </ul>
* Elle gère :
* - laffichage des tours (PieceLayer)
* - laffichage 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 <code>InteractionController</code>.
*
* @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 daffichage des rond verts */
private HighlightLayer highlightLayer;
/** Couche d'affichage des pions. */
/** Couche daffichage 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é lorsquun 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;
}
}

View File

@@ -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 <code>InteractionController</code> 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.
*
* <ul>
* <li>Sélection d'un pion</li>
* <li>Annulation de la sélection</li>
* <li>Calcul des cases de déplacement légales</li>
* </ul>
* Son rôle :
* - gérer la sélection dune tour
* - générer les coups légaux via lAPI (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 naffiche 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<Point> 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<Point> 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é lorsquun pion est cliqué dans BoardView.
* Gère :
* - sélection dune 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 DUNE 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 lAPI officielle : board.iterator()
*/
private void computeLegalMoves() {
legalMoves.clear();
Tower[][] grid = state.getGrid();
Tower src = grid[selectedRow][selectedCol];
int h = src.getHeight();
Iterator<AbstractPly> 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 dexécuter un déplacement vers (r,c) si cest 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 lAPI
if (board.isLegal(ply)) {
// Appliquer via lAPI
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();
}
}