diff --git a/fr/iut_fbleau/Avalam/AvalamWindow.java b/fr/iut_fbleau/Avalam/AvalamWindow.java index ddec50e..1497478 100644 --- a/fr/iut_fbleau/Avalam/AvalamWindow.java +++ b/fr/iut_fbleau/Avalam/AvalamWindow.java @@ -2,7 +2,7 @@ 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.DivineBot; import fr.iut_fbleau.Bot.IdiotBot; import fr.iut_fbleau.GameAPI.AbstractPly; import fr.iut_fbleau.GameAPI.Player; @@ -59,6 +59,8 @@ public class AvalamWindow extends JFrame { /** 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; @@ -68,7 +70,6 @@ public class AvalamWindow extends JFrame { * 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; @@ -110,7 +111,7 @@ public class AvalamWindow extends JFrame { 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; + this.divineBot = (mode == GameMode.PVGOD) ? new DivineBot(botPlayer, depth) : null; setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); @@ -220,11 +221,10 @@ public class AvalamWindow extends JFrame { if (mode == GameMode.PVALPHA && alphaBot == null) return; // A FAIRE PLUS TARD (PVGOD) - // if (mode == GameMode.PVGOD && divineBot == null) return; + 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; @@ -239,8 +239,7 @@ public class AvalamWindow extends JFrame { botMove = alphaBot.giveYourMove(board.safeCopy()); } else { // A FAIRE PLUS TARD (PVGOD) - // botMove = divineBot.giveYourMove(board.safeCopy()); - botMove = null; + botMove = divineBot.giveYourMove(board.safeCopy()); } if (botMove == null) { diff --git a/fr/iut_fbleau/Bot/DivineBot.java b/fr/iut_fbleau/Bot/DivineBot.java index cbc6fec..0361e98 100644 --- a/fr/iut_fbleau/Bot/DivineBot.java +++ b/fr/iut_fbleau/Bot/DivineBot.java @@ -1,22 +1,185 @@ 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 fr.iut_fbleau.Avalam.*; +import fr.iut_fbleau.GameAPI.*; import java.util.*; /** - * Bot "Divin" (fort) pour Avalam. - * - * - * Objectif : trop fort. */ -public class DivineBot{ + * 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 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); + + // 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 listMoves(IBoard board) { + List moves = new ArrayList<>(); + board.iterator().forEachRemaining(moves::add); + return moves; + } +} \ No newline at end of file