Compare commits
23 Commits
quitter_re
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3aba1312fb | |||
| c44e7495a4 | |||
| 86a86f598c | |||
| 2766fa7632 | |||
| c5d01dd2bb | |||
| f40f3daa1d | |||
| ebfc9d7d78 | |||
| 88df11dc03 | |||
| af70986edd | |||
|
|
d94b95fa36 | ||
|
|
2db0212b31 | ||
|
|
43e44bf7d2 | ||
| 023aa9d419 | |||
| 2cf929e024 | |||
|
|
ee374a9221 | ||
| 1eddda2605 | |||
| 8bbe17ebd8 | |||
| f43361a48f | |||
| ad2f0c63cf | |||
| dea162182b | |||
| c0cd120b1e | |||
| fa578b86d2 | |||
| 14e5df4332 |
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
|
||||
|
||||
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{
|
||||
+SIZE: int
|
||||
-MAX_HEIGHT: int
|
||||
@@ -43,8 +65,6 @@ classDiagram
|
||||
+toString(): String
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AvalamWindow{
|
||||
-board : AvalamBoard
|
||||
-scoreView : ScoreView
|
||||
@@ -70,14 +90,19 @@ classDiagram
|
||||
AvalamWindow *-- BoardView
|
||||
AvalamWindow *-- ScoreView
|
||||
AvalamWindow *-- TurnView
|
||||
AvalamWindow *-- EndGameDialog
|
||||
AvalamWindow --> GameMode
|
||||
|
||||
class BackgroundLayer{
|
||||
-img: Image
|
||||
+BackgroundLayer(String resourcePath)
|
||||
#paintComponent(Graphics g): void
|
||||
}
|
||||
|
||||
class BoardLoader{
|
||||
+loadFromFile(String resourcePath): Tower[][]
|
||||
}
|
||||
|
||||
|
||||
|
||||
class BoardView{
|
||||
-board: AvalamBoard
|
||||
-backgroundLayer: BackgroundLayer
|
||||
@@ -105,14 +130,6 @@ classDiagram
|
||||
BoardView *-- InteractionController
|
||||
BoardView --> AvalamBoard
|
||||
|
||||
class BackgroundLayer{
|
||||
-img: Image
|
||||
+BackgroundLayer(String resourcePath)
|
||||
#paintComponent(Graphics g): void
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Color{
|
||||
-YELLOW(int r, int g, int b)
|
||||
-RED(int r, int g, int b)
|
||||
@@ -122,11 +139,17 @@ classDiagram
|
||||
+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{
|
||||
PVP
|
||||
PVBOT
|
||||
PVALPHA
|
||||
PVGOD
|
||||
ARENA
|
||||
}
|
||||
|
||||
class HighlightLayer{
|
||||
@@ -165,6 +188,7 @@ classDiagram
|
||||
|
||||
Main ..> AvalamWindow
|
||||
Main ..> GameMode
|
||||
Main ..> ArenaWindow
|
||||
|
||||
class PieceButton{
|
||||
-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 |
@@ -17,7 +17,17 @@ classDiagram
|
||||
}
|
||||
|
||||
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{
|
||||
|
||||
54
Makefile
54
Makefile
@@ -1,3 +1,8 @@
|
||||
# === Environnements ===
|
||||
JUNIT_JAR = /usr/share/java/junit.jar
|
||||
HAMCREST_JAR = /usr/share/java/hamcrest-core.jar
|
||||
TEST_ENV = "bin:$(JUNIT_JAR):$(HAMCREST_JAR)"
|
||||
|
||||
# === Répertoires ===
|
||||
SRC_DIR = fr
|
||||
BIN_DIR = bin
|
||||
@@ -6,18 +11,28 @@ BIN_DIR = bin
|
||||
RES_SRC = fr/iut_fbleau/Res
|
||||
RES_BIN = bin/fr/iut_fbleau/Res
|
||||
|
||||
# === Recherche automatique des fichiers .java dans tous les sous-dossiers ===
|
||||
SOURCES := $(shell find $(SRC_DIR) -name "*.java")
|
||||
# === Recherche automatique des fichiers .java ===
|
||||
# SOURCES : uniquement le code de l'application (sans les fichiers de tests)
|
||||
SOURCES := $(shell find $(SRC_DIR) -name "*.java" -not -path "$(SRC_DIR)/iut_fbleau/Tests/*")
|
||||
# TEST_SOURCES : uniquement les fichiers de tests
|
||||
TEST_SOURCES := $(shell find $(SRC_DIR)/iut_fbleau/Tests -name "*.java" 2>/dev/null)
|
||||
|
||||
# === Classe principale ===
|
||||
MAIN_CLASS = fr.iut_fbleau.Avalam.Main
|
||||
|
||||
# === Classe de test ===
|
||||
TEST_CLASS = fr.iut_fbleau.Tests.AvalamBoardTest
|
||||
|
||||
# === Commandes Java ===
|
||||
JC = javac
|
||||
# Compilation normale (application uniquement)
|
||||
JCFLAGS = -d $(BIN_DIR)
|
||||
# Compilation des tests (application + JUnit)
|
||||
JCFLAGS_TESTS = -d $(BIN_DIR) -cp $(TEST_ENV)
|
||||
|
||||
JAVA = java
|
||||
JAVAFLAGS = -cp $(BIN_DIR)
|
||||
JAVAFLAGS_TESTS = -cp $(TEST_ENV)
|
||||
|
||||
# === Règle par défaut ===
|
||||
all: build
|
||||
@@ -31,6 +46,35 @@ compile:
|
||||
@mkdir -p $(BIN_DIR)
|
||||
@$(JC) $(JCFLAGS) $(SOURCES)
|
||||
|
||||
compile_tests: compile
|
||||
@echo "===> Compilation des tests..."
|
||||
@mkdir -p $(BIN_DIR)
|
||||
ifneq ($(TEST_SOURCES),)
|
||||
@$(JC) $(JCFLAGS_TESTS) $(TEST_SOURCES)
|
||||
else
|
||||
@echo "Aucun fichier de test trouvé dans $(SRC_DIR)/iut_fbleau/Tests"
|
||||
endif
|
||||
|
||||
# === Vérification / installation des dépendances de tests ===
|
||||
check_test_deps:
|
||||
@echo "===> Vérification des dépendances de tests (JUnit / Hamcrest)..."
|
||||
@if [ ! -f "$(JUNIT_JAR)" ] || [ ! -f "$(HAMCREST_JAR)" ]; then \
|
||||
echo " JUnit ou Hamcrest manquant, tentative d'installation (sudo requis)..."; \
|
||||
if command -v sudo >/dev/null 2>&1; then \
|
||||
sudo apt-get update && sudo apt-get install -y junit4 libhamcrest-java; \
|
||||
else \
|
||||
apt-get update && apt-get install -y junit4 libhamcrest-java; \
|
||||
fi; \
|
||||
if [ ! -f "$(JUNIT_JAR)" ] || [ ! -f "$(HAMCREST_JAR)" ]; then \
|
||||
echo "✖ Impossible de trouver/installer $(JUNIT_JAR) ou $(HAMCREST_JAR). Vérifiez manuellement vos paquets JUnit/Hamcrest."; \
|
||||
exit 1; \
|
||||
else \
|
||||
echo "✔ Dépendances de tests installées."; \
|
||||
fi; \
|
||||
else \
|
||||
echo "✔ JUnit et Hamcrest trouvés."; \
|
||||
fi
|
||||
|
||||
# === Copie des ressources (.txt) dans bin ===
|
||||
resources:
|
||||
@echo "===> Copie des ressources..."
|
||||
@@ -43,6 +87,12 @@ run:
|
||||
@echo "===> Lancement du jeu Avalam..."
|
||||
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
|
||||
|
||||
# === Tests ===
|
||||
test: check_test_deps compile_tests
|
||||
@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..."
|
||||
|
||||
46
README.md
46
README.md
@@ -4,38 +4,63 @@
|
||||
|
||||
Date de création : 16/10/25
|
||||
|
||||
Le but est de mettre en oeuvre des jeux plus intéressants que le jeu de Nim, toujours en suivant l'API.
|
||||
Date de rendu : 06/02/26
|
||||
|
||||
Dans un second temps, on fera des bots le plus efficace possible (probablement un alpha beta avec cut-off plus fonction d'évaluation qui peut être faite à la main ou par MonteCarlo).
|
||||
Le but est de mettre en oeuvre un jeu plus intéressants que le jeu de Nim, en suivant l'API de Florent Madelaine.
|
||||
|
||||
Dans un second temps, nous développerons des bots les plus efficaces possible, probablement en utilisant un algorithme alpha-bêta avec cut-off et une fonction d'évaluation réalisé à la main ou par MonteCarlo.
|
||||
|
||||
Le jeu de notre groupe est **Avalam**.
|
||||
|
||||
## Compilation et exécution
|
||||
## Compilation, exécution et tests
|
||||
|
||||
### Compilation
|
||||
### Compilation (sans tests)
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
ou simplement :
|
||||
```bash
|
||||
make
|
||||
```
|
||||
Cette commande :
|
||||
- compile uniquement le code de l'application (sans les fichiers du dossier `fr/iut_fbleau/Tests`) ;
|
||||
- copie les ressources dans `bin/`.
|
||||
|
||||
### Exécution
|
||||
### Exécution du jeu
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
Lance la fenêtre de jeu Avalam après compilation.
|
||||
|
||||
### Lancer les tests
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
Cette commande :
|
||||
- vérifie d'abord la présence de **JUnit** et **Hamcrest** dans `/usr/share/java` ;
|
||||
- si nécessaire, tente de les installer automatiquement via `apt-get` (sudo requis sur Debian/Ubuntu) ;
|
||||
- compile ensuite les fichiers de tests (`fr/iut_fbleau/Tests`) ;
|
||||
- lance enfin la suite de tests JUnit (`AvalamBoardTest`).
|
||||
|
||||
Si l'installation automatique échoue (autre OS, pas de droits sudo, pas d'accès réseau, etc.), un message l'indiquera et il faudra installer JUnit/Hamcrest manuellement.
|
||||
|
||||
### Nettoyage
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
Supprime le répertoire `bin/` (classes compilées et ressources copiées).
|
||||
|
||||
### Recompiler et exécuter
|
||||
### Recompiler puis exécuter
|
||||
```bash
|
||||
make re
|
||||
```
|
||||
Équivaut à `make clean` puis `make build` puis `make run`.
|
||||
|
||||
### Générer la Javadoc
|
||||
```bash
|
||||
make javadoc
|
||||
```
|
||||
Génère la documentation dans le dossier `doc/`.
|
||||
|
||||
## Architecture du projet
|
||||
|
||||
@@ -78,13 +103,13 @@ Le plateau est représenté par une grille 9x9 où chaque case contient :
|
||||
|
||||
Sur un plateau de jeu, les joueurs disposent de 24 pions chacun. Le but est de créer de petites tours avec son pion de couleur au-dessus. Pour ce faire, chaque joueur déplace son pion sur une tour ou déplace une tour sur un autre pion. La personne ayant le plus de pions sur le dessus des tours gagne.
|
||||
|
||||
### Régles officiel :
|
||||
### Règles officielles :
|
||||
**Régle de base** \
|
||||
Chaque joueur choisit sa couleur.
|
||||
Le but du jeu est de constituer un maximum de tours de 1 à 5 pions, jamais plus, surmontées par un pion de sa couleur.
|
||||
Un joueur est le propriétaire d'une tour lorsqu'un pion de sa couleur en occupe le sommet. Un pion isolé constitue également une tour.
|
||||
|
||||
**Deplacements** \
|
||||
**Déplacements** \
|
||||
Chaque joueur en effectue un seul mouvement, dans n'importe quel sens (horizontal, vertical, diagonal) avec n'importe quel pion (ou pile de pions), quelle qu'en soit la couleur. Ce mouvement consiste à empiler le ou les pions déplacés sur un trou directement voisin déjà occupé par un ou plusieurs pions.
|
||||
|
||||
**Mouvement interdit (1)** \
|
||||
@@ -98,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.
|
||||
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,24 +192,55 @@ public class ArenaWindow extends JFrame {
|
||||
* @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");
|
||||
// Créer un dialogue de progression
|
||||
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);
|
||||
|
||||
// Exécuter les parties dans un thread séparé pour ne pas bloquer l'interface
|
||||
Thread arenaThread = new Thread(() -> {
|
||||
SwingUtilities.invokeLater(() -> progressDialog.setVisible(true));
|
||||
|
||||
// Statistiques pour déboguer
|
||||
int bot1Wins = 0;
|
||||
int bot2Wins = 0;
|
||||
int draws = 0;
|
||||
int errors = 0;
|
||||
|
||||
// Lancer les parties
|
||||
for (int i = 1; i <= nbParties; i++) {
|
||||
final int partieNum = i;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressLabel.setText("Partie " + partieNum + " / " + nbParties + " en cours...");
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -217,28 +248,64 @@ public class ArenaWindow extends JFrame {
|
||||
Result result = game.run();
|
||||
String winner = getWinnerName(result, bot1Type, bot2Type);
|
||||
|
||||
// Ajouter au tableau
|
||||
// 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 " + i,
|
||||
"Partie " + partieNum,
|
||||
bot1Type,
|
||||
bot2Type,
|
||||
winner
|
||||
});
|
||||
});
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
tableModel.addRow(new Object[]{
|
||||
"Partie " + i,
|
||||
"Partie " + partieNum,
|
||||
bot1Type,
|
||||
bot2Type,
|
||||
"Erreur: " + e.getMessage()
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher un message de fin avec possibilité de quitter directement
|
||||
// Afficher les statistiques finales
|
||||
final int finalBot1Wins = bot1Wins;
|
||||
final int finalBot2Wins = bot2Wins;
|
||||
final int finalDraws = draws;
|
||||
final int finalErrors = errors;
|
||||
|
||||
// Fermer le dialogue et afficher le message de fin avec statistiques
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
|
||||
String statsMessage = String.format(
|
||||
"Toutes les parties sont terminées !\n\n" +
|
||||
"Statistiques :\n" +
|
||||
"- %s (Bot 1) : %d victoires\n" +
|
||||
"- %s (Bot 2) : %d victoires\n" +
|
||||
"- Matchs nuls : %d\n" +
|
||||
"- Erreurs : %d",
|
||||
bot1Type, finalBot1Wins,
|
||||
bot2Type, finalBot2Wins,
|
||||
finalDraws,
|
||||
finalErrors
|
||||
);
|
||||
|
||||
Object[] options = {"OK", "Quitter le jeu"};
|
||||
int choice = JOptionPane.showOptionDialog(
|
||||
this,
|
||||
"Toutes les parties sont terminées !",
|
||||
statsMessage,
|
||||
"Arène terminée",
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.INFORMATION_MESSAGE,
|
||||
@@ -250,6 +317,10 @@ public class ArenaWindow extends JFrame {
|
||||
if (choice == 1) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
arenaThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,9 +51,16 @@ public class AvalamBoard extends AbstractBoard {
|
||||
super(startingPlayer, new ArrayDeque<>());
|
||||
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 c = 0; c < SIZE; c++)
|
||||
this.grid[r][c] = initialGrid[r][c];
|
||||
for (int c = 0; c < SIZE; 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,6 +212,7 @@ public class AvalamBoard extends AbstractBoard {
|
||||
Tower dst = grid[xT][yT];
|
||||
if (src == null || dst == null) return false;
|
||||
|
||||
if (p.getPlayer() != getCurrentPlayer()) return false;
|
||||
if (src.getColor() != colorForPlayer(getCurrentPlayer())) return false;
|
||||
if (!areAdjacent(xF, yF, xT, yT)) return false;
|
||||
if (src.getColor() == dst.getColor()) return false;
|
||||
|
||||
@@ -233,7 +233,6 @@ public class AvalamWindow extends JFrame {
|
||||
} else if (mode == GameMode.PVALPHA) {
|
||||
botMove = alphaBot.giveYourMove(board.safeCopy());
|
||||
} else {
|
||||
// A FAIRE PLUS TARD (PVGOD)
|
||||
botMove = divineBot.giveYourMove(board.safeCopy());
|
||||
}
|
||||
|
||||
|
||||
@@ -2,74 +2,53 @@ 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é).
|
||||
* * Idée :
|
||||
* - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
|
||||
* - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
|
||||
* Bot expert pour le jeu Avalam utilisant l'algorithme Alpha-Beta.
|
||||
* * Ce bot évalue le plateau en fonction du contrôle des tours,
|
||||
* de leur isolation (points sécurisés) et de leur vulnérabilité.
|
||||
*/
|
||||
public class DivineBot extends AbstractGamePlayer {
|
||||
|
||||
// Attributs
|
||||
|
||||
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
|
||||
/** Joueur contrôlé par le bot. */
|
||||
private final Player me;
|
||||
|
||||
/** Profondeur maximale de recherche avant évaluation. */
|
||||
/** Profondeur de recherche dans l'arbre des coups. */
|
||||
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();
|
||||
|
||||
// Constructeur
|
||||
|
||||
/**
|
||||
* Construit le bot Divine.
|
||||
*
|
||||
* @param p joueur contrôlé par ce bot
|
||||
* @param maxDepth profondeur de l'arbre de recherche
|
||||
* Constructeur du DivineBot.
|
||||
* @param p le joueur (P1 ou P2).
|
||||
* @param maxDepth la profondeur d'anticipation.
|
||||
*/
|
||||
public DivineBot(Player p, int maxDepth) {
|
||||
super(p);
|
||||
this.me = p;
|
||||
this.maxDepth = Math.max(1, maxDepth);
|
||||
this.maxDepth = maxDepth;
|
||||
}
|
||||
|
||||
// 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)
|
||||
* Calcule et retourne le meilleur coup possible.
|
||||
* @param board l'état actuel du jeu.
|
||||
* @return le coup sélectionné.
|
||||
*/
|
||||
@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;
|
||||
int bestValue = Integer.MIN_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);
|
||||
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();
|
||||
@@ -77,73 +56,48 @@ public class DivineBot extends AbstractGamePlayer {
|
||||
} 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.
|
||||
* Algorithme Alpha-Beta pour optimiser la recherche du meilleur score.
|
||||
*/
|
||||
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
|
||||
if (board.isGameOver()) {
|
||||
Result r = board.getResult();
|
||||
if (r == Result.DRAW) return 0;
|
||||
return (r == Result.WIN == (me == Player.PLAYER1)) ? 1000000 : -1000000;
|
||||
}
|
||||
|
||||
// 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;
|
||||
boolean isMax = (board.getCurrentPlayer() == me);
|
||||
int best = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
|
||||
|
||||
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
|
||||
best = Math.max(best, val);
|
||||
alpha = Math.max(alpha, best);
|
||||
} else {
|
||||
beta = Math.min(beta, val);
|
||||
if (alpha >= beta) break; // Coupure Alpha
|
||||
best = Math.min(best, val);
|
||||
beta = Math.min(beta, best);
|
||||
}
|
||||
if (alpha >= beta) break;
|
||||
}
|
||||
|
||||
return isMax ? alpha : beta;
|
||||
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.
|
||||
* Analyse la situation actuelle et attribue un score au plateau.
|
||||
* @param board le plateau à analyser.
|
||||
* @return le score (positif si avantageux pour le bot).
|
||||
*/
|
||||
private int evaluate(IBoard board) {
|
||||
|
||||
if (!(board instanceof AvalamBoard)) return 0;
|
||||
AvalamBoard b = (AvalamBoard) board;
|
||||
|
||||
@@ -151,35 +105,76 @@ public class DivineBot extends AbstractGamePlayer {
|
||||
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();
|
||||
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;
|
||||
// Points sécurisés (isolation ou hauteur max)
|
||||
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 += value;
|
||||
else score -= value;
|
||||
if (t.getColor() == myColor) score += weight;
|
||||
else score -= weight;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
List<AbstractPly> moves = new ArrayList<>();
|
||||
board.iterator().forEachRemaining(moves::add);
|
||||
Iterator<AbstractPly> it = board.iterator();
|
||||
while (it.hasNext()) moves.add(it.next());
|
||||
return moves;
|
||||
}
|
||||
}
|
||||
123
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal file
123
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal file
@@ -0,0 +1,123 @@
|
||||
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; //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];
|
||||
|
||||
//Création des tours de tests
|
||||
/* Motif
|
||||
1,0,2 | 2,0,3
|
||||
2,1,0 | 2,3,0
|
||||
0,2,1 | 0,1,1
|
||||
*/
|
||||
grid[4][2] = new Tower(2, Color.YELLOW);
|
||||
grid[6][2] = new Tower(3, Color.RED);
|
||||
|
||||
grid[4][3] = new Tower(2, Color.RED);
|
||||
grid[5][3] = new Tower(3, Color.YELLOW);
|
||||
|
||||
grid[5][4] = new Tower(1, Color.RED);
|
||||
grid[6][4] = new Tower(1, Color.YELLOW);
|
||||
|
||||
//Joueur courant initialisé à 1, soit jaune
|
||||
board = new AvalamBoard(grid); //AvalamBoard copie la grille
|
||||
}
|
||||
|
||||
/*
|
||||
@Test //Mockito absent
|
||||
public void nonAvalamPly_returnsFalse() {
|
||||
//Vérifie si l'instance est bien AvalamPly
|
||||
AbstractPly fake = Mockito.mock(AbstractPly.class); //Crée une instance non-AvalamPly
|
||||
assertFalse(board.isLegal(fake));
|
||||
}*/
|
||||
|
||||
@Test
|
||||
public void outOfBounds_returnsFalse() {
|
||||
//Source "out of box"
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, -1, 2, 4, 2);
|
||||
assertFalse(board.isLegal(p));
|
||||
|
||||
//Destination "out of box"
|
||||
AvalamPly p2 = new AvalamPly(Player.PLAYER1, 6, 4, 9, 4);
|
||||
assertFalse(board.isLegal(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameCell_returnsFalse() {
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 5, 4, 5, 4);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptySourceOrDest_returnsFalse() {
|
||||
//Source null
|
||||
AvalamPly p1 = new AvalamPly(Player.PLAYER1, 5, 5, 5, 4);
|
||||
assertFalse(board.isLegal(p1));
|
||||
|
||||
//Destination null
|
||||
AvalamPly p2 = new AvalamPly(Player.PLAYER1, 6, 4, 6, 3);
|
||||
assertFalse(board.isLegal(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceNotOwned_returnsFalse() {
|
||||
//Le joueur courant n'est pas rouge
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 5, 4, 6, 4);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAdjacent_returnsFalse() {
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 4, 2, 6, 2);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameColor_returnsFalse() {
|
||||
//La couleur des tours est identique
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 4, 2, 5, 3);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tooTallAfterMerge_returnsFalse() {
|
||||
//Hauteur maximale dépassé : 3+3 = 6 > MAX_HEIGHT (5)
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 5, 3, 6, 2);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validMove_returnsTrue() {
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER1, 5, 3, 4, 3); //Hauteur limite à 5
|
||||
assertTrue(board.isLegal(p));
|
||||
}
|
||||
|
||||
@Test //À vérifier
|
||||
public void currentPlayerMismatchInPlyDoesNotAffectOwnershipCheck() {
|
||||
//Si le coup est construit avec le mauvais joueur
|
||||
AvalamPly p = new AvalamPly(Player.PLAYER2, 4, 2, 4, 3);
|
||||
assertFalse(board.isLegal(p));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user