Compare commits
13 Commits
2cf929e024
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3aba1312fb | |||
| c44e7495a4 | |||
| 86a86f598c | |||
| 2766fa7632 | |||
| c5d01dd2bb | |||
| f40f3daa1d | |||
| ebfc9d7d78 | |||
| 88df11dc03 | |||
| af70986edd | |||
|
|
d94b95fa36 | ||
|
|
2db0212b31 | ||
|
|
43e44bf7d2 | ||
| 023aa9d419 |
3
Diagrammes/Diagramme - Avalam.svg
Normal file
3
Diagrammes/Diagramme - Avalam.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 128 KiB |
3
Diagrammes/Diagramme - Bot.svg
Normal file
3
Diagrammes/Diagramme - Bot.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 22 KiB |
3
Diagrammes/Diagramme - GameAPI.svg
Normal file
3
Diagrammes/Diagramme - GameAPI.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 40 KiB |
@@ -3,6 +3,28 @@ title: Avalam - Diagramme de classes (complet)
|
|||||||
---
|
---
|
||||||
classDiagram
|
classDiagram
|
||||||
|
|
||||||
|
class ArenaGame{
|
||||||
|
+ArenaGame(IBoard board, AbstractGamePlayer bot1, AbstractGamePlayer bot2)
|
||||||
|
-createPlayerMap(AbstractGamePlayer bot1, AbstractGamePlayer bot2): EnumMap<Player, AbstractGamePlayer>
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArenaWindow{
|
||||||
|
-resultsTable: JTable
|
||||||
|
-tableModel: DefaultTableModel
|
||||||
|
-results: List<string>
|
||||||
|
|
||||||
|
+ArenaWindows()
|
||||||
|
-createConfigPanel(): JPanel
|
||||||
|
-createResultsTable()
|
||||||
|
-showConfigDialog()
|
||||||
|
-runArena(String bot1Type, String bot2Type, int depth, int nbParties)
|
||||||
|
-createBot(String botType, Player player, int depth): AbstractGamePlayer
|
||||||
|
-getWinnerName(Result result, String bot1Type, String bot2Type): String
|
||||||
|
}
|
||||||
|
|
||||||
|
ArenaWindow *-- AvalamBoard
|
||||||
|
ArenaWindow *-- ArenaGame
|
||||||
|
|
||||||
class AvalamBoard{
|
class AvalamBoard{
|
||||||
+SIZE: int
|
+SIZE: int
|
||||||
-MAX_HEIGHT: int
|
-MAX_HEIGHT: int
|
||||||
@@ -42,8 +64,6 @@ classDiagram
|
|||||||
|
|
||||||
+toString(): String
|
+toString(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AvalamWindow{
|
class AvalamWindow{
|
||||||
-board : AvalamBoard
|
-board : AvalamBoard
|
||||||
@@ -70,13 +90,18 @@ classDiagram
|
|||||||
AvalamWindow *-- BoardView
|
AvalamWindow *-- BoardView
|
||||||
AvalamWindow *-- ScoreView
|
AvalamWindow *-- ScoreView
|
||||||
AvalamWindow *-- TurnView
|
AvalamWindow *-- TurnView
|
||||||
|
AvalamWindow *-- EndGameDialog
|
||||||
AvalamWindow --> GameMode
|
AvalamWindow --> GameMode
|
||||||
|
|
||||||
|
class BackgroundLayer{
|
||||||
|
-img: Image
|
||||||
|
+BackgroundLayer(String resourcePath)
|
||||||
|
#paintComponent(Graphics g): void
|
||||||
|
}
|
||||||
|
|
||||||
class BoardLoader{
|
class BoardLoader{
|
||||||
+loadFromFile(String resourcePath): Tower[][]
|
+loadFromFile(String resourcePath): Tower[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BoardView{
|
class BoardView{
|
||||||
-board: AvalamBoard
|
-board: AvalamBoard
|
||||||
@@ -105,14 +130,6 @@ classDiagram
|
|||||||
BoardView *-- InteractionController
|
BoardView *-- InteractionController
|
||||||
BoardView --> AvalamBoard
|
BoardView --> AvalamBoard
|
||||||
|
|
||||||
class BackgroundLayer{
|
|
||||||
-img: Image
|
|
||||||
+BackgroundLayer(String resourcePath)
|
|
||||||
#paintComponent(Graphics g): void
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Color{
|
class Color{
|
||||||
-YELLOW(int r, int g, int b)
|
-YELLOW(int r, int g, int b)
|
||||||
-RED(int r, int g, int b)
|
-RED(int r, int g, int b)
|
||||||
@@ -122,11 +139,17 @@ classDiagram
|
|||||||
+toPlayer(): fr.iut_fbleau.GameAPI.Player
|
+toPlayer(): fr.iut_fbleau.GameAPI.Player
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EndGameDialog{
|
||||||
|
+EndGameDialog(JFrame parent, Result result, int scoreJaune, int scoreRouge, GameMode mode, int depth, Runnable onReplay, Runnable onMenu, Runnable onQuit)
|
||||||
|
-modeToString(GameMode mode, int depth): String
|
||||||
|
}
|
||||||
|
|
||||||
class GameMode{
|
class GameMode{
|
||||||
PVP
|
PVP
|
||||||
PVBOT
|
PVBOT
|
||||||
PVALPHA
|
PVALPHA
|
||||||
PVGOD
|
PVGOD
|
||||||
|
ARENA
|
||||||
}
|
}
|
||||||
|
|
||||||
class HighlightLayer{
|
class HighlightLayer{
|
||||||
@@ -165,6 +188,7 @@ classDiagram
|
|||||||
|
|
||||||
Main ..> AvalamWindow
|
Main ..> AvalamWindow
|
||||||
Main ..> GameMode
|
Main ..> GameMode
|
||||||
|
Main ..> ArenaWindow
|
||||||
|
|
||||||
class PieceButton{
|
class PieceButton{
|
||||||
-color: java.awt.Color
|
-color: java.awt.Color
|
||||||
|
|||||||
3
Diagrammes/Diagramme_Avalam_(simplifié).svg
Normal file
3
Diagrammes/Diagramme_Avalam_(simplifié).svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 100 KiB |
@@ -16,8 +16,18 @@ classDiagram
|
|||||||
-listMoves(IBoard board): List<AbstractPly>
|
-listMoves(IBoard board): List<AbstractPly>
|
||||||
}
|
}
|
||||||
|
|
||||||
class DivineBot{
|
class DivineBot {
|
||||||
|
-me : Player
|
||||||
|
-maxDepth : int
|
||||||
|
-rng : Random
|
||||||
|
|
||||||
|
+DivineBot(Player p, int maxDepth)
|
||||||
|
+giveYourMove(IBoard board) : AbstractPly
|
||||||
|
-alphaBeta(IBoard board, int depth, int alpha, int beta) : int
|
||||||
|
-evaluate(IBoard board) : int
|
||||||
|
-isIsolated(AvalamBoard b, int r, int c) : boolean
|
||||||
|
-isVulnerable(AvalamBoard b, int r, int c, Color enemyColor) : boolean
|
||||||
|
-listMoves(IBoard board) : List~AbstractPly~
|
||||||
}
|
}
|
||||||
|
|
||||||
class IdiotBot{
|
class IdiotBot{
|
||||||
|
|||||||
@@ -123,3 +123,8 @@ Tans qu'un joueur peut effectuer un mouvement il a l'obligation de jouer, la par
|
|||||||
On compte alors combien de pions de chaque couleur occupent le sommet des tours restantes, le vainqueur étant évidemment celui qui en totalise le plus.
|
On compte alors combien de pions de chaque couleur occupent le sommet des tours restantes, le vainqueur étant évidemment celui qui en totalise le plus.
|
||||||
Attention! Qu'une tour comporte 1,2,... ou 5 pions, elle vaut toujours UN point.
|
Attention! Qu'une tour comporte 1,2,... ou 5 pions, elle vaut toujours UN point.
|
||||||
|
|
||||||
|
## Arène de bots
|
||||||
|
Dans ce mode de jeu, deux bots s'affrontent pour certains nombre de partie choisit, afin de voir quel bot gagne le plus.
|
||||||
|
|
||||||
|
### Lancer parties
|
||||||
|
Lancer parties permet de faire fonctionner l'arène. On choisit le premier bot pour le joueur1, et le deuxième pour le joueur deux. On choisit la profondeurs de recherches pour les bots intelligent, puis enfin le nombre de partie que doivent effectués les bots. Enfin il suffit pour voir l'entièreté des résultats, si y a beaucoup de parties, sinon, on peut voir les parties se déroulé pendant la session.
|
||||||
BIN
Rapport_Avalam.pdf
Normal file
BIN
Rapport_Avalam.pdf
Normal file
Binary file not shown.
@@ -192,64 +192,135 @@ public class ArenaWindow extends JFrame {
|
|||||||
* @param nbParties nombre de parties à jouer
|
* @param nbParties nombre de parties à jouer
|
||||||
*/
|
*/
|
||||||
private void runArena(String bot1Type, String bot2Type, int depth, int nbParties) {
|
private void runArena(String bot1Type, String bot2Type, int depth, int nbParties) {
|
||||||
// Créer les bots
|
|
||||||
AbstractGamePlayer bot1 = createBot(bot1Type, Player.PLAYER1, depth);
|
|
||||||
AbstractGamePlayer bot2 = createBot(bot2Type, Player.PLAYER2, depth);
|
|
||||||
|
|
||||||
if (bot1 == null || bot2 == null) {
|
|
||||||
JOptionPane.showMessageDialog(this, "Erreur lors de la création des bots.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vider le tableau
|
// Vider le tableau
|
||||||
tableModel.setRowCount(0);
|
tableModel.setRowCount(0);
|
||||||
results.clear();
|
results.clear();
|
||||||
|
|
||||||
// Charger le plateau initial
|
// Créer un dialogue de progression
|
||||||
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
|
JDialog progressDialog = new JDialog(this, "Parties en cours...", true);
|
||||||
|
JLabel progressLabel = new JLabel("Préparation des parties...", JLabel.CENTER);
|
||||||
|
progressLabel.setBorder(BorderFactory.createEmptyBorder(20, 40, 20, 40));
|
||||||
|
progressDialog.add(progressLabel);
|
||||||
|
progressDialog.setSize(300, 100);
|
||||||
|
progressDialog.setLocationRelativeTo(this);
|
||||||
|
progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||||
|
|
||||||
// Lancer les parties
|
// Exécuter les parties dans un thread séparé pour ne pas bloquer l'interface
|
||||||
for (int i = 1; i <= nbParties; i++) {
|
Thread arenaThread = new Thread(() -> {
|
||||||
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1);
|
SwingUtilities.invokeLater(() -> progressDialog.setVisible(true));
|
||||||
ArenaGame game = new ArenaGame(board, bot1, bot2);
|
|
||||||
|
|
||||||
try {
|
// Statistiques pour déboguer
|
||||||
Result result = game.run();
|
int bot1Wins = 0;
|
||||||
String winner = getWinnerName(result, bot1Type, bot2Type);
|
int bot2Wins = 0;
|
||||||
|
int draws = 0;
|
||||||
// Ajouter au tableau
|
int errors = 0;
|
||||||
tableModel.addRow(new Object[]{
|
|
||||||
"Partie " + i,
|
for (int i = 1; i <= nbParties; i++) {
|
||||||
bot1Type,
|
final int partieNum = i;
|
||||||
bot2Type,
|
SwingUtilities.invokeLater(() -> {
|
||||||
winner
|
progressLabel.setText("Partie " + partieNum + " / " + nbParties + " en cours...");
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
tableModel.addRow(new Object[]{
|
|
||||||
"Partie " + i,
|
|
||||||
bot1Type,
|
|
||||||
bot2Type,
|
|
||||||
"Erreur: " + e.getMessage()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Recréer les bots à chaque partie pour garantir l'indépendance complète
|
||||||
|
// (notamment pour réinitialiser les générateurs aléatoires)
|
||||||
|
AbstractGamePlayer bot1 = createBot(bot1Type, Player.PLAYER1, depth);
|
||||||
|
AbstractGamePlayer bot2 = createBot(bot2Type, Player.PLAYER2, depth);
|
||||||
|
|
||||||
|
if (bot1 == null || bot2 == null) {
|
||||||
|
errors++;
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
"Partie " + partieNum,
|
||||||
|
bot1Type,
|
||||||
|
bot2Type,
|
||||||
|
"Erreur: Impossible de créer les bots"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharger le plateau à chaque partie pour garantir l'indépendance complète
|
||||||
|
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
|
||||||
|
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1);
|
||||||
|
ArenaGame game = new ArenaGame(board, bot1, bot2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Result result = game.run();
|
||||||
|
String winner = getWinnerName(result, bot1Type, bot2Type);
|
||||||
|
|
||||||
|
// Mettre à jour les statistiques
|
||||||
|
if (result == Result.WIN) {
|
||||||
|
bot1Wins++;
|
||||||
|
} else if (result == Result.LOSS) {
|
||||||
|
bot2Wins++;
|
||||||
|
} else if (result == Result.DRAW) {
|
||||||
|
draws++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter au tableau dans le thread EDT
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
"Partie " + partieNum,
|
||||||
|
bot1Type,
|
||||||
|
bot2Type,
|
||||||
|
winner
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
errors++;
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
"Partie " + partieNum,
|
||||||
|
bot1Type,
|
||||||
|
bot2Type,
|
||||||
|
"Erreur: " + e.getMessage()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Afficher les statistiques finales
|
||||||
|
final int finalBot1Wins = bot1Wins;
|
||||||
|
final int finalBot2Wins = bot2Wins;
|
||||||
|
final int finalDraws = draws;
|
||||||
|
final int finalErrors = errors;
|
||||||
|
|
||||||
// Afficher un message de fin avec possibilité de quitter directement
|
// Fermer le dialogue et afficher le message de fin avec statistiques
|
||||||
Object[] options = {"OK", "Quitter le jeu"};
|
SwingUtilities.invokeLater(() -> {
|
||||||
int choice = JOptionPane.showOptionDialog(
|
progressDialog.dispose();
|
||||||
this,
|
|
||||||
"Toutes les parties sont terminées !",
|
String statsMessage = String.format(
|
||||||
"Arène terminée",
|
"Toutes les parties sont terminées !\n\n" +
|
||||||
JOptionPane.DEFAULT_OPTION,
|
"Statistiques :\n" +
|
||||||
JOptionPane.INFORMATION_MESSAGE,
|
"- %s (Bot 1) : %d victoires\n" +
|
||||||
null,
|
"- %s (Bot 2) : %d victoires\n" +
|
||||||
options,
|
"- Matchs nuls : %d\n" +
|
||||||
options[0]
|
"- Erreurs : %d",
|
||||||
);
|
bot1Type, finalBot1Wins,
|
||||||
|
bot2Type, finalBot2Wins,
|
||||||
|
finalDraws,
|
||||||
|
finalErrors
|
||||||
|
);
|
||||||
|
|
||||||
|
Object[] options = {"OK", "Quitter le jeu"};
|
||||||
|
int choice = JOptionPane.showOptionDialog(
|
||||||
|
this,
|
||||||
|
statsMessage,
|
||||||
|
"Arène terminée",
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.INFORMATION_MESSAGE,
|
||||||
|
null,
|
||||||
|
options,
|
||||||
|
options[0]
|
||||||
|
);
|
||||||
|
|
||||||
if (choice == 1) {
|
if (choice == 1) {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
arenaThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -51,9 +51,16 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
super(startingPlayer, new ArrayDeque<>());
|
super(startingPlayer, new ArrayDeque<>());
|
||||||
this.grid = new Tower[SIZE][SIZE];
|
this.grid = new Tower[SIZE][SIZE];
|
||||||
|
|
||||||
|
// Copie profonde : créer de nouvelles tours pour éviter que toutes les parties partagent les mêmes objets
|
||||||
for (int r = 0; r < SIZE; r++)
|
for (int r = 0; r < SIZE; r++)
|
||||||
for (int c = 0; c < SIZE; c++)
|
for (int c = 0; c < SIZE; c++) {
|
||||||
this.grid[r][c] = initialGrid[r][c];
|
Tower t = initialGrid[r][c];
|
||||||
|
if (t == null) {
|
||||||
|
this.grid[r][c] = null;
|
||||||
|
} else {
|
||||||
|
this.grid[r][c] = new Tower(t.getHeight(), t.getColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ public class AvalamWindow extends JFrame {
|
|||||||
} else if (mode == GameMode.PVALPHA) {
|
} else if (mode == GameMode.PVALPHA) {
|
||||||
botMove = alphaBot.giveYourMove(board.safeCopy());
|
botMove = alphaBot.giveYourMove(board.safeCopy());
|
||||||
} else {
|
} else {
|
||||||
// A FAIRE PLUS TARD (PVGOD)
|
|
||||||
botMove = divineBot.giveYourMove(board.safeCopy());
|
botMove = divineBot.giveYourMove(board.safeCopy());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,184 +2,179 @@ package fr.iut_fbleau.Bot;
|
|||||||
|
|
||||||
import fr.iut_fbleau.Avalam.*;
|
import fr.iut_fbleau.Avalam.*;
|
||||||
import fr.iut_fbleau.GameAPI.*;
|
import fr.iut_fbleau.GameAPI.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bot "Divin" (alpha-beta + évaluateur pondéré).
|
* Bot expert pour le jeu Avalam utilisant l'algorithme Alpha-Beta.
|
||||||
* * Idée :
|
* * Ce bot évalue le plateau en fonction du contrôle des tours,
|
||||||
* - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
|
* de leur isolation (points sécurisés) et de leur vulnérabilité.
|
||||||
* - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
|
|
||||||
*/
|
*/
|
||||||
public class DivineBot extends AbstractGamePlayer {
|
public class DivineBot extends AbstractGamePlayer {
|
||||||
|
|
||||||
// Attributs
|
/** Joueur contrôlé par le bot. */
|
||||||
|
|
||||||
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
|
|
||||||
private final Player me;
|
private final Player me;
|
||||||
|
|
||||||
/** Profondeur maximale de recherche avant évaluation. */
|
/** Profondeur de recherche dans l'arbre des coups. */
|
||||||
private final int maxDepth;
|
private final int maxDepth;
|
||||||
|
|
||||||
/** Générateur aléatoire pour choisir parmi les meilleurs coups équivalents. */
|
/** Générateur aléatoire pour choisir entre des coups d'égale valeur. */
|
||||||
private final Random rng = new Random();
|
private final Random rng = new Random();
|
||||||
|
|
||||||
// Constructeur
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construit le bot Divine.
|
* Constructeur du DivineBot.
|
||||||
*
|
* @param p le joueur (P1 ou P2).
|
||||||
* @param p joueur contrôlé par ce bot
|
* @param maxDepth la profondeur d'anticipation.
|
||||||
* @param maxDepth profondeur de l'arbre de recherche
|
|
||||||
*/
|
*/
|
||||||
public DivineBot(Player p, int maxDepth) {
|
public DivineBot(Player p, int maxDepth) {
|
||||||
super(p);
|
super(p);
|
||||||
this.me = p;
|
this.me = p;
|
||||||
this.maxDepth = Math.max(1, maxDepth);
|
this.maxDepth = maxDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthodes
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode principale de décision du bot.
|
* Calcule et retourne le meilleur coup possible.
|
||||||
* Explore le premier niveau de l'arbre et lance les appels Alpha-Beta.
|
* @param board l'état actuel du jeu.
|
||||||
* * @param board état actuel du jeu
|
* @return le coup sélectionné.
|
||||||
* @return le meilleur coup calculé (AbstractPly)
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AbstractPly giveYourMove(IBoard board) {
|
public AbstractPly giveYourMove(IBoard board) {
|
||||||
|
|
||||||
if (board == null || board.isGameOver()) return null;
|
if (board == null || board.isGameOver()) return null;
|
||||||
|
|
||||||
List<AbstractPly> moves = listMoves(board);
|
List<AbstractPly> moves = listMoves(board);
|
||||||
if (moves.isEmpty()) return null;
|
int bestValue = Integer.MIN_VALUE;
|
||||||
|
|
||||||
boolean isMax = board.getCurrentPlayer() == me;
|
|
||||||
|
|
||||||
int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
|
|
||||||
List<AbstractPly> bestMoves = new ArrayList<>();
|
List<AbstractPly> bestMoves = new ArrayList<>();
|
||||||
|
|
||||||
int alpha = Integer.MIN_VALUE;
|
|
||||||
int beta = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
for (AbstractPly m : moves) {
|
for (AbstractPly m : moves) {
|
||||||
IBoard next = board.safeCopy();
|
IBoard next = board.safeCopy();
|
||||||
next.doPly(m);
|
next.doPly(m);
|
||||||
|
int value = alphaBeta(next, maxDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||||
|
|
||||||
// Appel récursif pour évaluer la suite du coup
|
if (value > bestValue) {
|
||||||
int value = alphaBeta(next, maxDepth - 1, alpha, beta);
|
bestValue = value;
|
||||||
|
bestMoves.clear();
|
||||||
if (isMax) {
|
bestMoves.add(m);
|
||||||
if (value > bestValue) {
|
} else if (value == bestValue) {
|
||||||
bestValue = value;
|
bestMoves.add(m);
|
||||||
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()));
|
return bestMoves.get(rng.nextInt(bestMoves.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Algorithme récursif de recherche avec élagage Alpha-Beta.
|
* Algorithme Alpha-Beta pour optimiser la recherche du meilleur score.
|
||||||
*/
|
*/
|
||||||
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
|
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
|
||||||
|
if (board.isGameOver()) {
|
||||||
// Cas de base : fin de partie ou limite de profondeur atteinte
|
Result r = board.getResult();
|
||||||
if (board.isGameOver()) return terminalValue(board);
|
if (r == Result.DRAW) return 0;
|
||||||
|
return (r == Result.WIN == (me == Player.PLAYER1)) ? 1000000 : -1000000;
|
||||||
|
}
|
||||||
|
|
||||||
if (depth == 0) return evaluate(board);
|
if (depth == 0) return evaluate(board);
|
||||||
|
|
||||||
boolean isMax = board.getCurrentPlayer() == me;
|
boolean isMax = (board.getCurrentPlayer() == me);
|
||||||
|
int best = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
|
||||||
|
|
||||||
for (AbstractPly m : listMoves(board)) {
|
for (AbstractPly m : listMoves(board)) {
|
||||||
IBoard next = board.safeCopy();
|
IBoard next = board.safeCopy();
|
||||||
next.doPly(m);
|
next.doPly(m);
|
||||||
|
|
||||||
int val = alphaBeta(next, depth - 1, alpha, beta);
|
int val = alphaBeta(next, depth - 1, alpha, beta);
|
||||||
|
|
||||||
if (isMax) {
|
if (isMax) {
|
||||||
alpha = Math.max(alpha, val);
|
best = Math.max(best, val);
|
||||||
if (alpha >= beta) break; // Coupure Beta
|
alpha = Math.max(alpha, best);
|
||||||
} else {
|
} else {
|
||||||
beta = Math.min(beta, val);
|
best = Math.min(best, val);
|
||||||
if (alpha >= beta) break; // Coupure Alpha
|
beta = Math.min(beta, best);
|
||||||
}
|
}
|
||||||
|
if (alpha >= beta) break;
|
||||||
}
|
}
|
||||||
|
return best;
|
||||||
return isMax ? alpha : beta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calcule la valeur de l'état final (Victoire / Défaite).
|
* Analyse la situation actuelle et attribue un score au plateau.
|
||||||
*/
|
* @param board le plateau à analyser.
|
||||||
private int terminalValue(IBoard board) {
|
* @return le score (positif si avantageux pour le bot).
|
||||||
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) {
|
private int evaluate(IBoard board) {
|
||||||
|
|
||||||
if (!(board instanceof AvalamBoard)) return 0;
|
if (!(board instanceof AvalamBoard)) return 0;
|
||||||
AvalamBoard b = (AvalamBoard) board;
|
AvalamBoard b = (AvalamBoard) board;
|
||||||
|
|
||||||
Color myColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
|
Color myColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
|
||||||
Color oppColor = (myColor == Color.YELLOW) ? Color.RED : Color.YELLOW;
|
Color oppColor = (myColor == Color.YELLOW) ? Color.RED : Color.YELLOW;
|
||||||
|
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
||||||
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
||||||
|
|
||||||
Tower t = b.getTowerAt(r, c);
|
Tower t = b.getTowerAt(r, c);
|
||||||
if (t == null) continue;
|
if (t == null || t.getHeight() == 0) continue;
|
||||||
|
|
||||||
int h = t.getHeight();
|
int h = t.getHeight();
|
||||||
|
int weight = 0;
|
||||||
// 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;
|
// Points sécurisés (isolation ou hauteur max)
|
||||||
else score -= value;
|
if (h == 5 || isIsolated(b, r, c)) {
|
||||||
|
weight = 1000;
|
||||||
|
} else {
|
||||||
|
// Gestion de la vulnérabilité face à l'ennemi
|
||||||
|
if (isVulnerable(b, r, c, oppColor)) {
|
||||||
|
weight = -200;
|
||||||
|
} else {
|
||||||
|
switch (h) {
|
||||||
|
case 4: weight = 400; break;
|
||||||
|
case 3: weight = 150; break;
|
||||||
|
case 2: weight = 60; break;
|
||||||
|
default: weight = 10; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.getColor() == myColor) score += weight;
|
||||||
|
else score -= weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Génère la liste de tous les coups possibles sur le plateau donné.
|
* Vérifie si une tour n'a plus de voisins (point bloqué).
|
||||||
|
*/
|
||||||
|
private boolean isIsolated(AvalamBoard b, int r, int c) {
|
||||||
|
for (int dr = -1; dr <= 1; dr++) {
|
||||||
|
for (int dc = -1; dc <= 1; dc++) {
|
||||||
|
if (dr == 0 && dc == 0) continue;
|
||||||
|
Tower n = b.getTowerAt(r + dr, c + dc);
|
||||||
|
if (n != null && n.getHeight() > 0) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si l'adversaire peut capturer la tour au prochain coup.
|
||||||
|
*/
|
||||||
|
private boolean isVulnerable(AvalamBoard b, int r, int c, Color enemyColor) {
|
||||||
|
int myHeight = b.getTowerAt(r, c).getHeight();
|
||||||
|
for (int dr = -1; dr <= 1; dr++) {
|
||||||
|
for (int dc = -1; dc <= 1; dc++) {
|
||||||
|
if (dr == 0 && dc == 0) continue;
|
||||||
|
Tower n = b.getTowerAt(r + dr, c + dc);
|
||||||
|
if (n != null && n.getColor() == enemyColor && (n.getHeight() + myHeight <= 5)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste tous les coups possibles sur le plateau actuel.
|
||||||
*/
|
*/
|
||||||
private List<AbstractPly> listMoves(IBoard board) {
|
private List<AbstractPly> listMoves(IBoard board) {
|
||||||
List<AbstractPly> moves = new ArrayList<>();
|
List<AbstractPly> moves = new ArrayList<>();
|
||||||
board.iterator().forEachRemaining(moves::add);
|
Iterator<AbstractPly> it = board.iterator();
|
||||||
|
while (it.hasNext()) moves.add(it.next());
|
||||||
return moves;
|
return moves;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user