9 Commits

14 changed files with 1118 additions and 118 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Ignorer le répertoire bin
/bin

View File

@@ -0,0 +1,218 @@
---
title: Avalam - Diagramme de classes (complet)
---
classDiagram
class AvalamBoard{
+SIZE: int
-MAX_HEIGHT: int
-grid: Tower[][]
-gameOver: boolean
-result: Result
+AvalamBoard(Tower[][] initialGrid, Player startingPlayer)
+AvalamBoard(Tower[][] initialGrid)
-AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result)
+getTowerAt(int row, int col): Tower
-inBounds(int r, int c): boolean
-areAdjacent(int r1, int c1, int r2, int c2): boolean
-colorForPlayer(Player p): Color
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+doPly(AbstractPly c): void
+iterator(): Iterator<AbstractPly>
+safeCopy(): IBoard
}
AvalamBoard "1" *-- "many" Tower
AvalamBoard ..> AvalamPly
class AvalamPly{
-xFrom : int
-yFrom : int
-xTo : int
-yTo : int
+AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo)
+getXFrom(): int
+getXFrom(): int
+getXFrom(): int
+getXFrom(): int
+toString(): String
}
class AvalamWindow{
-board : AvalamBoard
-scoreView : ScoreView
-turnView : TurnView
-boardView : BoardView
-mode: GameMode
-botPlayer: Player
-idiotBot: IdiotBot
-alphaBot: AlphaBetaBot
-divineBot: Object
-botAnimating: boolean
+AvalamWindow()
+AvalamWindow(GameMode mode)
+AvalamWindow(GameMode mode, int alphaDepth)
+onBoardUpdated(): void
-maybePlayBotTurn(): void
-computeScore(Color c): int
-turnMessage(): String
}
AvalamWindow *-- AvalamBoard
AvalamWindow *-- BoardView
AvalamWindow *-- ScoreView
AvalamWindow *-- TurnView
AvalamWindow --> GameMode
class BoardLoader{
+loadFromFile(String resourcePath): Tower[][]
}
class BoardView{
-board: AvalamBoard
-backgroundLayer: BackgroundLayer
-highlightLayer: HighlightLayer
-pieceLayer: PieceLayer
-controller: InteractionController
-size: int
-spacing: int
-xBase: int
-yBase: int
-boardUpdateCallback: Runnable
+BoardView(AvalamBoard board, Runnable boardUpdateCallback)
+getController(): InteractionController
+setInteractionEnabled(boolean enabled): void
+onBoardUpdated(): void
+refresh(): void
-boardGrid(): Tower[][]
-boardGrid(): Tower[][]
}
BoardView *-- BackgroundLayer
BoardView *-- HighlightLayer
BoardView *-- PieceLayer
BoardView *-- InteractionController
BoardView --> AvalamBoard
class BackgroundLayer{
-img: Image
+BackgroundLayer(String resourcePath)
#paintComponent(Graphics g): void
}
class Color{
-YELLOW(int r, int g, int b)
-RED(int r, int g, int b)
-swingColor: java.awt.Color
+Color(int r, int g, int b)
+getSwingColor(): java.awt.Color
+toPlayer(): fr.iut_fbleau.GameAPI.Player
}
class GameMode{
PVP
PVBOT
PVALPHA
PVGOD
}
class HighlightLayer{
-xBase: int
-yBase: int
-spacing: int
-size: int
-legalMoves: List<Point>
+HighlightLayer(int xBase, int yBase, int spacing, int size)
+setLegalMoves(List<Point> moves): void
#paintComponent(Graphics g): void
}
class InteractionController{
-board: AvalamBoard
-selectedRow: int
-selectedCol: int
-legalMoves: List<Point>
-view: BoardView
+InteractionController(AvalamBoard board, BoardView view)
+onPieceClicked(int r, int c): void
+selectTower(int r, int c): void
-clearSelection(): void
-computeLegalMoves(): void
-tryMove(int r, int c): void
}
InteractionController --> AvalamBoard
InteractionController --> BoardView
class Main{
+main(String[] args): void
}
Main ..> AvalamWindow
Main ..> GameMode
class PieceButton{
-color: java.awt.Color
-height: int
-hover: boolean
+row: int
+col: int
+PieceButton(java.awt.Color c, int height, int row, int col)
#paintComponent(Graphics g): void
+contains(int x, int y): boolean
}
class PieceLayer{
+PieceLayer()
+displayGrid(Tower[][] grid, int xBase, int yBase, int spacing, int size, java.util.function.BiConsumer<Integer, Integer> clickCallback): void
}
class ScoreView{
-scoreY: JLabel
-scoreR: JLabel
+ScoreView(int y, int r)
+updateScores(int y, int r): void
}
class Tower{
-height: byte
-color: Color
+Tower(int height, Color color)
+createTower(Color color): Tower
+getHeight(): int
+getColor(): Color
+mergeTower(Tower src): void
+toString(): String
}
Tower --> Color
class TurnView{
-text: JLabel
+TurnView(String initial)
+setTurn(String s): void
}
BoardLoader ..> Tower
PieceLayer ..> Tower
PieceButton ..> Tower

View File

@@ -0,0 +1,27 @@
---
title: Bot - Diagramme de classes (complet)
---
classDiagram
class AlphaBetaBot{
-me: Player
-maxDepth: int
-rng: Random
+AlphaBetaBot(Player p, int maxDepth)
+giveYourMove(IBoard board): AbstractPly
-alphaBeta(IBoard board, int depth, int alpha, int beta): int
-terminalValue(IBoard board): int
-evaluate(IBoard board): int
-listMoves(IBoard board): List<AbstractPly>
}
class DivineBot{
}
class IdiotBot{
-rng: Random
+IdiotBot(Player p)
+giveYourMove(IBoard board): AbstractPly
}

View File

@@ -0,0 +1,73 @@
---
title: GameAPI - Diagramme de classes (complet)
---
classDiagram
class AbstractBoard{
<<abstract>>
-currentPlayer: Player
-history: Deque<AbstractPly>
+AbstractBoard(Player p, Deque<AbstractPly> h)
#setNextPlayer(): void
#addPlyToHistory(AbstractPly c): void
#removePlyFromHistory(): AbstractPly
#getLastPlyFromHistory(): AbstractPly
+getCurrentPlayer()
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+iterator(): Iterator<AbstractPly>
+doPly(AbstractPly c): void
+undoPly(): void
+safeCopy(): IBoard
}
class AbstractGame{
<<abstract>>
-currentBoard: IBoard
-mapPlayers: EnumMap<Player, AbstractGamePlayer>
+AbstractGame(IBoard b, EnumMap<Player,AbstractGamePlayer> m)
+run(): Result
}
class AbstractGamePlayer{
<<abstract>>
-iAm: Player
+AbstractGamePlayer(Player p)
+giveYourMove(IBoard p): AbstractPly
}
class AbstractPly{
<<abstract>>
-joueur: Player
+AbstractPly(Player j)
+getPlayer(): Player
}
class IBoard{
+getCurrentPlayer(): Player
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+iterator(): Iterator<AbstractPly>
+doPly(AbstractPly c): void
+undoPly(): void
+safeCopy(): IBoard
}
class Player{
<<enumerate>>
PLAYER1
PLAYER2
}
class Result{
<<enumerate>>
WIN
DRAW
LOSS
}

View File

@@ -280,12 +280,23 @@ public class AvalamBoard extends AbstractBoard {
*/ */
@Override @Override
public IBoard safeCopy() { public IBoard safeCopy() {
Tower[][] newGrid = new Tower[SIZE][SIZE]; Tower[][] newGrid = new Tower[SIZE][SIZE];
for (int r = 0; r < SIZE; r++) for (int r = 0; r < SIZE; r++) {
for (int c = 0; c < SIZE; c++) for (int c = 0; c < SIZE; c++) {
newGrid[r][c] = grid[r][c];
return new AvalamBoard(newGrid, getCurrentPlayer(), gameOver, result); 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());
} }
} }

View File

@@ -1,5 +1,10 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Bot.AlphaBetaBot;
// A FAIRE PLUS TARD (PVGOD)
import fr.iut_fbleau.Bot.DivineBot;
import fr.iut_fbleau.Bot.IdiotBot;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player; import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result; import fr.iut_fbleau.GameAPI.Result;
@@ -12,10 +17,19 @@ import java.awt.*;
* Fenêtre principale (interface graphique) du jeu Avalam. * Fenêtre principale (interface graphique) du jeu Avalam.
* Elle contient : * Elle contient :
* - le plateau (BoardView) * - le plateau (BoardView)
* - l'affichage du score (ScoreView) * - laffichage du score (ScoreView)
* - l'affichage du joueur courant (TurnView) * - laffichage du joueur courant (TurnView)
* *
* Elle pilote un objet <code>AvalamBoard</code> (moteur du jeu). * Elle pilote un objet <code>AvalamBoard</code> (moteur du jeu).
* Elle peut fonctionner en mode :
* - joueur vs joueur
* - joueur vs bot idiot (aléatoire)
* - joueur vs bot alpha (cut-off)
* - joueur vs bot divin (PVGOD)
*
* @version 1.0
* Date :
* Licence :
*/ */
public class AvalamWindow extends JFrame { public class AvalamWindow extends JFrame {
@@ -24,36 +38,91 @@ public class AvalamWindow extends JFrame {
/** Moteur du jeu (état + règles). */ /** Moteur du jeu (état + règles). */
private AvalamBoard board; private AvalamBoard board;
/** Vue affichant le score des deux couleurs. */ /** Vue affichant le score. */
private ScoreView scoreView; private ScoreView scoreView;
/** Vue affichant le joueur dont c'est le tour. */ /** Vue affichant le joueur courant. */
private TurnView turnView; private TurnView turnView;
/** Vue affichant le plateau et gérant les interactions de jeu. */ /** Vue affichant le plateau. */
private BoardView boardView; private BoardView boardView;
/** Mode de jeu sélectionné. */
private final GameMode mode;
/** Joueur contrôlé par le bot (si actif). */
private final Player botPlayer = Player.PLAYER2;
/** Bot idiot (utilisé si mode PVBOT). */
private final IdiotBot idiotBot;
/** Bot Alpha-Beta (utilisé si mode PVALPHA). */
private final AlphaBetaBot alphaBot;
private final DivineBot divineBot;
// A FAIRE PLUS TARD (PVGOD)
// /** Bot Divin (utilisé si mode PVGOD). */
// private final DivineBot divineBot;
/**
* A FAIRE PLUS TARD (PVGOD)
* On garde l'attribut à null pour ne pas casser la compilation,
* mais toute la logique PVGOD est désactivée/commentée.
*/
/** Indique si une animation de tour de bot est en cours. */
private boolean botAnimating = false;
//Constructeur //Constructeur
/** /**
* Construit la fenêtre et initialise linterface : * Construit la fenêtre en mode joueur vs joueur.
* - charge le plateau initial depuis Plateau.txt
* - construit les vues (score, tour, plateau)
* - affiche la fenêtre
*/ */
public AvalamWindow() { public AvalamWindow() {
this(GameMode.PVP, 4);
}
/**
* Construit la fenêtre en fonction du mode choisi.
* Pour PVALPHA/PVGOD, la profondeur par défaut est 4.
*
* @param mode mode de jeu
*/
public AvalamWindow(GameMode mode) {
this(mode, 4);
}
/**
* Construit la fenêtre en fonction du mode choisi.
* Si le mode est PVALPHA ou PVGOD, la profondeur est utilisée comme cut-off.
*
* @param mode mode de jeu
* @param alphaDepth profondeur de recherche pour Alpha-Beta / Bot Divin
*/
public AvalamWindow(GameMode mode, int alphaDepth) {
super("Avalam"); super("Avalam");
this.mode = mode;
this.idiotBot = (mode == GameMode.PVBOT) ? new IdiotBot(botPlayer) : null;
int depth = Math.max(1, alphaDepth);
this.alphaBot = (mode == GameMode.PVALPHA) ? new AlphaBetaBot(botPlayer, depth) : null;
// A FAIRE PLUS TARD (PVGOD)
this.divineBot = (mode == GameMode.PVGOD) ? new DivineBot(botPlayer, depth) : null;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
// Chargement du plateau initial depuis Plateau.txt // Chargement du plateau initial
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt"); Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
// Initialisation du moteur (PLAYER1 commence) // Initialisation du moteur (PLAYER1 commence)
board = new AvalamBoard(initialGrid); board = new AvalamBoard(initialGrid);
// Création du panneau supérieur (score + tour) // Panneau supérieur (score + tour)
JPanel topPanel = new JPanel(new GridLayout(2, 1)); JPanel topPanel = new JPanel(new GridLayout(2, 1));
topPanel.setBackground(new java.awt.Color(200, 200, 200)); topPanel.setBackground(new java.awt.Color(200, 200, 200));
@@ -69,7 +138,7 @@ public class AvalamWindow extends JFrame {
add(topPanel, BorderLayout.NORTH); add(topPanel, BorderLayout.NORTH);
// Création de la vue plateau (avec callback de mise à jour) // Plateau
boardView = new BoardView(board, this::onBoardUpdated); boardView = new BoardView(board, this::onBoardUpdated);
add(boardView, BorderLayout.CENTER); add(boardView, BorderLayout.CENTER);
@@ -77,35 +146,30 @@ public class AvalamWindow extends JFrame {
setResizable(false); setResizable(false);
setLocationRelativeTo(null); setLocationRelativeTo(null);
setVisible(true); setVisible(true);
// Si un jour le bot devait commencer (pas le cas ici), on le ferait jouer ici.
maybePlayBotTurn();
} }
//Méthodes //Méthodes
/** /**
* Méthode appelée automatiquement après chaque coup (via BoardView). * Appelée après chaque coup (humain ou bot).
* Elle rafraîchit : * Met à jour score, tour, et affiche la fin de partie.
* - les scores
* - le joueur courant
* et affiche un message si la partie est terminée.
*/ */
public void onBoardUpdated() { public void onBoardUpdated() {
// Mise à jour du score
scoreView.updateScores( scoreView.updateScores(
computeScore(Color.YELLOW), computeScore(Color.YELLOW),
computeScore(Color.RED) computeScore(Color.RED)
); );
// Mise à jour du joueur courant
turnView.setTurn(turnMessage()); turnView.setTurn(turnMessage());
// Détection de fin de partie
if (board.isGameOver()) { if (board.isGameOver()) {
Result res = board.getResult(); Result res = board.getResult();
String msg; String msg;
// Correction : ajout des "break" pour éviter le fall-through.
switch (res) { switch (res) {
case WIN: case WIN:
msg = "Le joueur jaune a gagné !"; msg = "Le joueur jaune a gagné !";
@@ -127,11 +191,102 @@ public class AvalamWindow extends JFrame {
"Partie terminée", "Partie terminée",
JOptionPane.INFORMATION_MESSAGE JOptionPane.INFORMATION_MESSAGE
); );
return;
} }
// Si on est contre un bot et que cest son tour, on déclenche son animation.
maybePlayBotTurn();
} }
/** /**
* Calcule le score d'une couleur : nombre de tours contrôlées (sommet de la tour). * Fait jouer le bot (idiot / alpha / divin) en deux étapes visibles :
* 1) sélection de la tour (affiche les coups légaux)
* 2) attente 1 seconde
* 3) déplacement vers la destination
*
* Le tout sans bloquer l'interface (Timer Swing).
*/
private void maybePlayBotTurn() {
// Mode joueur vs joueur : aucun bot
if (mode == GameMode.PVP) return;
// Sécurité
if (board.isGameOver()) return;
if (board.getCurrentPlayer() != botPlayer) return;
if (botAnimating) return;
// Vérifier qu'on a bien le bot correspondant
if (mode == GameMode.PVBOT && idiotBot == null) return;
if (mode == GameMode.PVALPHA && alphaBot == null) return;
// A FAIRE PLUS TARD (PVGOD)
if (mode == GameMode.PVGOD && divineBot == null) return;
// A FAIRE PLUS TARD (PVGOD)
// Pour l'instant, si PVGOD est sélectionné, on ne joue pas de coup bot.
botAnimating = true;
// Désactiver les interactions du joueur pendant le tour du bot.
boardView.setInteractionEnabled(false);
// Choix dun coup sur une copie sûre
AbstractPly botMove;
if (mode == GameMode.PVBOT) {
botMove = idiotBot.giveYourMove(board.safeCopy());
} else if (mode == GameMode.PVALPHA) {
botMove = alphaBot.giveYourMove(board.safeCopy());
} else {
// A FAIRE PLUS TARD (PVGOD)
botMove = divineBot.giveYourMove(board.safeCopy());
}
if (botMove == null) {
botAnimating = false;
boardView.setInteractionEnabled(true);
return;
}
if (!(botMove instanceof AvalamPly)) {
botAnimating = false;
boardView.setInteractionEnabled(true);
return;
}
AvalamPly ap = (AvalamPly) botMove;
// Étape 1 : sélection (comme un clic humain)
InteractionController ctrl = boardView.getController();
ctrl.onPieceClicked(ap.getXFrom(), ap.getYFrom());
boardView.refresh();
// Étape 2 : attendre puis cliquer la destination
javax.swing.Timer t = new javax.swing.Timer(1000, e -> {
// Sécurité : si la partie a changé entre temps
if (board.isGameOver() || board.getCurrentPlayer() != botPlayer) {
botAnimating = false;
boardView.setInteractionEnabled(true);
((javax.swing.Timer) e.getSource()).stop();
return;
}
ctrl.onPieceClicked(ap.getXTo(), ap.getYTo());
boardView.refresh();
botAnimating = false;
boardView.setInteractionEnabled(true);
((javax.swing.Timer) e.getSource()).stop();
});
t.setRepeats(false);
t.start();
}
/**
* Calcule le score d'une couleur : nombre de tours contrôlées.
* *
* @param c couleur à compter * @param c couleur à compter
* @return nombre de tours appartenant à la couleur c * @return nombre de tours appartenant à la couleur c
@@ -150,12 +305,14 @@ public class AvalamWindow extends JFrame {
} }
/** /**
* Retourne le message correspondant au joueur dont c'est le tour. * Retourne le message affiché pour le joueur courant.
* *
* @return message daffichage du tour * @return message du tour
*/ */
private String turnMessage() { private String turnMessage() {
return "Tour du joueur : " + return "Tour du joueur : " +
(board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge"); (board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge");
} }
//Affichage
} }

View File

@@ -61,10 +61,10 @@ public class BoardLoader {
switch (value) { switch (value) {
case 1: case 1:
grid[row][col] = new Tower(Color.YELLOW); grid[row][col] = Tower.createTower(Color.YELLOW);
break; break;
case 2: case 2:
grid[row][col] = new Tower(Color.RED); grid[row][col] = Tower.createTower(Color.RED);
break; break;
default: default:
grid[row][col] = null; grid[row][col] = null;

View File

@@ -1,8 +1,5 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Avalam.AvalamBoard;
import fr.iut_fbleau.Avalam.Tower;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -14,33 +11,37 @@ import java.awt.*;
* - laffichage des tours (PieceLayer) * - laffichage des tours (PieceLayer)
* - laffichage des coups possibles (HighlightLayer) * - laffichage des coups possibles (HighlightLayer)
* - laffichage du fond graphique * - laffichage du fond graphique
* - la gestion des interactions via InteractionController * - les clics via InteractionController
* *
* Cette classe ne contient aucune logique de jeu. * Cette classe ne contient aucune logique de règles du jeu.
*
* @version 1.0
* Date :
* Licence :
*/ */
public class BoardView extends JLayeredPane { public class BoardView extends JLayeredPane {
//Attributs //Attributs
/** Référence au moteur du jeu Avalam. */ /** Référence au moteur Avalam. */
private AvalamBoard board; private AvalamBoard board;
/** Couche graphique du fond du plateau. */ /** Couche daffichage du fond. */
private BackgroundLayer backgroundLayer; private BackgroundLayer backgroundLayer;
/** Couche graphique des déplacements possibles. */ /** Couche daffichage des coups possibles. */
private HighlightLayer highlightLayer; private HighlightLayer highlightLayer;
/** Couche graphique des pièces (tours). */ /** Couche daffichage des pièces. */
private PieceLayer pieceLayer; private PieceLayer pieceLayer;
/** Contrôleur des interactions utilisateur. */ /** Contrôleur des interactions. */
private InteractionController controller; private InteractionController controller;
/** Taille dun pion en pixels. */ /** Taille dun pion en pixels. */
private final int size = 50; private final int size = 50;
/** Espacement entre deux cases du plateau. */ /** Espacement entre les cases. */
private final int spacing = 70; private final int spacing = 70;
/** Décalage horizontal du plateau. */ /** Décalage horizontal du plateau. */
@@ -49,16 +50,16 @@ public class BoardView extends JLayeredPane {
/** Décalage vertical du plateau. */ /** Décalage vertical du plateau. */
private final int yBase = 60; private final int yBase = 60;
/** Callback vers AvalamWindow pour mettre à jour linterface (score, tour, fin). */ /** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */
private Runnable boardUpdateCallback; private Runnable boardUpdateCallback;
//Constructeur //Constructeur
/** /**
* Construit la vue du plateau Avalam. * Construit la vue du plateau.
* *
* @param board moteur du jeu Avalam * @param board moteur du jeu
* @param boardUpdateCallback fonction de rappel après un coup * @param boardUpdateCallback callback à appeler après un coup
*/ */
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) { public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
this.board = board; this.board = board;
@@ -66,19 +67,19 @@ public class BoardView extends JLayeredPane {
setLayout(null); setLayout(null);
// --- Contrôleur --- // Contrôleur
this.controller = new InteractionController(board, this); this.controller = new InteractionController(board, this);
// --- Couche fond --- // Couche fond
backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png"); backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png");
backgroundLayer.setBounds(0, 0, 725, 725); backgroundLayer.setBounds(0, 0, 725, 725);
add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER); add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER);
// --- Couche highlight --- // Couche highlight
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size); highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
add(highlightLayer, JLayeredPane.DEFAULT_LAYER); add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
// --- Couche des pièces --- // Couche pièces
pieceLayer = new PieceLayer(); pieceLayer = new PieceLayer();
add(pieceLayer, JLayeredPane.PALETTE_LAYER); add(pieceLayer, JLayeredPane.PALETTE_LAYER);
@@ -89,6 +90,29 @@ public class BoardView extends JLayeredPane {
//Méthodes //Méthodes
/**
* Retourne le contrôleur d'interactions (utile pour simuler les clics du bot).
*
* @return contrôleur
*/
public InteractionController getController() {
return controller;
}
/**
* Active/désactive les interactions utilisateur sur le plateau.
* Utile pendant l'animation du bot pour éviter des clics concurrents.
*
* @param enabled true pour activer, false pour désactiver
*/
public void setInteractionEnabled(boolean enabled) {
// Désactive la couche des pièces (boutons) principalement
pieceLayer.setEnabled(enabled);
for (Component c : pieceLayer.getComponents()) {
c.setEnabled(enabled);
}
}
/** /**
* Appelé par le contrôleur après un coup. * Appelé par le contrôleur après un coup.
*/ */
@@ -99,7 +123,7 @@ public class BoardView extends JLayeredPane {
} }
/** /**
* Rafraîchit laffichage du plateau. * Rafraîchit les couches visuelles.
*/ */
public void refresh() { public void refresh() {
@@ -118,7 +142,7 @@ public class BoardView extends JLayeredPane {
} }
/** /**
* Récupère la grille actuelle du moteur de jeu. * Récupère la grille depuis le moteur.
* *
* @return grille 9x9 de tours * @return grille 9x9 de tours
*/ */
@@ -136,15 +160,15 @@ public class BoardView extends JLayeredPane {
//Affichage //Affichage
/** /**
* Classe interne représentant la couche graphique du fond. * Composant affichant limage de fond.
*/ */
private static class BackgroundLayer extends JComponent { private static class BackgroundLayer extends JComponent {
private Image img; private Image img;
/** /**
* Construit une couche de fond à partir dune image. * Construit une couche de fond.
* *
* @param resourcePath chemin de limage * @param resourcePath chemin de l'image de fond
*/ */
public BackgroundLayer(String resourcePath) { public BackgroundLayer(String resourcePath) {
img = Toolkit.getDefaultToolkit().getImage( img = Toolkit.getDefaultToolkit().getImage(
@@ -153,7 +177,9 @@ public class BoardView extends JLayeredPane {
} }
/** /**
* Dessine limage de fond. * Dessine l'image de fond.
*
* @param g contexte graphique
*/ */
@Override @Override
protected void paintComponent(Graphics g) { protected void paintComponent(Graphics g) {

View File

@@ -0,0 +1,11 @@
package fr.iut_fbleau.Avalam;
/**
* Mode de jeu au lancement.
*/
public enum GameMode {
PVP, // joueur vs joueur
PVBOT, // joueur vs bot idiot
PVALPHA, // joueur vs bot alpha
PVGOD // joueur vs bot stratégique
}

View File

@@ -1,28 +1,69 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import javax.swing.*;
/** /**
* La classe <code>Main</code> * Point dentrée : propose un menu de sélection de mode, puis lance la fenêtre Avalam.
*
* Point dentrée du programme.
* Lance linterface graphique principale (<code>AvalamWindow</code>).
*/ */
public class Main { public class Main {
//Attributs
//Constructeur
public Main() {
}
//Méthodes
/**
* Méthode principale : démarre lapplication.
*
* @param args arguments de la ligne de commande (non utilisés)
*/
public static void main(String[] args) { public static void main(String[] args) {
new AvalamWindow();
SwingUtilities.invokeLater(() -> {
String[] options = {
"joueur vs joueur",
"joueur vs botidiot",
"joueur vs bot alpha",
"joueur vs bot divin (NON IMPLEMENTE)"
};
int choice = JOptionPane.showOptionDialog(
null,
"Choisissez un mode de jeu :",
"Avalam - Mode de jeu",
JOptionPane.DEFAULT_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]
);
if (choice == -1) System.exit(0);
GameMode mode;
if (choice == 1) mode = GameMode.PVBOT;
else if (choice == 2) mode = GameMode.PVALPHA;
else if (choice == 3) mode = GameMode.PVGOD;
else mode = GameMode.PVP;
// Pour ALPHA et GOD : demander une profondeur
if (mode == GameMode.PVALPHA || mode == GameMode.PVGOD) {
String s = JOptionPane.showInputDialog(
null,
"Profondeur de recherche ?\n(Conseil 4)",
(mode == GameMode.PVGOD ? "Bot Divin (PVGOD)" : "Bot Alpha-Beta"),
JOptionPane.QUESTION_MESSAGE
);
int depth = 4; // défaut
if (s != null) {
try { depth = Integer.parseInt(s.trim()); }
catch (Exception ignored) { depth = 4; }
} else {
// Annulation : on revient en PVP
new AvalamWindow(GameMode.PVP);
return;
}
if (depth < 1) depth = 1;
new AvalamWindow(mode, depth);
return;
}
new AvalamWindow(mode);
});
} }
} }

View File

@@ -9,33 +9,21 @@ package fr.iut_fbleau.Avalam;
* - une hauteur (nombre de pions empilés) * - une hauteur (nombre de pions empilés)
* *
* Cette version est volontairement compatible avec le reste du projet : * Cette version est volontairement compatible avec le reste du projet :
* - constructeur Tower(Color) utilisé par BoardLoader
* - constructeur Tower(int, Color) utilisé dans d'autres parties possibles * - constructeur Tower(int, Color) utilisé dans d'autres parties possibles
* - usine createTower(Color) utilisé par BoardLoader
* - méthode mergeTower(Tower) utilisée par AvalamBoard * - méthode mergeTower(Tower) utilisée par AvalamBoard
* - méthode merge(Tower) conservée (si elle est utilisée ailleurs)
*/ */
public class Tower { public class Tower {
//Attributs //Attributs
/** Hauteur de la tour (nombre de pions empilés). */ /** Hauteur de la tour (nombre de pions empilés). */
private int height; private byte height;
/** Couleur du sommet de la tour (propriétaire actuel). */ /** Couleur du sommet de la tour (propriétaire actuel). */
private Color color; private Color color;
//Constructeur //Constructeur
/**
* Construit une tour de hauteur 1 avec la couleur donnée.
* (Constructeur attendu par BoardLoader dans ton projet.)
*
* @param color couleur du sommet
*/
public Tower(Color color) {
this(1, color);
}
/** /**
* Construit une tour avec une hauteur et une couleur données. * Construit une tour avec une hauteur et une couleur données.
* *
@@ -43,10 +31,20 @@ public class Tower {
* @param color couleur du sommet * @param color couleur du sommet
*/ */
public Tower(int height, Color color) { public Tower(int height, Color color) {
this.height = height; this.height = (byte) height;
this.color = color; this.color = color;
} }
/**
* Construit une tour de hauteur 1 avec la couleur donnée.
* (Constructeur attendu par BoardLoader dans le projet.)
*
* @param color couleur du sommet
*/
public static Tower createTower(Color color) {
return new Tower(1, color);
}
//Méthodes //Méthodes
/** /**
@@ -73,20 +71,11 @@ public class Tower {
* *
* @param src tour source empilée sur la destination * @param src tour source empilée sur la destination
*/ */
public void merge(Tower src) { public void mergeTower(Tower src) {
this.height += src.height; this.height += src.height;
this.color = src.color; this.color = src.color;
} }
/**
* Alias de merge() pour compatibilité avec d'autres classes.
*
* @param src tour source empilée sur la destination
*/
public void mergeTower(Tower src) {
merge(src);
}
//Affichage //Affichage
/** /**

View File

@@ -0,0 +1,217 @@
package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.AvalamBoard;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Bot Alpha-Beta avec cut-off (profondeur maximale).
*
* Idée :
* - si on atteint la profondeur limite, on évalue la position (heuristique).
* - sinon, on explore les coups avec Alpha-Beta (minimax optimisé).
*
*
*/
public class AlphaBetaBot extends AbstractGamePlayer {
//Attributs
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
private final Player me;
/** Profondeur maximale de recherche (cut-off). */
private final int maxDepth;
/** Générateur aléatoire pour départager des coups de même valeur. */
private final Random rng = new Random();
//Constructeur
/**
* Construit un bot Alpha-Beta.
*
* @param p joueur contrôlé par ce bot
* @param maxDepth profondeur maximale de recherche
*/
public AlphaBetaBot(Player p, int maxDepth) {
super(p);
this.me = p;
this.maxDepth = Math.max(1, maxDepth);
}
//Méthodes
/**
* Méthode appelée par GameAPI : le bot doit choisir un coup.
*
* @param board copie sûre de l'état de jeu (IBoard)
* @return un coup (AbstractPly) ou null si aucun coup possible
*/
@Override
public AbstractPly giveYourMove(IBoard board) {
if (board == null || board.isGameOver()) return null;
List<AbstractPly> moves = listMoves(board);
if (moves.isEmpty()) return null;
boolean isMax = (board.getCurrentPlayer() == me);
int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
List<AbstractPly> bestMoves = new ArrayList<>();
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
int value = alphaBeta(next, maxDepth - 1, alpha, beta);
if (isMax) {
if (value > bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
alpha = Math.max(alpha, bestValue);
} else {
if (value < bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
beta = Math.min(beta, bestValue);
}
}
return bestMoves.get(rng.nextInt(bestMoves.size()));
}
/**
* Fonction récursive Alpha-Beta.
*/
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
if (board.isGameOver()) {
return terminalValue(board);
}
if (depth == 0) {
return evaluate(board);
}
boolean isMax = (board.getCurrentPlayer() == me);
List<AbstractPly> moves = listMoves(board);
if (moves.isEmpty()) {
return evaluate(board);
}
if (isMax) {
int best = Integer.MIN_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
best = Math.max(best, val);
alpha = Math.max(alpha, best);
if (alpha >= beta) break;
}
return best;
} else {
int best = Integer.MAX_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
best = Math.min(best, val);
beta = Math.min(beta, best);
if (alpha >= beta) break;
}
return best;
}
}
/**
* Convertit le résultat final en valeur très grande (victoire/défaite).
* Result est du point de vue de PLAYER1.
*/
private int terminalValue(IBoard board) {
Result r = board.getResult();
if (r == null) return 0;
boolean botIsP1 = (me == Player.PLAYER1);
if (r == Result.DRAW) return 0;
if (botIsP1) {
return (r == Result.WIN) ? 100000 : -100000;
} else {
return (r == Result.LOSS) ? 100000 : -100000;
}
}
/**
* Heuristique simple Avalam :
* score = (tours du bot) - (tours adverses)
*/
private int evaluate(IBoard board) {
if (!(board instanceof AvalamBoard)) return 0;
AvalamBoard b = (AvalamBoard) board;
Color botColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
Color oppColor = (me == Player.PLAYER1) ? Color.RED : Color.YELLOW;
int botTowers = 0;
int oppTowers = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
for (int c = 0; c < AvalamBoard.SIZE; c++) {
Tower t = b.getTowerAt(r, c);
if (t == null) continue;
if (t.getColor() == botColor) botTowers++;
else if (t.getColor() == oppColor) oppTowers++;
}
}
return botTowers - oppTowers;
}
/**
* Récupère tous les coups légaux via iterator().
*/
private List<AbstractPly> listMoves(IBoard board) {
List<AbstractPly> moves = new ArrayList<>();
Iterator<AbstractPly> it = board.iterator();
while (it.hasNext()) moves.add(it.next());
return moves;
}
//Affichage
}

View File

@@ -0,0 +1,185 @@
package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.*;
import fr.iut_fbleau.GameAPI.*;
import java.util.*;
/**
* Bot "Divin" (alpha-beta + évaluateur pondéré).
* * Idée :
* - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
* - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
*/
public class DivineBot extends AbstractGamePlayer {
// Attributs
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
private final Player me;
/** Profondeur maximale de recherche avant évaluation. */
private final int maxDepth;
/** Générateur aléatoire pour choisir parmi les meilleurs coups équivalents. */
private final Random rng = new Random();
// Constructeur
/**
* Construit le bot Divine.
*
* @param p joueur contrôlé par ce bot
* @param maxDepth profondeur de l'arbre de recherche
*/
public DivineBot(Player p, int maxDepth) {
super(p);
this.me = p;
this.maxDepth = Math.max(1, maxDepth);
}
// Méthodes
/**
* Méthode principale de décision du bot.
* Explore le premier niveau de l'arbre et lance les appels Alpha-Beta.
* * @param board état actuel du jeu
* @return le meilleur coup calculé (AbstractPly)
*/
@Override
public AbstractPly giveYourMove(IBoard board) {
if (board == null || board.isGameOver()) return null;
List<AbstractPly> moves = listMoves(board);
if (moves.isEmpty()) return null;
boolean isMax = board.getCurrentPlayer() == me;
int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
List<AbstractPly> bestMoves = new ArrayList<>();
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
// Appel récursif pour évaluer la suite du coup
int value = alphaBeta(next, maxDepth - 1, alpha, beta);
if (isMax) {
if (value > bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
alpha = Math.max(alpha, bestValue);
} else {
if (value < bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
beta = Math.min(beta, bestValue);
}
}
// Retourne un coup au hasard parmi les meilleurs ex-aequo
return bestMoves.get(rng.nextInt(bestMoves.size()));
}
/**
* Algorithme récursif de recherche avec élagage Alpha-Beta.
*/
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
// Cas de base : fin de partie ou limite de profondeur atteinte
if (board.isGameOver()) return terminalValue(board);
if (depth == 0) return evaluate(board);
boolean isMax = board.getCurrentPlayer() == me;
for (AbstractPly m : listMoves(board)) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
if (isMax) {
alpha = Math.max(alpha, val);
if (alpha >= beta) break; // Coupure Beta
} else {
beta = Math.min(beta, val);
if (alpha >= beta) break; // Coupure Alpha
}
}
return isMax ? alpha : beta;
}
/**
* Calcule la valeur de l'état final (Victoire / Défaite).
*/
private int terminalValue(IBoard board) {
Result r = board.getResult();
if (r == null) return 0;
if (r == Result.DRAW) return 0;
boolean botIsP1 = (me == Player.PLAYER1);
// Si le bot gagne, valeur positive élevée, sinon valeur négative
return ((r == Result.WIN) == botIsP1) ? 100000 : -100000;
}
/**
* Heuristique évoluée pour Avalam :
* Calcule un score basé sur le contrôle des tours et leur hauteur.
* Les tours de hauteur 5 sont prioritaires car elles sont bloquées.
*/
private int evaluate(IBoard board) {
if (!(board instanceof AvalamBoard)) return 0;
AvalamBoard b = (AvalamBoard) board;
Color myColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
Color oppColor = (myColor == Color.YELLOW) ? Color.RED : Color.YELLOW;
int score = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
for (int c = 0; c < AvalamBoard.SIZE; c++) {
Tower t = b.getTowerAt(r, c);
if (t == null) continue;
int h = t.getHeight();
// Pondération selon la hauteur (heuristique "Divine")
int value =
(h == 5) ? 1000 :
(h == 4) ? 300 :
(h == 3) ? 120 :
(h == 2) ? 40 : 10;
if (t.getColor() == myColor) score += value;
else score -= value;
}
}
return score;
}
/**
* Génère la liste de tous les coups possibles sur le plateau donné.
*/
private List<AbstractPly> listMoves(IBoard board) {
List<AbstractPly> moves = new ArrayList<>();
board.iterator().forEachRemaining(moves::add);
return moves;
}
}

View File

@@ -0,0 +1,43 @@
package fr.iut_fbleau.Bot;
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.IBoard;
import fr.iut_fbleau.GameAPI.Player;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Bot idiot : choisit un coup légal au hasard parmi ceux retournés par IBoard.iterator().
* Compatible avec n'importe quel jeu respectant GameAPI (dont AvalamBoard).
*/
public class IdiotBot extends AbstractGamePlayer {
private final Random rng;
public IdiotBot(Player p) {
super(p);
this.rng = new Random();
}
@Override
public AbstractPly giveYourMove(IBoard board) {
// Si la partie est terminée ou qu'il n'y a pas de coups, on ne joue rien.
if (board == null || board.isGameOver()) return null;
Iterator<AbstractPly> it = board.iterator();
List<AbstractPly> moves = new ArrayList<>();
while (it.hasNext()) {
moves.add(it.next());
}
if (moves.isEmpty()) return null;
return moves.get(rng.nextInt(moves.size()));
}
}