diff --git a/fr/iut_fbleau/Avalam/AvalamBoard.java b/fr/iut_fbleau/Avalam/AvalamBoard.java
index 6c23677..a91a8a9 100644
--- a/fr/iut_fbleau/Avalam/AvalamBoard.java
+++ b/fr/iut_fbleau/Avalam/AvalamBoard.java
@@ -280,12 +280,23 @@ public class AvalamBoard extends AbstractBoard {
*/
@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];
+ for (int r = 0; r < SIZE; r++) {
+ for (int c = 0; c < SIZE; 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());
}
}
diff --git a/fr/iut_fbleau/Avalam/AvalamWindow.java b/fr/iut_fbleau/Avalam/AvalamWindow.java
index d0e6304..ddec50e 100644
--- a/fr/iut_fbleau/Avalam/AvalamWindow.java
+++ b/fr/iut_fbleau/Avalam/AvalamWindow.java
@@ -1,5 +1,8 @@
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;
@@ -9,24 +12,25 @@ import javax.swing.*;
import java.awt.*;
/**
-* La classe AvalamWindow
-*
-* Fenêtre principale (interface graphique) du jeu Avalam.
-* Elle contient :
-* - le plateau (BoardView)
-* - l’affichage du score (ScoreView)
-* - l’affichage du joueur courant (TurnView)
-*
-* Elle pilote un objet AvalamBoard (moteur du jeu).
-* Elle peut fonctionner en mode :
-* - joueur vs joueur
-* - joueur vs bot idiot (aléatoire)
-* - joueur vs bot alpha (préparé)
-*
-* @version 1.0
-* Date :
-* Licence :
-*/
+ * La classe AvalamWindow
+ *
+ * Fenêtre principale (interface graphique) du jeu Avalam.
+ * Elle contient :
+ * - le plateau (BoardView)
+ * - l’affichage du score (ScoreView)
+ * - l’affichage du joueur courant (TurnView)
+ *
+ * Elle pilote un objet AvalamBoard (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 {
//Attributs
@@ -52,29 +56,62 @@ public class AvalamWindow extends JFrame {
/** Bot idiot (utilisé si mode PVBOT). */
private final IdiotBot idiotBot;
+ /** Bot Alpha-Beta (utilisé si mode PVALPHA). */
+ private final AlphaBetaBot alphaBot;
+
+ // 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.
+ */
+ private final Object divineBot = null;
+
/** Indique si une animation de tour de bot est en cours. */
private boolean botAnimating = false;
//Constructeur
/**
- * Construit la fenêtre en mode joueur vs joueur.
- */
+ * Construit la fenêtre en mode joueur vs joueur.
+ */
public AvalamWindow() {
- this(GameMode.PVP);
+ this(GameMode.PVP, 4);
}
/**
- * Construit la fenêtre en fonction du mode choisi.
- *
- * @param mode mode de jeu
- */
+ * 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");
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);
setLayout(new BorderLayout());
@@ -109,16 +146,16 @@ public class AvalamWindow extends JFrame {
setLocationRelativeTo(null);
setVisible(true);
- // Si un jour le bot doit commencer, on peut déclencher ici.
+ // Si un jour le bot devait commencer (pas le cas ici), on le ferait jouer ici.
maybePlayBotTurn();
}
//Méthodes
/**
- * Appelée après chaque coup (humain ou bot).
- * Met à jour score, tour, et affiche la fin de partie.
- */
+ * Appelée après chaque coup (humain ou bot).
+ * Met à jour score, tour, et affiche la fin de partie.
+ */
public void onBoardUpdated() {
scoreView.updateScores(
@@ -161,27 +198,51 @@ public class AvalamWindow extends JFrame {
}
/**
- * Fait jouer le bot 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).
- */
+ * 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() {
- if (mode != GameMode.PVBOT) return;
+ // 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.
+ if (mode == GameMode.PVGOD) return;
+
botAnimating = true;
// Désactiver les interactions du joueur pendant le tour du bot.
boardView.setInteractionEnabled(false);
// Choix d’un coup sur une copie sûre
- AbstractPly botMove = idiotBot.giveYourMove(board.safeCopy());
+ 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());
+ botMove = null;
+ }
+
if (botMove == null) {
botAnimating = false;
boardView.setInteractionEnabled(true);
@@ -226,11 +287,11 @@ public class AvalamWindow extends JFrame {
}
/**
- * Calcule le score d'une couleur : nombre de tours contrôlées.
- *
- * @param c couleur à compter
- * @return nombre de tours appartenant à la couleur c
- */
+ * Calcule le score d'une couleur : nombre de tours contrôlées.
+ *
+ * @param c couleur à compter
+ * @return nombre de tours appartenant à la couleur c
+ */
private int computeScore(Color c) {
int score = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
@@ -245,10 +306,10 @@ public class AvalamWindow extends JFrame {
}
/**
- * Retourne le message affiché pour le joueur courant.
- *
- * @return message du tour
- */
+ * Retourne le message affiché pour le joueur courant.
+ *
+ * @return message du tour
+ */
private String turnMessage() {
return "Tour du joueur : " +
(board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge");
diff --git a/fr/iut_fbleau/Avalam/GameMode.java b/fr/iut_fbleau/Avalam/GameMode.java
index 8d5ac1b..09c306a 100644
--- a/fr/iut_fbleau/Avalam/GameMode.java
+++ b/fr/iut_fbleau/Avalam/GameMode.java
@@ -6,5 +6,6 @@ package fr.iut_fbleau.Avalam;
public enum GameMode {
PVP, // joueur vs joueur
PVBOT, // joueur vs bot idiot
- PVALPHA // joueur vs bot alpha (préparé)
+ PVALPHA, // joueur vs bot alpha
+ PVGOD // joueur vs bot stratégique
}
diff --git a/fr/iut_fbleau/Avalam/Main.java b/fr/iut_fbleau/Avalam/Main.java
index 91bb0aa..6cd72de 100644
--- a/fr/iut_fbleau/Avalam/Main.java
+++ b/fr/iut_fbleau/Avalam/Main.java
@@ -14,7 +14,8 @@ public class Main {
String[] options = {
"joueur vs joueur",
"joueur vs botidiot",
- "joueur vs bot alpha"
+ "joueur vs bot alpha",
+ "joueur vs bot divin (NON IMPLEMENTE)"
};
int choice = JOptionPane.showOptionDialog(
@@ -28,20 +29,38 @@ public class Main {
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;
- // Si alpha choisi : non implémenté, on prévient et on lance en PVP (préparation).
- if (mode == GameMode.PVALPHA) {
- JOptionPane.showMessageDialog(
+ // Pour ALPHA et GOD : demander une profondeur
+ if (mode == GameMode.PVALPHA || mode == GameMode.PVGOD) {
+
+ String s = JOptionPane.showInputDialog(
null,
- "Bot Alpha-Beta non implémenté pour l'instant.\nLancement en joueur vs joueur.",
- "Information",
- JOptionPane.INFORMATION_MESSAGE
+ "Profondeur de recherche ?\n(Conseil 4)",
+ (mode == GameMode.PVGOD ? "Bot Divin (PVGOD)" : "Bot Alpha-Beta"),
+ JOptionPane.QUESTION_MESSAGE
);
- mode = GameMode.PVP;
+
+ 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);
diff --git a/fr/iut_fbleau/Bot/AlphaBetaBot.java b/fr/iut_fbleau/Bot/AlphaBetaBot.java
index 459b99f..8694e42 100644
--- a/fr/iut_fbleau/Bot/AlphaBetaBot.java
+++ b/fr/iut_fbleau/Bot/AlphaBetaBot.java
@@ -1,22 +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 (préparé).
- * Pour l'instant non implémenté.
+ * 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 {
- public AlphaBetaBot(Player p) {
+ //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) {
- throw new UnsupportedOperationException("AlphaBetaBot non implémenté pour l'instant.");
+
+ if (board == null || board.isGameOver()) return null;
+
+ List moves = listMoves(board);
+ if (moves.isEmpty()) return null;
+
+ boolean isMax = (board.getCurrentPlayer() == me);
+ int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+
+ List 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 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 listMoves(IBoard board) {
+ List moves = new ArrayList<>();
+ Iterator it = board.iterator();
+ while (it.hasNext()) moves.add(it.next());
+ return moves;
+ }
+
+ //Affichage
}
diff --git a/fr/iut_fbleau/Bot/DivineBot.java b/fr/iut_fbleau/Bot/DivineBot.java
new file mode 100644
index 0000000..cbc6fec
--- /dev/null
+++ b/fr/iut_fbleau/Bot/DivineBot.java
@@ -0,0 +1,22 @@
+package fr.iut_fbleau.Bot;
+
+import fr.iut_fbleau.Avalam.AvalamBoard;
+import fr.iut_fbleau.Avalam.AvalamPly;
+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.*;
+
+/**
+ * Bot "Divin" (fort) pour Avalam.
+ *
+ *
+ * Objectif : trop fort. */
+public class DivineBot{
+
+}