|
|
|
|
@@ -6,6 +6,13 @@ 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
|
|
|
|
|
}
|
|
|
|
|
@@ -20,12 +27,19 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
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, MAXDEPTH, -Float.MAX_VALUE, Float.MAX_VALUE, true);
|
|
|
|
|
|
|
|
|
|
float score = minimax(hb, depthToUse, -Float.MAX_VALUE, Float.MAX_VALUE, true, useCutoff);
|
|
|
|
|
|
|
|
|
|
if (score > bestScore) {
|
|
|
|
|
bestScore = score;
|
|
|
|
|
bestMove = move;
|
|
|
|
|
@@ -37,8 +51,19 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
return bestMove;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private float minimax(HexBoard board, int depth, float alpha, float beta, boolean isMaximizing) {
|
|
|
|
|
if (depth == 0 || board.isGameOver()) {
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -49,10 +74,18 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
|
|
|
|
|
if (board.isLegal(move)) {
|
|
|
|
|
board.doPly(move);
|
|
|
|
|
float score = minimax(board, depth - 1, alpha, beta, false);
|
|
|
|
|
|
|
|
|
|
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) break; // Pruning
|
|
|
|
|
|
|
|
|
|
if (beta <= alpha) {
|
|
|
|
|
board.undoPly();
|
|
|
|
|
break; // Pruning
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
board.undoPly();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -65,10 +98,18 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
|
|
|
|
|
if (board.isLegal(move)) {
|
|
|
|
|
board.doPly(move);
|
|
|
|
|
float score = minimax(board, depth - 1, alpha, beta, true);
|
|
|
|
|
|
|
|
|
|
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) break; // Pruning
|
|
|
|
|
|
|
|
|
|
if (beta <= alpha) {
|
|
|
|
|
board.undoPly();
|
|
|
|
|
break; // Pruning
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
board.undoPly();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -77,6 +118,22 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
@@ -90,4 +147,19 @@ public class MiniMaxBot extends AbstractGamePlayer {
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|