diff --git a/fr/iut_fbleau/Bot/DivineBot.java b/fr/iut_fbleau/Bot/DivineBot.java index 7902df9..5e74552 100644 --- a/fr/iut_fbleau/Bot/DivineBot.java +++ b/fr/iut_fbleau/Bot/DivineBot.java @@ -2,14 +2,14 @@ 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é). + * Bot expert utilisant l'algorithme Alpha-Beta pour le jeu Avalam. * * Idée : - * - Utilise l'algorithme Alpha-Beta pour anticiper les coups. - * - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes. + * - Explore l'arbre des coups possibles jusqu'à une profondeur donnée. + * - Utilise l'élagage Alpha-Beta pour optimiser la recherche. + * - Évalue les positions selon le contrôle des tours et leur hauteur. */ public class DivineBot extends AbstractGamePlayer { @@ -18,184 +18,141 @@ public class DivineBot extends AbstractGamePlayer { /** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */ private final Player me; - /** Profondeur maximale de recherche avant évaluation. */ + /** Profondeur maximale de recherche (cut-off). */ private final int maxDepth; - /** Générateur aléatoire pour choisir parmi les meilleurs coups équivalents. */ + /** Générateur aléatoire pour départager les coups de même valeur. */ 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 + * Construit un bot DivineBot. + * * @param p joueur contrôlé par ce bot + * @param maxDepth profondeur maximale de recherche */ public DivineBot(Player p, int maxDepth) { super(p); this.me = p; - this.maxDepth = Math.max(1, maxDepth); + this.maxDepth = 2; } // 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) + * Méthode appelée par GameAPI : le bot doit choisir le meilleur coup possible. + * * @param board copie sûre de l'état de jeu (IBoard) + * @return le coup choisi (AbstractPly) ou null si aucun coup n'est possible */ @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; + int bestValue = Integer.MIN_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); + // On calcule la valeur du plateau après ce coup + int value = alphaBeta(next, maxDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE); - // 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); + if (value > bestValue) { + bestValue = value; + bestMoves.clear(); + bestMoves.add(m); + } else if (value == bestValue) { + bestMoves.add(m); } } - - // 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. + * Fonction récursive Alpha-Beta pour évaluer l'arbre de décision. + * * @param board état actuel du plateau + * @param depth profondeur restante à explorer + * @param alpha borne inférieure + * @param beta borne supérieure + * @return la valeur de l'évaluation */ 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 (board.isGameOver()) { + Result r = board.getResult(); + if (r == Result.DRAW) return 0; + + boolean p1Wins = (r == Result.WIN); + boolean amIP1 = (me == Player.PLAYER1); + return (p1Wins == amIP1) ? 10000 : -10000; + } + if (depth == 0) return evaluate(board); - boolean isMax = board.getCurrentPlayer() == me; + // Si c'est à moi de jouer, je maximise. Sinon, je minimise. + boolean isMax = (board.getCurrentPlayer() == me); + int best = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE; - List moves = listMoves(board); - if (moves.isEmpty()) { - return evaluate(board); - } + for (AbstractPly m : listMoves(board)) { + IBoard next = board.safeCopy(); + next.doPly(m); + int val = alphaBeta(next, depth - 1, alpha, beta); - 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); + if (isMax) { best = Math.max(best, val); alpha = Math.max(alpha, best); - - if (alpha >= beta) break; // Coupure Beta - } - 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); + } else { best = Math.min(best, val); beta = Math.min(beta, best); - - if (alpha >= beta) break; // Coupure Alpha } - return best; + if (alpha >= beta) break; } + return best; } /** - * 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. + * Heuristique spécifique à Avalam. + * Valorise le contrôle des tours, avec un bonus pour les tours de hauteur 5 (verrouillées). + * * @param board plateau à évaluer + * @return score numérique de la position (positif si avantageux) */ private int evaluate(IBoard board) { - if (!(board instanceof AvalamBoard)) return 0; AvalamBoard b = (AvalamBoard) board; + // Configuration des couleurs 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; + if (t == null || t.getHeight() == 0) continue; - int h = t.getHeight(); + // Une tour de 5 vaut beaucoup plus car elle est "verrouillée". + int val = (t.getHeight() == 5) ? 10 : 1; - // 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; + if (t.getColor() == myColor) { + score += val; + } else { + score -= val; + } } } return score; } /** - * Génère la liste de tous les coups possibles sur le plateau donné. + * Récupère la liste de tous les coups légaux disponibles sur le plateau. + * * @param board plateau actuel + * @return liste des coups possibles */ private List listMoves(IBoard board) { List moves = new ArrayList<>(); - board.iterator().forEachRemaining(moves::add); + Iterator it = board.iterator(); + while (it.hasNext()) { + moves.add(it.next()); + } return moves; } } \ No newline at end of file