Compare commits
22 Commits
dea162182b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3aba1312fb | |||
| c44e7495a4 | |||
| 86a86f598c | |||
| 2766fa7632 | |||
| c5d01dd2bb | |||
| f40f3daa1d | |||
| ebfc9d7d78 | |||
| 88df11dc03 | |||
| af70986edd | |||
|
|
d94b95fa36 | ||
|
|
2db0212b31 | ||
|
|
43e44bf7d2 | ||
| 023aa9d419 | |||
| 2cf929e024 | |||
|
|
ee374a9221 | ||
| 1eddda2605 | |||
|
|
b52badad31 | ||
|
|
88c65bc194 | ||
| 8bbe17ebd8 | |||
| f43361a48f | |||
| ad2f0c63cf | |||
|
|
6226f4254a |
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
|
||||||
@@ -43,8 +65,6 @@ classDiagram
|
|||||||
+toString(): String
|
+toString(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AvalamWindow{
|
class AvalamWindow{
|
||||||
-board : AvalamBoard
|
-board : AvalamBoard
|
||||||
-scoreView : ScoreView
|
-scoreView : ScoreView
|
||||||
@@ -70,14 +90,19 @@ 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
|
||||||
-backgroundLayer: BackgroundLayer
|
-backgroundLayer: BackgroundLayer
|
||||||
@@ -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 |
@@ -17,7 +17,17 @@ classDiagram
|
|||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
|
|||||||
47
Makefile
47
Makefile
@@ -1,5 +1,7 @@
|
|||||||
# === Environnements ===
|
# === Environnements ===
|
||||||
TEST_ENV = "bin:/usr/share/java/junit.jar:/usr/share/java/hamcrest-core.jar"
|
JUNIT_JAR = /usr/share/java/junit.jar
|
||||||
|
HAMCREST_JAR = /usr/share/java/hamcrest-core.jar
|
||||||
|
TEST_ENV = "bin:$(JUNIT_JAR):$(HAMCREST_JAR)"
|
||||||
|
|
||||||
# === Répertoires ===
|
# === Répertoires ===
|
||||||
SRC_DIR = fr
|
SRC_DIR = fr
|
||||||
@@ -9,8 +11,11 @@ BIN_DIR = bin
|
|||||||
RES_SRC = fr/iut_fbleau/Res
|
RES_SRC = fr/iut_fbleau/Res
|
||||||
RES_BIN = bin/fr/iut_fbleau/Res
|
RES_BIN = bin/fr/iut_fbleau/Res
|
||||||
|
|
||||||
# === Recherche automatique des fichiers .java dans tous les sous-dossiers ===
|
# === Recherche automatique des fichiers .java ===
|
||||||
SOURCES := $(shell find $(SRC_DIR) -name "*.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 ===
|
# === Classe principale ===
|
||||||
MAIN_CLASS = fr.iut_fbleau.Avalam.Main
|
MAIN_CLASS = fr.iut_fbleau.Avalam.Main
|
||||||
@@ -20,7 +25,10 @@ TEST_CLASS = fr.iut_fbleau.Tests.AvalamBoardTest
|
|||||||
|
|
||||||
# === Commandes Java ===
|
# === Commandes Java ===
|
||||||
JC = javac
|
JC = javac
|
||||||
JCFLAGS = -d $(BIN_DIR) -cp $(TEST_ENV)
|
# Compilation normale (application uniquement)
|
||||||
|
JCFLAGS = -d $(BIN_DIR)
|
||||||
|
# Compilation des tests (application + JUnit)
|
||||||
|
JCFLAGS_TESTS = -d $(BIN_DIR) -cp $(TEST_ENV)
|
||||||
|
|
||||||
JAVA = java
|
JAVA = java
|
||||||
JAVAFLAGS = -cp $(BIN_DIR)
|
JAVAFLAGS = -cp $(BIN_DIR)
|
||||||
@@ -38,6 +46,35 @@ compile:
|
|||||||
@mkdir -p $(BIN_DIR)
|
@mkdir -p $(BIN_DIR)
|
||||||
@$(JC) $(JCFLAGS) $(SOURCES)
|
@$(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 ===
|
# === Copie des ressources (.txt) dans bin ===
|
||||||
resources:
|
resources:
|
||||||
@echo "===> Copie des ressources..."
|
@echo "===> Copie des ressources..."
|
||||||
@@ -51,7 +88,7 @@ run:
|
|||||||
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
|
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
|
||||||
|
|
||||||
# === Tests ===
|
# === Tests ===
|
||||||
test:
|
test: check_test_deps compile_tests
|
||||||
@echo "===> Lancement des tests..."
|
@echo "===> Lancement des tests..."
|
||||||
@$(JAVA) $(JAVAFLAGS_TESTS) org.junit.runner.JUnitCore $(TEST_CLASS)
|
@$(JAVA) $(JAVAFLAGS_TESTS) org.junit.runner.JUnitCore $(TEST_CLASS)
|
||||||
@echo "... Fin des tests."
|
@echo "... Fin des tests."
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -12,37 +12,55 @@ Dans un second temps, nous développerons des bots les plus efficaces possible,
|
|||||||
|
|
||||||
Le jeu de notre groupe est **Avalam**.
|
Le jeu de notre groupe est **Avalam**.
|
||||||
|
|
||||||
## Compilation et exécution
|
## Compilation, exécution et tests
|
||||||
|
|
||||||
### Compilation
|
### Compilation (sans tests)
|
||||||
```bash
|
```bash
|
||||||
make build
|
make build
|
||||||
```
|
```
|
||||||
|
ou simplement :
|
||||||
### Tests
|
|
||||||
```bash
|
```bash
|
||||||
make test
|
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
|
```bash
|
||||||
make run
|
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
|
### Nettoyage
|
||||||
```bash
|
```bash
|
||||||
make clean
|
make clean
|
||||||
```
|
```
|
||||||
|
Supprime le répertoire `bin/` (classes compilées et ressources copiées).
|
||||||
|
|
||||||
### Recompiler et exécuter
|
### Recompiler puis exécuter
|
||||||
```bash
|
```bash
|
||||||
make re
|
make re
|
||||||
```
|
```
|
||||||
|
Équivaut à `make clean` puis `make build` puis `make run`.
|
||||||
|
|
||||||
### Générer la Javadoc
|
### Générer la Javadoc
|
||||||
```bash
|
```bash
|
||||||
make javadoc
|
make javadoc
|
||||||
```
|
```
|
||||||
|
Génère la documentation dans le dossier `doc/`.
|
||||||
|
|
||||||
## Architecture du projet
|
## Architecture du projet
|
||||||
|
|
||||||
@@ -105,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.
@@ -1,6 +1,7 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Bot.AlphaBetaBot;
|
import fr.iut_fbleau.Bot.AlphaBetaBot;
|
||||||
|
import fr.iut_fbleau.Bot.DivineBot;
|
||||||
import fr.iut_fbleau.Bot.IdiotBot;
|
import fr.iut_fbleau.Bot.IdiotBot;
|
||||||
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
|
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
|
||||||
import fr.iut_fbleau.GameAPI.Player;
|
import fr.iut_fbleau.GameAPI.Player;
|
||||||
@@ -13,10 +14,14 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fenêtre pour le mode Arène.
|
* Fenêtre pour le mode Arène (bots vs bots).
|
||||||
* Permet de sélectionner deux bots, le nombre de parties, et affiche les résultats.
|
|
||||||
*
|
*
|
||||||
* @version 1.0
|
* Elle permet :
|
||||||
|
* - de sélectionner deux bots parmi Idiot, Alpha-Beta et Divin ;
|
||||||
|
* - de configurer la profondeur de recherche pour les bots intelligents ;
|
||||||
|
* - de choisir le nombre de parties à jouer ;
|
||||||
|
* - d’afficher, dans un tableau, le résultat de chaque partie (gagnant ou erreur) ;
|
||||||
|
* - de revenir au menu principal ou de quitter entièrement le jeu.
|
||||||
*/
|
*/
|
||||||
public class ArenaWindow extends JFrame {
|
public class ArenaWindow extends JFrame {
|
||||||
|
|
||||||
@@ -61,6 +66,11 @@ public class ArenaWindow extends JFrame {
|
|||||||
});
|
});
|
||||||
buttonPanel.add(backButton);
|
buttonPanel.add(backButton);
|
||||||
|
|
||||||
|
// Nouveau bouton pour quitter entièrement le jeu
|
||||||
|
JButton quitButton = new JButton("Quitter");
|
||||||
|
quitButton.addActionListener(e -> System.exit(0));
|
||||||
|
buttonPanel.add(quitButton);
|
||||||
|
|
||||||
add(buttonPanel, BorderLayout.SOUTH);
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
@@ -182,24 +192,55 @@ 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);
|
||||||
|
|
||||||
|
// 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++) {
|
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);
|
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1);
|
||||||
ArenaGame game = new ArenaGame(board, bot1, bot2);
|
ArenaGame game = new ArenaGame(board, bot1, bot2);
|
||||||
|
|
||||||
@@ -207,30 +248,79 @@ public class ArenaWindow extends JFrame {
|
|||||||
Result result = game.run();
|
Result result = game.run();
|
||||||
String winner = getWinnerName(result, bot1Type, bot2Type);
|
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[]{
|
tableModel.addRow(new Object[]{
|
||||||
"Partie " + i,
|
"Partie " + partieNum,
|
||||||
bot1Type,
|
bot1Type,
|
||||||
bot2Type,
|
bot2Type,
|
||||||
winner
|
winner
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
errors++;
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
tableModel.addRow(new Object[]{
|
tableModel.addRow(new Object[]{
|
||||||
"Partie " + i,
|
"Partie " + partieNum,
|
||||||
bot1Type,
|
bot1Type,
|
||||||
bot2Type,
|
bot2Type,
|
||||||
"Erreur: " + e.getMessage()
|
"Erreur: " + e.getMessage()
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Afficher un message de fin
|
// Afficher les statistiques finales
|
||||||
JOptionPane.showMessageDialog(
|
final int finalBot1Wins = bot1Wins;
|
||||||
this,
|
final int finalBot2Wins = bot2Wins;
|
||||||
"Toutes les parties sont terminées !",
|
final int finalDraws = draws;
|
||||||
"Arène terminée",
|
final int finalErrors = errors;
|
||||||
JOptionPane.INFORMATION_MESSAGE
|
|
||||||
|
// 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,
|
||||||
|
statsMessage,
|
||||||
|
"Arène terminée",
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.INFORMATION_MESSAGE,
|
||||||
|
null,
|
||||||
|
options,
|
||||||
|
options[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (choice == 1) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
arenaThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,14 +337,7 @@ public class ArenaWindow extends JFrame {
|
|||||||
} else if (botType.equals("Bot Alpha-Beta")) {
|
} else if (botType.equals("Bot Alpha-Beta")) {
|
||||||
return new AlphaBetaBot(player, depth);
|
return new AlphaBetaBot(player, depth);
|
||||||
} else if (botType.equals("Bot Divin")) {
|
} else if (botType.equals("Bot Divin")) {
|
||||||
// Pour l'instant, le Bot Divin n'est pas implémenté, on utilise IdiotBot
|
return new DivineBot(player, depth);
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,6 +212,7 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
Tower dst = grid[xT][yT];
|
Tower dst = grid[xT][yT];
|
||||||
if (src == null || dst == null) return false;
|
if (src == null || dst == null) return false;
|
||||||
|
|
||||||
|
if (p.getPlayer() != getCurrentPlayer()) return false;
|
||||||
if (src.getColor() != colorForPlayer(getCurrentPlayer())) return false;
|
if (src.getColor() != colorForPlayer(getCurrentPlayer())) return false;
|
||||||
if (!areAdjacent(xF, yF, xT, yT)) return false;
|
if (!areAdjacent(xF, yF, xT, yT)) return false;
|
||||||
if (src.getColor() == dst.getColor()) return false;
|
if (src.getColor() == dst.getColor()) return false;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Bot.AlphaBetaBot;
|
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.Bot.IdiotBot;
|
||||||
import fr.iut_fbleau.GameAPI.AbstractPly;
|
import fr.iut_fbleau.GameAPI.AbstractPly;
|
||||||
@@ -14,22 +13,25 @@ import java.awt.*;
|
|||||||
/**
|
/**
|
||||||
* La classe <code>AvalamWindow</code>
|
* La classe <code>AvalamWindow</code>
|
||||||
*
|
*
|
||||||
* Fenêtre principale (interface graphique) du jeu Avalam.
|
* Fenêtre principale (interface graphique) du jeu Avalam pour tous les modes
|
||||||
* Elle contient :
|
* hors Arène :
|
||||||
* - le plateau (BoardView)
|
* - joueur vs joueur (PVP)
|
||||||
* - l’affichage du score (ScoreView)
|
* - joueur vs bot idiot (PVBOT)
|
||||||
* - l’affichage du joueur courant (TurnView)
|
* - joueur vs bot alpha (PVALPHA)
|
||||||
*
|
|
||||||
* Elle pilote un objet <code>AvalamBoard</code> (moteur du jeu).
|
|
||||||
* Elle peut fonctionner en mode :
|
|
||||||
* - joueur vs joueur
|
|
||||||
* - joueur vs bot idiot (aléatoire)
|
|
||||||
* - joueur vs bot alpha (cut-off)
|
|
||||||
* - joueur vs bot divin (PVGOD)
|
* - joueur vs bot divin (PVGOD)
|
||||||
*
|
*
|
||||||
* @version 1.0
|
* Elle contient :
|
||||||
* Date :
|
* - le plateau (<code>BoardView</code>)
|
||||||
* Licence :
|
* - l’affichage du score (<code>ScoreView</code>)
|
||||||
|
* - l’affichage du joueur courant (<code>TurnView</code>)
|
||||||
|
*
|
||||||
|
* Elle pilote un objet <code>AvalamBoard</code> (moteur du jeu) et,
|
||||||
|
* en fonction du {@link GameMode}, instancie le bot approprié
|
||||||
|
* (idiot, alpha-bêta ou divin).
|
||||||
|
*
|
||||||
|
* En fin de partie, elle ouvre une fenêtre de fin (<code>EndGameDialog</code>)
|
||||||
|
* affichant le gagnant, les scores et proposant les actions :
|
||||||
|
* « Rejouer », « Menu principal » ou « Quitter le jeu ».
|
||||||
*/
|
*/
|
||||||
public class AvalamWindow extends JFrame {
|
public class AvalamWindow extends JFrame {
|
||||||
|
|
||||||
@@ -50,6 +52,9 @@ public class AvalamWindow extends JFrame {
|
|||||||
/** Mode de jeu sélectionné. */
|
/** Mode de jeu sélectionné. */
|
||||||
private final GameMode mode;
|
private final GameMode mode;
|
||||||
|
|
||||||
|
/** Profondeur de recherche utilisée (utile pour les modes avec bot intelligent et pour rejouer). */
|
||||||
|
private final int searchDepth;
|
||||||
|
|
||||||
/** Joueur contrôlé par le bot (si actif). */
|
/** Joueur contrôlé par le bot (si actif). */
|
||||||
private final Player botPlayer = Player.PLAYER2;
|
private final Player botPlayer = Player.PLAYER2;
|
||||||
|
|
||||||
@@ -59,18 +64,9 @@ public class AvalamWindow extends JFrame {
|
|||||||
/** Bot Alpha-Beta (utilisé si mode PVALPHA). */
|
/** Bot Alpha-Beta (utilisé si mode PVALPHA). */
|
||||||
private final AlphaBetaBot alphaBot;
|
private final AlphaBetaBot alphaBot;
|
||||||
|
|
||||||
|
/** Bot Divin (utilisé si mode PVGOD). */
|
||||||
private final DivineBot divineBot;
|
private final DivineBot divineBot;
|
||||||
|
|
||||||
// A FAIRE PLUS TARD (PVGOD)
|
|
||||||
// /** Bot Divin (utilisé si mode PVGOD). */
|
|
||||||
// private final DivineBot divineBot;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A FAIRE PLUS TARD (PVGOD)
|
|
||||||
* On garde l'attribut à null pour ne pas casser la compilation,
|
|
||||||
* mais toute la logique PVGOD est désactivée/commentée.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Indique si une animation de tour de bot est en cours. */
|
/** Indique si une animation de tour de bot est en cours. */
|
||||||
private boolean botAnimating = false;
|
private boolean botAnimating = false;
|
||||||
|
|
||||||
@@ -87,7 +83,7 @@ public class AvalamWindow extends JFrame {
|
|||||||
* Construit la fenêtre en fonction du mode choisi.
|
* Construit la fenêtre en fonction du mode choisi.
|
||||||
* Pour PVALPHA/PVGOD, la profondeur par défaut est 4.
|
* Pour PVALPHA/PVGOD, la profondeur par défaut est 4.
|
||||||
*
|
*
|
||||||
* @param mode mode de jeu
|
* @param mode mode de jeu (PVP, PVBOT, PVALPHA ou PVGOD)
|
||||||
*/
|
*/
|
||||||
public AvalamWindow(GameMode mode) {
|
public AvalamWindow(GameMode mode) {
|
||||||
this(mode, 4);
|
this(mode, 4);
|
||||||
@@ -95,7 +91,8 @@ public class AvalamWindow extends JFrame {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Construit la fenêtre en fonction du mode choisi.
|
* Construit la fenêtre en fonction du mode choisi.
|
||||||
* Si le mode est PVALPHA ou PVGOD, la profondeur est utilisée comme cut-off.
|
* Si le mode est PVALPHA ou PVGOD, la profondeur est utilisée comme cut-off
|
||||||
|
* pour les bots Alpha-Beta et Divin.
|
||||||
*
|
*
|
||||||
* @param mode mode de jeu
|
* @param mode mode de jeu
|
||||||
* @param alphaDepth profondeur de recherche pour Alpha-Beta / Bot Divin
|
* @param alphaDepth profondeur de recherche pour Alpha-Beta / Bot Divin
|
||||||
@@ -104,13 +101,13 @@ public class AvalamWindow extends JFrame {
|
|||||||
super("Avalam");
|
super("Avalam");
|
||||||
|
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.searchDepth = Math.max(1, alphaDepth);
|
||||||
|
|
||||||
this.idiotBot = (mode == GameMode.PVBOT) ? new IdiotBot(botPlayer) : null;
|
this.idiotBot = (mode == GameMode.PVBOT) ? new IdiotBot(botPlayer) : null;
|
||||||
|
|
||||||
int depth = Math.max(1, alphaDepth);
|
int depth = this.searchDepth;
|
||||||
this.alphaBot = (mode == GameMode.PVALPHA) ? new AlphaBetaBot(botPlayer, depth) : null;
|
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);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
@@ -169,28 +166,30 @@ public class AvalamWindow extends JFrame {
|
|||||||
if (board.isGameOver()) {
|
if (board.isGameOver()) {
|
||||||
Result res = board.getResult();
|
Result res = board.getResult();
|
||||||
|
|
||||||
String msg;
|
int scoreJaune = computeScore(Color.YELLOW);
|
||||||
switch (res) {
|
int scoreRouge = computeScore(Color.RED);
|
||||||
case WIN:
|
|
||||||
msg = "Le joueur jaune a gagné !";
|
|
||||||
break;
|
|
||||||
case LOSS:
|
|
||||||
msg = "Le joueur rouge a gagné !";
|
|
||||||
break;
|
|
||||||
case DRAW:
|
|
||||||
msg = "Égalité !";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
msg = "Fin de partie.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(
|
EndGameDialog dialog = new EndGameDialog(
|
||||||
this,
|
this,
|
||||||
msg,
|
res,
|
||||||
"Partie terminée",
|
scoreJaune,
|
||||||
JOptionPane.INFORMATION_MESSAGE
|
scoreRouge,
|
||||||
|
mode,
|
||||||
|
searchDepth,
|
||||||
|
// Rejouer : on ferme la fenêtre actuelle et on relance une nouvelle partie avec le même mode/profondeur
|
||||||
|
() -> SwingUtilities.invokeLater(() -> {
|
||||||
|
dispose();
|
||||||
|
new AvalamWindow(mode, searchDepth);
|
||||||
|
}),
|
||||||
|
// Menu principal : on ferme la fenêtre actuelle et on réaffiche le menu de mode de jeu
|
||||||
|
() -> SwingUtilities.invokeLater(() -> {
|
||||||
|
dispose();
|
||||||
|
Main.showModeSelection();
|
||||||
|
}),
|
||||||
|
// Quitter complètement l'application
|
||||||
|
() -> System.exit(0)
|
||||||
);
|
);
|
||||||
|
dialog.setVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,12 +219,8 @@ public class AvalamWindow extends JFrame {
|
|||||||
if (mode == GameMode.PVBOT && idiotBot == null) return;
|
if (mode == GameMode.PVBOT && idiotBot == null) return;
|
||||||
if (mode == GameMode.PVALPHA && alphaBot == null) return;
|
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.
|
|
||||||
|
|
||||||
botAnimating = true;
|
botAnimating = true;
|
||||||
|
|
||||||
// Désactiver les interactions du joueur pendant le tour du bot.
|
// Désactiver les interactions du joueur pendant le tour du bot.
|
||||||
@@ -238,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
160
fr/iut_fbleau/Avalam/EndGameDialog.java
Normal file
160
fr/iut_fbleau/Avalam/EndGameDialog.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import fr.iut_fbleau.GameAPI.Result;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fenêtre de fin de partie.
|
||||||
|
*
|
||||||
|
* Elle est ouverte par {@link AvalamWindow} lorsque le moteur signale
|
||||||
|
* que la partie est terminée. Elle affiche :
|
||||||
|
* - le résultat (gagnant ou égalité) à partir du {@link Result} ;
|
||||||
|
* - le score détaillé (tours contrôlées par Jaune et Rouge) ;
|
||||||
|
* - le mode de jeu courant (PVP, PVBOT, PVALPHA, PVGOD, avec profondeur pour les bots intelligents).
|
||||||
|
*
|
||||||
|
* Elle propose également trois actions sous forme de boutons :
|
||||||
|
* - « Rejouer » : relancer une partie avec la même configuration ;
|
||||||
|
* - « Menu principal » : retourner au menu de sélection de mode ;
|
||||||
|
* - « Quitter » : fermer complètement l’application.
|
||||||
|
*/
|
||||||
|
public class EndGameDialog extends JDialog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la fenêtre de fin de partie.
|
||||||
|
*
|
||||||
|
* @param parent fenêtre principale (généralement une {@link AvalamWindow})
|
||||||
|
* @param result résultat de la partie (WIN / LOSS / DRAW du point de vue de PLAYER1 / Jaune)
|
||||||
|
* @param scoreJaune score du joueur jaune (nombre de tours contrôlées)
|
||||||
|
* @param scoreRouge score du joueur rouge (nombre de tours contrôlées)
|
||||||
|
* @param mode mode de jeu courant (pour l’information et le « Rejouer »)
|
||||||
|
* @param depth profondeur utilisée (pour les modes avec bot intelligent)
|
||||||
|
* @param onReplay action à exécuter lorsque l’utilisateur clique sur « Rejouer »
|
||||||
|
* @param onMenu action à exécuter lorsque l’utilisateur clique sur « Menu principal »
|
||||||
|
* @param onQuit action à exécuter lorsque l’utilisateur clique sur « Quitter »
|
||||||
|
*/
|
||||||
|
public EndGameDialog(
|
||||||
|
JFrame parent,
|
||||||
|
Result result,
|
||||||
|
int scoreJaune,
|
||||||
|
int scoreRouge,
|
||||||
|
GameMode mode,
|
||||||
|
int depth,
|
||||||
|
Runnable onReplay,
|
||||||
|
Runnable onMenu,
|
||||||
|
Runnable onQuit
|
||||||
|
) {
|
||||||
|
super(parent, "Fin de partie", true);
|
||||||
|
|
||||||
|
setLayout(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
// Panel principal d'information
|
||||||
|
JPanel infoPanel = new JPanel();
|
||||||
|
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.Y_AXIS));
|
||||||
|
infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
JLabel titre = new JLabel("Partie terminée");
|
||||||
|
titre.setFont(titre.getFont().deriveFont(Font.BOLD, 18f));
|
||||||
|
titre.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||||
|
|
||||||
|
String gagnant;
|
||||||
|
switch (result) {
|
||||||
|
case WIN:
|
||||||
|
gagnant = "Le joueur JAUNE a gagné !";
|
||||||
|
break;
|
||||||
|
case LOSS:
|
||||||
|
gagnant = "Le joueur ROUGE a gagné !";
|
||||||
|
break;
|
||||||
|
case DRAW:
|
||||||
|
gagnant = "Égalité parfaite !";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gagnant = "Fin de partie.";
|
||||||
|
}
|
||||||
|
|
||||||
|
JLabel gagnantLabel = new JLabel(gagnant);
|
||||||
|
gagnantLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||||
|
|
||||||
|
JLabel scoreLabel = new JLabel(
|
||||||
|
"Score - Jaune : " + scoreJaune + " | Rouge : " + scoreRouge
|
||||||
|
);
|
||||||
|
scoreLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||||
|
|
||||||
|
JLabel modeLabel = new JLabel("Mode : " + modeToString(mode, depth));
|
||||||
|
modeLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||||
|
|
||||||
|
infoPanel.add(titre);
|
||||||
|
infoPanel.add(Box.createVerticalStrut(10));
|
||||||
|
infoPanel.add(gagnantLabel);
|
||||||
|
infoPanel.add(Box.createVerticalStrut(5));
|
||||||
|
infoPanel.add(scoreLabel);
|
||||||
|
infoPanel.add(Box.createVerticalStrut(5));
|
||||||
|
infoPanel.add(modeLabel);
|
||||||
|
|
||||||
|
add(infoPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Panel des boutons d'action
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 10));
|
||||||
|
|
||||||
|
JButton replayButton = new JButton("Rejouer");
|
||||||
|
replayButton.addActionListener(e -> {
|
||||||
|
if (onReplay != null) {
|
||||||
|
onReplay.run();
|
||||||
|
}
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
JButton menuButton = new JButton("Menu principal");
|
||||||
|
menuButton.addActionListener(e -> {
|
||||||
|
if (onMenu != null) {
|
||||||
|
onMenu.run();
|
||||||
|
}
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
JButton quitButton = new JButton("Quitter");
|
||||||
|
quitButton.addActionListener(e -> {
|
||||||
|
if (onQuit != null) {
|
||||||
|
onQuit.run();
|
||||||
|
}
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonPanel.add(replayButton);
|
||||||
|
buttonPanel.add(menuButton);
|
||||||
|
buttonPanel.add(quitButton);
|
||||||
|
|
||||||
|
add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
setResizable(false);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne une description lisible du mode de jeu courant.
|
||||||
|
*
|
||||||
|
* @param mode mode de jeu
|
||||||
|
* @param depth profondeur (pour les bots intelligents)
|
||||||
|
* @return chaîne décrivant le mode
|
||||||
|
*/
|
||||||
|
private String modeToString(GameMode mode, int depth) {
|
||||||
|
switch (mode) {
|
||||||
|
case PVP:
|
||||||
|
return "Joueur vs Joueur";
|
||||||
|
case PVBOT:
|
||||||
|
return "Joueur vs Bot idiot";
|
||||||
|
case PVALPHA:
|
||||||
|
return "Joueur vs Bot Alpha (profondeur " + depth + ")";
|
||||||
|
case PVGOD:
|
||||||
|
return "Joueur vs Bot Divin (profondeur " + depth + ")";
|
||||||
|
case ARENA:
|
||||||
|
return "Arène (bots)"; // normalement non utilisé ici
|
||||||
|
default:
|
||||||
|
return mode.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ public class Main {
|
|||||||
"joueur vs joueur",
|
"joueur vs joueur",
|
||||||
"joueur vs botidiot",
|
"joueur vs botidiot",
|
||||||
"joueur vs bot alpha",
|
"joueur vs bot alpha",
|
||||||
"joueur vs bot divin (NON IMPLEMENTE)",
|
"joueur vs bot divin",
|
||||||
"Arène"
|
"Arène"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,74 +2,53 @@ 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
|
|
||||||
int value = alphaBeta(next, maxDepth - 1, alpha, beta);
|
|
||||||
|
|
||||||
if (isMax) {
|
|
||||||
if (value > bestValue) {
|
if (value > bestValue) {
|
||||||
bestValue = value;
|
bestValue = value;
|
||||||
bestMoves.clear();
|
bestMoves.clear();
|
||||||
@@ -77,73 +56,48 @@ public class DivineBot extends AbstractGamePlayer {
|
|||||||
} else if (value == bestValue) {
|
} else if (value == bestValue) {
|
||||||
bestMoves.add(m);
|
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()) {
|
||||||
|
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);
|
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;
|
||||||
|
|
||||||
@@ -151,35 +105,76 @@ public class DivineBot extends AbstractGamePlayer {
|
|||||||
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")
|
// Points sécurisés (isolation ou hauteur max)
|
||||||
int value =
|
if (h == 5 || isIsolated(b, r, c)) {
|
||||||
(h == 5) ? 1000 :
|
weight = 1000;
|
||||||
(h == 4) ? 300 :
|
} else {
|
||||||
(h == 3) ? 120 :
|
// Gestion de la vulnérabilité face à l'ennemi
|
||||||
(h == 2) ? 40 : 10;
|
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;
|
if (t.getColor() == myColor) score += weight;
|
||||||
else score -= value;
|
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