package fr.iut_fbleau.HexGame; import fr.iut_fbleau.GameAPI.*; public class MiniMaxBot extends AbstractGamePlayer { private int MAXDEPTH = 5; /** * En dessous (ou égal) à ce nombre de coups restants (cases vides), * on considère que l'arbre est "petit" et on fait un alpha-bêta simple : * pas de cut-off, on explore jusqu'aux positions terminales (isGameOver()). */ private int SMALL_TREE_MOVE_LIMIT = 6; public MiniMaxBot(Player me) { super(me); // Correct constructor usage } public Boolean jesuisMinimax(){ return true; } @Override public AbstractPly giveYourMove(IBoard board) { HexBoard hb = (HexBoard) board; float bestScore = -Float.MAX_VALUE; HexPly bestMove = null; // Détermine si l'arbre est petit ou grand int movesLeft = countLegalMoves(hb); boolean useCutoff = (movesLeft > SMALL_TREE_MOVE_LIMIT); int depthToUse = useCutoff ? MAXDEPTH : 0; // 0 car en "simple" on ne coupe pas sur depth for (int i = 0; i < hb.getSize(); i++) { for (int j = 0; j < hb.getSize(); j++) { HexPly move = new HexPly(hb.getCurrentPlayer(), i, j); if (hb.isLegal(move)) { hb.doPly(move); float score = minimax(hb, depthToUse, -Float.MAX_VALUE, Float.MAX_VALUE, true, useCutoff); if (score > bestScore) { bestScore = score; bestMove = move; } hb.undoPly(); } } } return bestMove; } /** * Minimax + alpha-bêta * - useCutoff = false : alpha-bêta "simple" -> on s'arrête uniquement sur isGameOver() * - useCutoff = true : alpha-bêta avec cut-off -> arrêt si depth == 0 (heuristique) */ private float minimax(HexBoard board, int depth, float alpha, float beta, boolean isMaximizing, boolean useCutoff) { // Toujours prioritaire : position terminale if (board.isGameOver()) { return terminalScore(board); } // Cut-off uniquement si demandé if (useCutoff && depth == 0) { return evaluateBoard(board); } if (isMaximizing) { float bestScore = -Float.MAX_VALUE; for (int i = 0; i < board.getSize(); i++) { for (int j = 0; j < board.getSize(); j++) { HexPly move = new HexPly(board.getCurrentPlayer(), i, j); if (board.isLegal(move)) { board.doPly(move); int nextDepth = useCutoff ? (depth - 1) : depth; float score = minimax(board, nextDepth, alpha, beta, false, useCutoff); bestScore = Math.max(bestScore, score); alpha = Math.max(alpha, bestScore); if (beta <= alpha) { board.undoPly(); break; // Pruning } board.undoPly(); } } } return bestScore; } else { float bestScore = Float.MAX_VALUE; for (int i = 0; i < board.getSize(); i++) { for (int j = 0; j < board.getSize(); j++) { HexPly move = new HexPly(board.getCurrentPlayer(), i, j); if (board.isLegal(move)) { board.doPly(move); int nextDepth = useCutoff ? (depth - 1) : depth; float score = minimax(board, nextDepth, alpha, beta, true, useCutoff); bestScore = Math.min(bestScore, score); beta = Math.min(beta, bestScore); if (beta <= alpha) { board.undoPly(); break; // Pruning } board.undoPly(); } } } return bestScore; } } /** * Score terminal (feuille) du point de vue de PLAYER1 : * - WIN : PLAYER1 gagne * - LOSS : PLAYER1 perd */ private float terminalScore(HexBoard board) { Result r = board.getResult(); if (r == null) return 0f; if (r == Result.WIN) return 1000000f; return -1000000f; } /** * Heuristique actuelle (inchangée) : distance au centre des pions PLAYER1. * (Je ne la modifie pas pour ne pas toucher à la logique existante.) */ private float evaluateBoard(HexBoard board) { int size = board.getSize(); int center = size / 2; int score = 0; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (board.getCellPlayer(i, j) == Player.PLAYER1) { score += Math.abs(i - center) + Math.abs(j - center); // Distance from center } } } return score; } /** * Compte les coups légaux (cases vides) sur le plateau courant. */ private int countLegalMoves(HexBoard board) { int count = 0; for (int i = 0; i < board.getSize(); i++) { for (int j = 0; j < board.getSize(); j++) { if (board.getCellPlayer(i, j) == null) { count++; } } } return count; } }