From e209e5f1020d14b82bc003a5d3322fd5aaabadf8 Mon Sep 17 00:00:00 2001 From: raban Date: Fri, 30 Jan 2026 13:40:04 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Bot=20divin=20en=20cours=20de=20cr=C3=A9ati?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fr/iut_fbleau/Bot/DivineBot.java | 170 +++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/fr/iut_fbleau/Bot/DivineBot.java b/fr/iut_fbleau/Bot/DivineBot.java index cbc6fec..3ffd569 100644 --- a/fr/iut_fbleau/Bot/DivineBot.java +++ b/fr/iut_fbleau/Bot/DivineBot.java @@ -18,5 +18,175 @@ import java.util.*; * * Objectif : trop fort. */ public class DivineBot{ + private final Player me; + private final int maxDepth; + private final Random rng = new Random(); + public DivineBot(Player p, int maxDepth) { + super(p); + this.me = p; + this.maxDepth = Math.max(1, maxDepth); + } + + // ===================== COUP À JOUER ===================== + + @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); + + 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())); + } + + // ===================== 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); + } + + List moves = listMoves(board); + if (moves.isEmpty()) { + return evaluate(board); + } + + boolean isMax = board.getCurrentPlayer() == me; + + 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; + } + } + + // ===================== TERMINAL ===================== + + 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; + } + } + + // ===================== ÉVALUATEUR ===================== + + /** + * Évaluateur heuristique Avalam : + * - tours finales > quasi finales > stables > faibles + * - approximation du score final + */ + 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 = (me == Player.PLAYER1) ? 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(); + int value; + + if (h == 5) value = 1000; // tour gagnée + else if (h == 4) value = 300; // quasi gagnée + else if (h == 3) value = 120; // stable + else if (h == 2) value = 40; + else value = 10; + + if (t.getColor() == myColor) score += value; + else if (t.getColor() == oppColor) score -= value; + } + } + + return score; + } + + // ===================== OUTILS ===================== + + private List listMoves(IBoard board) { + List moves = new ArrayList<>(); + Iterator it = board.iterator(); + while (it.hasNext()) moves.add(it.next()); + return moves; + } } -- 2.52.0 From 2a00accfd80f0e5e37a098c1e0ea78cdc2200eb4 Mon Sep 17 00:00:00 2001 From: raban Date: Fri, 30 Jan 2026 14:11:13 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Bot=20Divin=20et=20AvalamWindow=20connect?= =?UTF-8?q?=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fr/iut_fbleau/Avalam/AvalamWindow.java | 13 +-- fr/iut_fbleau/Bot/DivineBot.java | 155 ++++++++++++------------- 2 files changed, 80 insertions(+), 88 deletions(-) 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 3ffd569..0361e98 100644 --- a/fr/iut_fbleau/Bot/DivineBot.java +++ b/fr/iut_fbleau/Bot/DivineBot.java @@ -1,35 +1,51 @@ 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); } - // ===================== COUP À JOUER ===================== + // 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) { @@ -50,6 +66,7 @@ public class DivineBot{ 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) { @@ -73,80 +90,57 @@ public class DivineBot{ } } + // Retourne un coup au hasard parmi les meilleurs ex-aequo return bestMoves.get(rng.nextInt(bestMoves.size())); } - // ===================== ALPHA-BETA ===================== - + /** + * Algorithme récursif de recherche avec élagage 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); - } - - List moves = listMoves(board); - if (moves.isEmpty()) { - return evaluate(board); - } + // 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; - if (isMax) { - int best = Integer.MIN_VALUE; - for (AbstractPly m : moves) { - IBoard next = board.safeCopy(); - next.doPly(m); + for (AbstractPly m : listMoves(board)) { + 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); + int val = alphaBeta(next, depth - 1, alpha, beta); - if (alpha >= beta) break; + 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 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; } + + return isMax ? alpha : beta; } - // ===================== TERMINAL ===================== - + /** + * Calcule la valeur de l'état final (Victoire / Défaite). + */ 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; - } + boolean botIsP1 = (me == Player.PLAYER1); + // Si le bot gagne, valeur positive élevée, sinon valeur négative + return ((r == Result.WIN) == botIsP1) ? 100000 : -100000; } - // ===================== ÉVALUATEUR ===================== - /** - * Évaluateur heuristique Avalam : - * - tours finales > quasi finales > stables > faibles - * - approximation du score final + * 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) { @@ -154,7 +148,7 @@ public class DivineBot{ AvalamBoard b = (AvalamBoard) board; Color myColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED; - Color oppColor = (me == Player.PLAYER1) ? Color.RED : Color.YELLOW; + Color oppColor = (myColor == Color.YELLOW) ? Color.RED : Color.YELLOW; int score = 0; @@ -165,28 +159,27 @@ public class DivineBot{ if (t == null) continue; int h = t.getHeight(); - int value; - - if (h == 5) value = 1000; // tour gagnée - else if (h == 4) value = 300; // quasi gagnée - else if (h == 3) value = 120; // stable - else if (h == 2) value = 40; - else value = 10; + + // 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 if (t.getColor() == oppColor) score -= value; + else score -= value; } } - return score; } - // ===================== OUTILS ===================== - + /** + * Génère la liste de tous les coups possibles sur le plateau donné. + */ private List listMoves(IBoard board) { List moves = new ArrayList<>(); - Iterator it = board.iterator(); - while (it.hasNext()) moves.add(it.next()); + board.iterator().forEachRemaining(moves::add); return moves; } -} +} \ No newline at end of file -- 2.52.0