Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14e5df4332 |
15
Makefile
15
Makefile
@@ -1,3 +1,6 @@
|
||||
# === Environnements ===
|
||||
TEST_ENV = "bin:/usr/share/java/junit.jar:/usr/share/java/hamcrest-core.jar"
|
||||
|
||||
# === Répertoires ===
|
||||
SRC_DIR = fr
|
||||
BIN_DIR = bin
|
||||
@@ -12,12 +15,16 @@ SOURCES := $(shell find $(SRC_DIR) -name "*.java")
|
||||
# === Classe principale ===
|
||||
MAIN_CLASS = fr.iut_fbleau.Avalam.Main
|
||||
|
||||
# === Classe de test ===
|
||||
TEST_CLASS = fr.iut_fbleau.Tests.AvalamBoardTest
|
||||
|
||||
# === Commandes Java ===
|
||||
JC = javac
|
||||
JCFLAGS = -d $(BIN_DIR)
|
||||
JCFLAGS = -d $(BIN_DIR) -cp $(TEST_ENV)
|
||||
|
||||
JAVA = java
|
||||
JAVAFLAGS = -cp $(BIN_DIR)
|
||||
JAVAFLAGS_TESTS = -cp $(TEST_ENV)
|
||||
|
||||
# === Règle par défaut ===
|
||||
all: build
|
||||
@@ -43,6 +50,12 @@ run:
|
||||
@echo "===> Lancement du jeu Avalam..."
|
||||
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
|
||||
|
||||
# === Tests ===
|
||||
test:
|
||||
@echo "===> Lancement des tests..."
|
||||
@$(JAVA) $(JAVAFLAGS_TESTS) org.junit.runner.JUnitCore $(TEST_CLASS)
|
||||
@echo "... Fin des tests."
|
||||
|
||||
# === Nettoyage ===
|
||||
clean:
|
||||
@echo "===> Suppression des fichiers compilés..."
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package fr.iut_fbleau.Avalam;
|
||||
|
||||
import fr.iut_fbleau.GameAPI.AbstractGame;
|
||||
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
|
||||
import fr.iut_fbleau.GameAPI.IBoard;
|
||||
import fr.iut_fbleau.GameAPI.Player;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
/**
|
||||
* Classe pour jouer une partie entre deux bots sans interface graphique.
|
||||
* Utilisée dans le mode Arène.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ArenaGame extends AbstractGame {
|
||||
|
||||
/**
|
||||
* Construit une partie Arène entre deux bots.
|
||||
*
|
||||
* @param board plateau initial
|
||||
* @param bot1 bot pour PLAYER1
|
||||
* @param bot2 bot pour PLAYER2
|
||||
*/
|
||||
public ArenaGame(IBoard board, AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
|
||||
super(board, createPlayerMap(bot1, bot2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée la map des joueurs pour AbstractGame.
|
||||
*
|
||||
* @param bot1 bot pour PLAYER1
|
||||
* @param bot2 bot pour PLAYER2
|
||||
* @return une EnumMap associant chaque Player à son bot
|
||||
*/
|
||||
private static EnumMap<Player, AbstractGamePlayer> createPlayerMap(
|
||||
AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
|
||||
EnumMap<Player, AbstractGamePlayer> map = new EnumMap<>(Player.class);
|
||||
map.put(Player.PLAYER1, bot1);
|
||||
map.put(Player.PLAYER2, bot2);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
package fr.iut_fbleau.Avalam;
|
||||
|
||||
import fr.iut_fbleau.Bot.AlphaBetaBot;
|
||||
import fr.iut_fbleau.Bot.IdiotBot;
|
||||
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
|
||||
import fr.iut_fbleau.GameAPI.Player;
|
||||
import fr.iut_fbleau.GameAPI.Result;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fenêtre pour le mode Arène.
|
||||
* Permet de sélectionner deux bots, le nombre de parties, et affiche les résultats.
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ArenaWindow extends JFrame {
|
||||
|
||||
/** Tableau affichant les résultats des parties. */
|
||||
private JTable resultsTable;
|
||||
|
||||
/** Modèle de données pour le tableau des résultats. */
|
||||
private DefaultTableModel tableModel;
|
||||
|
||||
/** Liste des résultats des parties (non utilisée actuellement). */
|
||||
private List<String> results;
|
||||
|
||||
/**
|
||||
* Construit la fenêtre Arène.
|
||||
*/
|
||||
public ArenaWindow() {
|
||||
super("Arène - Bot vs Bot");
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
results = new ArrayList<>();
|
||||
|
||||
// Panneau de configuration
|
||||
JPanel configPanel = createConfigPanel();
|
||||
add(configPanel, BorderLayout.NORTH);
|
||||
|
||||
// Tableau des résultats
|
||||
createResultsTable();
|
||||
JScrollPane scrollPane = new JScrollPane(resultsTable);
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Panneau des boutons
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
JButton startButton = new JButton("Lancer les parties");
|
||||
startButton.addActionListener(e -> showConfigDialog());
|
||||
buttonPanel.add(startButton);
|
||||
|
||||
JButton backButton = new JButton("Retour au menu");
|
||||
backButton.addActionListener(e -> {
|
||||
dispose(); // Ferme la fenêtre Arène
|
||||
Main.showModeSelection(); // Affiche le menu principal
|
||||
});
|
||||
buttonPanel.add(backButton);
|
||||
|
||||
add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
pack();
|
||||
setSize(600, 400);
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée le panneau de configuration (pour l'instant vide, sera rempli par le dialogue).
|
||||
*
|
||||
* @return un JPanel contenant les informations de configuration
|
||||
*/
|
||||
private JPanel createConfigPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setBorder(BorderFactory.createTitledBorder("Configuration"));
|
||||
panel.add(new JLabel("Utilisez le bouton 'Lancer les parties' pour configurer et démarrer."));
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée le tableau des résultats avec les colonnes : Partie, Bot 1, Bot 2, Gagnant.
|
||||
*/
|
||||
private void createResultsTable() {
|
||||
String[] columnNames = {"Partie", "Bot 1", "Bot 2", "Gagnant"};
|
||||
tableModel = new DefaultTableModel(columnNames, 0) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
resultsTable = new JTable(tableModel);
|
||||
resultsTable.setFillsViewportHeight(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le dialogue de configuration et lance les parties.
|
||||
*/
|
||||
private void showConfigDialog() {
|
||||
// Choix du bot 1
|
||||
String[] botTypes = {"Bot Idiot", "Bot Alpha-Beta", "Bot Divin"};
|
||||
String bot1Choice = (String) JOptionPane.showInputDialog(
|
||||
this,
|
||||
"Choisissez le Bot 1 (Joueur 1) :",
|
||||
"Configuration Arène - Bot 1",
|
||||
JOptionPane.QUESTION_MESSAGE,
|
||||
null,
|
||||
botTypes,
|
||||
botTypes[0]
|
||||
);
|
||||
|
||||
if (bot1Choice == null) return;
|
||||
|
||||
// Choix du bot 2
|
||||
String bot2Choice = (String) JOptionPane.showInputDialog(
|
||||
this,
|
||||
"Choisissez le Bot 2 (Joueur 2) :",
|
||||
"Configuration Arène - Bot 2",
|
||||
JOptionPane.QUESTION_MESSAGE,
|
||||
null,
|
||||
botTypes,
|
||||
botTypes[0]
|
||||
);
|
||||
|
||||
if (bot2Choice == null) return;
|
||||
|
||||
// Profondeur pour Alpha-Beta et Divin
|
||||
int depth = 4;
|
||||
if (bot1Choice.contains("Alpha") || bot1Choice.contains("Divin") ||
|
||||
bot2Choice.contains("Alpha") || bot2Choice.contains("Divin")) {
|
||||
String depthStr = JOptionPane.showInputDialog(
|
||||
this,
|
||||
"Profondeur de recherche pour les bots Alpha-Beta/Divin ?\n(Conseil: 4)",
|
||||
"Profondeur",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
if (depthStr != null) {
|
||||
try {
|
||||
depth = Integer.parseInt(depthStr.trim());
|
||||
if (depth < 1) depth = 1;
|
||||
} catch (Exception e) {
|
||||
depth = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nombre de parties
|
||||
String nbPartiesStr = JOptionPane.showInputDialog(
|
||||
this,
|
||||
"Combien de parties voulez-vous jouer ?",
|
||||
"Nombre de parties",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
|
||||
if (nbPartiesStr == null) return;
|
||||
|
||||
int nbParties;
|
||||
try {
|
||||
nbParties = Integer.parseInt(nbPartiesStr.trim());
|
||||
if (nbParties < 1) {
|
||||
JOptionPane.showMessageDialog(this, "Le nombre de parties doit être au moins 1.");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(this, "Nombre invalide.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Lancer les parties
|
||||
runArena(bot1Choice, bot2Choice, depth, nbParties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance les parties entre les deux bots.
|
||||
*
|
||||
* @param bot1Type type du bot 1 (Joueur 1)
|
||||
* @param bot2Type type du bot 2 (Joueur 2)
|
||||
* @param depth profondeur de recherche pour les bots Alpha-Beta/Divin
|
||||
* @param nbParties nombre de parties à jouer
|
||||
*/
|
||||
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
|
||||
tableModel.setRowCount(0);
|
||||
results.clear();
|
||||
|
||||
// Charger le plateau initial
|
||||
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
|
||||
|
||||
// Lancer les parties
|
||||
for (int i = 1; i <= nbParties; i++) {
|
||||
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);
|
||||
|
||||
// Ajouter au tableau
|
||||
tableModel.addRow(new Object[]{
|
||||
"Partie " + i,
|
||||
bot1Type,
|
||||
bot2Type,
|
||||
winner
|
||||
});
|
||||
} catch (Exception e) {
|
||||
tableModel.addRow(new Object[]{
|
||||
"Partie " + i,
|
||||
bot1Type,
|
||||
bot2Type,
|
||||
"Erreur: " + e.getMessage()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher un message de fin
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
"Toutes les parties sont terminées !",
|
||||
"Arène terminée",
|
||||
JOptionPane.INFORMATION_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un bot selon son type.
|
||||
*
|
||||
* @param botType type de bot ("Bot Idiot", "Bot Alpha-Beta", "Bot Divin")
|
||||
* @param player joueur contrôlé par ce bot (PLAYER1 ou PLAYER2)
|
||||
* @param depth profondeur de recherche pour Alpha-Beta/Divin
|
||||
* @return une instance de AbstractGamePlayer correspondant au type, ou null si le type est invalide
|
||||
*/
|
||||
private AbstractGamePlayer createBot(String botType, Player player, int depth) {
|
||||
if (botType.equals("Bot Idiot")) {
|
||||
return new IdiotBot(player);
|
||||
} else if (botType.equals("Bot Alpha-Beta")) {
|
||||
return new AlphaBetaBot(player, depth);
|
||||
} else if (botType.equals("Bot Divin")) {
|
||||
// Pour l'instant, le Bot Divin n'est pas implémenté, on utilise IdiotBot
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
"Le Bot Divin n'est pas encore implémenté. Utilisation du Bot Idiot à la place.",
|
||||
"Avertissement",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
return new IdiotBot(player);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine le nom du gagnant selon le résultat.
|
||||
*
|
||||
* @param result résultat de la partie (WIN, LOSS, ou DRAW du point de vue de PLAYER1)
|
||||
* @param bot1Type nom du bot 1
|
||||
* @param bot2Type nom du bot 2
|
||||
* @return une chaîne indiquant le gagnant ou "Match nul"
|
||||
*/
|
||||
private String getWinnerName(Result result, String bot1Type, String bot2Type) {
|
||||
if (result == Result.WIN) {
|
||||
return bot1Type + " (Joueur 1)";
|
||||
} else if (result == Result.LOSS) {
|
||||
return bot2Type + " (Joueur 2)";
|
||||
} else {
|
||||
return "Match nul";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +59,6 @@ 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;
|
||||
@@ -70,6 +68,7 @@ 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;
|
||||
@@ -111,7 +110,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());
|
||||
@@ -221,10 +220,11 @@ 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,7 +239,8 @@ public class AvalamWindow extends JFrame {
|
||||
botMove = alphaBot.giveYourMove(board.safeCopy());
|
||||
} else {
|
||||
// A FAIRE PLUS TARD (PVGOD)
|
||||
botMove = divineBot.giveYourMove(board.safeCopy());
|
||||
// botMove = divineBot.giveYourMove(board.safeCopy());
|
||||
botMove = null;
|
||||
}
|
||||
|
||||
if (botMove == null) {
|
||||
|
||||
@@ -7,6 +7,5 @@ public enum GameMode {
|
||||
PVP, // joueur vs joueur
|
||||
PVBOT, // joueur vs bot idiot
|
||||
PVALPHA, // joueur vs bot alpha
|
||||
PVGOD, // joueur vs bot stratégique
|
||||
ARENA // bot vs bot (mode arène)
|
||||
PVGOD // joueur vs bot stratégique
|
||||
}
|
||||
|
||||
@@ -8,22 +8,14 @@ import javax.swing.*;
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
showModeSelection();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le menu de sélection du mode de jeu.
|
||||
* Peut être appelé depuis d'autres fenêtres pour revenir au menu.
|
||||
*/
|
||||
public static void showModeSelection() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
String[] options = {
|
||||
"joueur vs joueur",
|
||||
"joueur vs botidiot",
|
||||
"joueur vs bot alpha",
|
||||
"joueur vs bot divin (NON IMPLEMENTE)",
|
||||
"Arène"
|
||||
"joueur vs bot divin (NON IMPLEMENTE)"
|
||||
};
|
||||
|
||||
int choice = JOptionPane.showOptionDialog(
|
||||
@@ -39,12 +31,6 @@ public class Main {
|
||||
|
||||
if (choice == -1) System.exit(0);
|
||||
|
||||
// Mode Arène
|
||||
if (choice == 4) {
|
||||
new ArenaWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
GameMode mode;
|
||||
if (choice == 1) mode = GameMode.PVBOT;
|
||||
else if (choice == 2) mode = GameMode.PVALPHA;
|
||||
@@ -78,5 +64,6 @@ public class Main {
|
||||
}
|
||||
|
||||
new AvalamWindow(mode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,185 +1,22 @@
|
||||
package fr.iut_fbleau.Bot;
|
||||
|
||||
import fr.iut_fbleau.Avalam.*;
|
||||
import fr.iut_fbleau.GameAPI.*;
|
||||
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 java.util.*;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Bot "Divin" (fort) pour Avalam.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
*
|
||||
* Objectif : trop fort. */
|
||||
public class DivineBot{
|
||||
|
||||
// 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) {
|
||||
|
||||
if (board == null || board.isGameOver()) return null;
|
||||
|
||||
List<AbstractPly> moves = listMoves(board);
|
||||
if (moves.isEmpty()) return null;
|
||||
|
||||
boolean isMax = board.getCurrentPlayer() == me;
|
||||
|
||||
int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
|
||||
List<AbstractPly> bestMoves = new ArrayList<>();
|
||||
|
||||
int alpha = Integer.MIN_VALUE;
|
||||
int beta = Integer.MAX_VALUE;
|
||||
|
||||
for (AbstractPly m : moves) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
*/
|
||||
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 (depth == 0) return evaluate(board);
|
||||
|
||||
boolean isMax = board.getCurrentPlayer() == me;
|
||||
|
||||
for (AbstractPly m : listMoves(board)) {
|
||||
IBoard next = board.safeCopy();
|
||||
next.doPly(m);
|
||||
|
||||
int val = alphaBeta(next, depth - 1, alpha, beta);
|
||||
|
||||
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 isMax ? alpha : beta;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 = (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;
|
||||
|
||||
int h = t.getHeight();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère la liste de tous les coups possibles sur le plateau donné.
|
||||
*/
|
||||
private List<AbstractPly> listMoves(IBoard board) {
|
||||
List<AbstractPly> moves = new ArrayList<>();
|
||||
board.iterator().forEachRemaining(moves::add);
|
||||
return moves;
|
||||
}
|
||||
}
|
||||
122
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal file
122
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package fr.iut_fbleau.Tests;
|
||||
|
||||
import fr.iut_fbleau.GameAPI.AbstractPly;
|
||||
import fr.iut_fbleau.GameAPI.Player;
|
||||
|
||||
import fr.iut_fbleau.Avalam.AvalamBoard;
|
||||
import fr.iut_fbleau.Avalam.AvalamPly;
|
||||
import fr.iut_fbleau.Avalam.Tower;
|
||||
import fr.iut_fbleau.Avalam.Color;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
//import org.mockito.Mockito; //À retirer si Mockito absent
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* La classe <code>AvalamBoardTest</code> test si la méthode isLegal() fonctionne comme prévu.
|
||||
*/
|
||||
public class AvalamBoardTest {
|
||||
|
||||
private Tower[][] grid;
|
||||
private AvalamBoard board;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
|
||||
// Par défaut, current player sera PLAYER1 via constructeur sans joueur
|
||||
board = new AvalamBoard(grid);
|
||||
}
|
||||
|
||||
/*
|
||||
@Test //À retirer si Mockito absent
|
||||
public void nonAvalamPly_returnsFalse() {
|
||||
AbstractPly fake = Mockito.mock(AbstractPly.class); // instance non-AvalamPly
|
||||
assertFalse(board.isLegal(fake));
|
||||
}*/
|
||||
|
||||
@Test
|
||||
public void outOfBounds_returnsFalse() {
|
||||
grid[2][2] = new Tower(1, Color.YELLOW);
|
||||
grid[2][3] = new Tower(1, Color.RED);
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, -1, 2, 2, 3);
|
||||
assertFalse(board.isLegal(p));
|
||||
|
||||
AvalamPly p2 = new AvalamPly(Player.PLAYER1, 2, 2, 9, 3);
|
||||
assertFalse(board.isLegal(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameCell_returnsFalse() {
|
||||
grid[4][4] = new Tower(1, Color.YELLOW);
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 4, 4, 4, 4);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptySourceOrDest_returnsFalse() {
|
||||
// source null
|
||||
grid[3][3] = null;
|
||||
grid[3][4] = new Tower(1, Color.RED);
|
||||
AvalamPly p1 = new AvalamPly(Player.PLAYER1, 3, 3, 3, 4);
|
||||
assertFalse(board.isLegal(p1));
|
||||
|
||||
// dest null
|
||||
grid[5][5] = new Tower(1, Color.YELLOW);
|
||||
grid[5][6] = null;
|
||||
AvalamPly p2 = new AvalamPly(Player.PLAYER1, 5, 5, 5, 6);
|
||||
assertFalse(board.isLegal(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceNotOwned_returnsFalse() {
|
||||
// current player = PLAYER1 -> color must be YELLOW
|
||||
grid[2][2] = new Tower(1, Color.RED); // not owned
|
||||
grid[2][3] = new Tower(1, Color.YELLOW);
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 2, 2, 2, 3);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAdjacent_returnsFalse() {
|
||||
grid[0][0] = new Tower(1, Color.YELLOW);
|
||||
grid[0][2] = new Tower(1, Color.RED);
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 0, 0, 0, 2);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameColor_returnsFalse() {
|
||||
grid[6][6] = new Tower(1, Color.YELLOW);
|
||||
grid[6][7] = new Tower(1, Color.YELLOW); // same color as source
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 6, 6, 6, 7);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tooTallAfterMerge_returnsFalse() {
|
||||
grid[1][1] = new Tower(3, Color.YELLOW);
|
||||
grid[1][2] = new Tower(3, Color.RED); // 3+3 = 6 > MAX_HEIGHT (5)
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 1, 1, 1, 2);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMove_returnsTrue() {
|
||||
grid[4][4] = new Tower(2, Color.YELLOW); // owned by PLAYER1
|
||||
grid[4][5] = new Tower(2, Color.RED); // opposite color
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 4, 4, 4, 5);
|
||||
assertTrue(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentPlayerMismatchInPlyDoesNotAffectOwnershipCheck() {
|
||||
// Even if AvalamPly is constructed with a player value, isLegal uses board.getCurrentPlayer()
|
||||
grid[7][7] = new Tower(1, Color.YELLOW); // owned by PLAYER1 (board default)
|
||||
grid[7][8] = new Tower(1, Color.RED);
|
||||
// Construct ply with PLAYER2 explicitly — ownership check should still compare to board.getCurrentPlayer()
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER2, 7, 7, 7, 8);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user