23 Commits

Author SHA1 Message Date
felix-vi
d94b95fa36 erreur de création dans l'arène 2026-02-05 20:50:23 +01:00
felix-vi
2db0212b31 bug sur la référence des tour dans avalamboard 2026-02-05 20:13:37 +01:00
felix-vi
43e44bf7d2 ajout d'une fenetre arene et bug divinebot 2026-02-05 19:46:02 +01:00
felix-vi
ee374a9221 modification du makefile pour séparer la compilation du jeu et des test et automatiser l'installation des dépendance 2026-02-05 16:37:57 +01:00
1eddda2605 Merge pull request 'quitter_rejouer_score' (#25) from quitter_rejouer_score into master
Reviewed-on: #25
2026-02-05 16:26:20 +01:00
felix-vi
b52badad31 mise à jour de la javadoc 2026-02-05 16:24:56 +01:00
felix-vi
88c65bc194 ajout d'un menu de fin et de pouvoir quitter 2026-02-05 16:21:09 +01:00
8bbe17ebd8 Merge pull request 'Test complet de la fonction isLegal() de AvalamBoard' (#24) from tests into master
Reviewed-on: #24
2026-02-05 16:16:53 +01:00
f43361a48f Ajout vérification correspondance du Joueur (Test Ok) 2026-02-05 16:06:03 +01:00
ad2f0c63cf Merge pull request 'mise en place du bot divin dans l'arène et modification de la javadoc' (#23) from Verification_BotDivin_Arene into master
Reviewed-on: #23
2026-02-05 16:05:22 +01:00
dea162182b Merge branch 'master' of https://grond.iut-fbleau.fr/dick/BUT3ProjetJeuGroupe into tests 2026-02-05 14:01:16 +01:00
c0cd120b1e Ajout de la commande de test au ReadMe + légères corrections de langue 2026-02-05 11:52:12 +01:00
fa578b86d2 Adaptation du test (Board copie la grille) (à tester) 2026-02-05 11:50:15 +01:00
felix-vi
6226f4254a mise en place du bot divin dans l'arène et modification de la javadoc 2026-02-04 13:22:17 +01:00
32f77e5495 Bot Divin et AvalamWindow connecté 2026-02-04 13:17:52 +01:00
7ae0d69aaa Bot divin en cours de création 2026-02-04 13:17:52 +01:00
fb0b8da097 Merge pull request 'Arene' (#21) from Arene into master
Reviewed-on: #21
2026-02-04 13:01:07 +01:00
14e5df4332 Test de isLegal (à debug) [wip] 2026-02-03 10:38:14 +01:00
6eb63cacaa Séparation des classes BoardView et BackgroundLayer 2026-02-03 09:20:22 +01:00
fdba5f0f2f Diagrammes V1(incomplet) 2026-01-30 10:44:58 +01:00
6269404e7d Merge pull request 'Retour sur master' (#18) from main into master
Reviewed-on: #18
2026-01-30 09:44:55 +01:00
4e8528fd58 Classe Tower Optimisé 2026-01-30 09:42:14 +01:00
fa493f2fc1 Ajout gitignore 2026-01-30 09:40:49 +01:00
17 changed files with 1295 additions and 361 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Ignorer le répertoire bin
/bin

View File

@@ -0,0 +1,218 @@
---
title: Avalam - Diagramme de classes (complet)
---
classDiagram
class AvalamBoard{
+SIZE: int
-MAX_HEIGHT: int
-grid: Tower[][]
-gameOver: boolean
-result: Result
+AvalamBoard(Tower[][] initialGrid, Player startingPlayer)
+AvalamBoard(Tower[][] initialGrid)
-AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result)
+getTowerAt(int row, int col): Tower
-inBounds(int r, int c): boolean
-areAdjacent(int r1, int c1, int r2, int c2): boolean
-colorForPlayer(Player p): Color
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+doPly(AbstractPly c): void
+iterator(): Iterator<AbstractPly>
+safeCopy(): IBoard
}
AvalamBoard "1" *-- "many" Tower
AvalamBoard ..> AvalamPly
class AvalamPly{
-xFrom : int
-yFrom : int
-xTo : int
-yTo : int
+AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo)
+getXFrom(): int
+getXFrom(): int
+getXFrom(): int
+getXFrom(): int
+toString(): String
}
class AvalamWindow{
-board : AvalamBoard
-scoreView : ScoreView
-turnView : TurnView
-boardView : BoardView
-mode: GameMode
-botPlayer: Player
-idiotBot: IdiotBot
-alphaBot: AlphaBetaBot
-divineBot: Object
-botAnimating: boolean
+AvalamWindow()
+AvalamWindow(GameMode mode)
+AvalamWindow(GameMode mode, int alphaDepth)
+onBoardUpdated(): void
-maybePlayBotTurn(): void
-computeScore(Color c): int
-turnMessage(): String
}
AvalamWindow *-- AvalamBoard
AvalamWindow *-- BoardView
AvalamWindow *-- ScoreView
AvalamWindow *-- TurnView
AvalamWindow --> GameMode
class BoardLoader{
+loadFromFile(String resourcePath): Tower[][]
}
class BoardView{
-board: AvalamBoard
-backgroundLayer: BackgroundLayer
-highlightLayer: HighlightLayer
-pieceLayer: PieceLayer
-controller: InteractionController
-size: int
-spacing: int
-xBase: int
-yBase: int
-boardUpdateCallback: Runnable
+BoardView(AvalamBoard board, Runnable boardUpdateCallback)
+getController(): InteractionController
+setInteractionEnabled(boolean enabled): void
+onBoardUpdated(): void
+refresh(): void
-boardGrid(): Tower[][]
-boardGrid(): Tower[][]
}
BoardView *-- BackgroundLayer
BoardView *-- HighlightLayer
BoardView *-- PieceLayer
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)
-swingColor: java.awt.Color
+Color(int r, int g, int b)
+getSwingColor(): java.awt.Color
+toPlayer(): fr.iut_fbleau.GameAPI.Player
}
class GameMode{
PVP
PVBOT
PVALPHA
PVGOD
}
class HighlightLayer{
-xBase: int
-yBase: int
-spacing: int
-size: int
-legalMoves: List<Point>
+HighlightLayer(int xBase, int yBase, int spacing, int size)
+setLegalMoves(List<Point> moves): void
#paintComponent(Graphics g): void
}
class InteractionController{
-board: AvalamBoard
-selectedRow: int
-selectedCol: int
-legalMoves: List<Point>
-view: BoardView
+InteractionController(AvalamBoard board, BoardView view)
+onPieceClicked(int r, int c): void
+selectTower(int r, int c): void
-clearSelection(): void
-computeLegalMoves(): void
-tryMove(int r, int c): void
}
InteractionController --> AvalamBoard
InteractionController --> BoardView
class Main{
+main(String[] args): void
}
Main ..> AvalamWindow
Main ..> GameMode
class PieceButton{
-color: java.awt.Color
-height: int
-hover: boolean
+row: int
+col: int
+PieceButton(java.awt.Color c, int height, int row, int col)
#paintComponent(Graphics g): void
+contains(int x, int y): boolean
}
class PieceLayer{
+PieceLayer()
+displayGrid(Tower[][] grid, int xBase, int yBase, int spacing, int size, java.util.function.BiConsumer<Integer, Integer> clickCallback): void
}
class ScoreView{
-scoreY: JLabel
-scoreR: JLabel
+ScoreView(int y, int r)
+updateScores(int y, int r): void
}
class Tower{
-height: byte
-color: Color
+Tower(int height, Color color)
+createTower(Color color): Tower
+getHeight(): int
+getColor(): Color
+mergeTower(Tower src): void
+toString(): String
}
Tower --> Color
class TurnView{
-text: JLabel
+TurnView(String initial)
+setTurn(String s): void
}
BoardLoader ..> Tower
PieceLayer ..> Tower
PieceButton ..> Tower

View File

@@ -0,0 +1,27 @@
---
title: Bot - Diagramme de classes (complet)
---
classDiagram
class AlphaBetaBot{
-me: Player
-maxDepth: int
-rng: Random
+AlphaBetaBot(Player p, int maxDepth)
+giveYourMove(IBoard board): AbstractPly
-alphaBeta(IBoard board, int depth, int alpha, int beta): int
-terminalValue(IBoard board): int
-evaluate(IBoard board): int
-listMoves(IBoard board): List<AbstractPly>
}
class DivineBot{
}
class IdiotBot{
-rng: Random
+IdiotBot(Player p)
+giveYourMove(IBoard board): AbstractPly
}

View File

@@ -0,0 +1,73 @@
---
title: GameAPI - Diagramme de classes (complet)
---
classDiagram
class AbstractBoard{
<<abstract>>
-currentPlayer: Player
-history: Deque<AbstractPly>
+AbstractBoard(Player p, Deque<AbstractPly> h)
#setNextPlayer(): void
#addPlyToHistory(AbstractPly c): void
#removePlyFromHistory(): AbstractPly
#getLastPlyFromHistory(): AbstractPly
+getCurrentPlayer()
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+iterator(): Iterator<AbstractPly>
+doPly(AbstractPly c): void
+undoPly(): void
+safeCopy(): IBoard
}
class AbstractGame{
<<abstract>>
-currentBoard: IBoard
-mapPlayers: EnumMap<Player, AbstractGamePlayer>
+AbstractGame(IBoard b, EnumMap<Player,AbstractGamePlayer> m)
+run(): Result
}
class AbstractGamePlayer{
<<abstract>>
-iAm: Player
+AbstractGamePlayer(Player p)
+giveYourMove(IBoard p): AbstractPly
}
class AbstractPly{
<<abstract>>
-joueur: Player
+AbstractPly(Player j)
+getPlayer(): Player
}
class IBoard{
+getCurrentPlayer(): Player
+isGameOver(): boolean
+getResult(): Result
+isLegal(AbstractPly c): boolean
+iterator(): Iterator<AbstractPly>
+doPly(AbstractPly c): void
+undoPly(): void
+safeCopy(): IBoard
}
class Player{
<<enumerate>>
PLAYER1
PLAYER2
}
class Result{
<<enumerate>>
WIN
DRAW
LOSS
}

View File

@@ -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 === # === Répertoires ===
SRC_DIR = fr SRC_DIR = fr
BIN_DIR = bin BIN_DIR = bin
@@ -6,18 +11,28 @@ 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
# === Classe de test ===
TEST_CLASS = fr.iut_fbleau.Tests.AvalamBoardTest
# === Commandes Java === # === Commandes Java ===
JC = javac JC = javac
# Compilation normale (application uniquement)
JCFLAGS = -d $(BIN_DIR) 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)
JAVAFLAGS_TESTS = -cp $(TEST_ENV)
# === Règle par défaut === # === Règle par défaut ===
all: build all: build
@@ -31,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..."
@@ -43,6 +87,12 @@ run:
@echo "===> Lancement du jeu Avalam..." @echo "===> Lancement du jeu Avalam..."
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS) @$(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 === # === Nettoyage ===
clean: clean:
@echo "===> Suppression des fichiers compilés..." @echo "===> Suppression des fichiers compilés..."

View File

@@ -4,38 +4,63 @@
Date de création : 16/10/25 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**. 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 :
```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 ```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
@@ -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. 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** \ **Régle de base** \
Chaque joueur choisit sa couleur. 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. 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. 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. 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)** \ **Mouvement interdit (1)** \

View File

@@ -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. *
* * Elle permet :
* @version 1.0 * - 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 ;
* - dafficher, 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 {
@@ -53,14 +58,19 @@ public class ArenaWindow extends JFrame {
JButton startButton = new JButton("Lancer les parties"); JButton startButton = new JButton("Lancer les parties");
startButton.addActionListener(e -> showConfigDialog()); startButton.addActionListener(e -> showConfigDialog());
buttonPanel.add(startButton); buttonPanel.add(startButton);
JButton backButton = new JButton("Retour au menu"); JButton backButton = new JButton("Retour au menu");
backButton.addActionListener(e -> { backButton.addActionListener(e -> {
dispose(); // Ferme la fenêtre Arène dispose(); // Ferme la fenêtre Arène
Main.showModeSelection(); // Affiche le menu principal Main.showModeSelection(); // Affiche le menu principal
}); });
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,55 +192,135 @@ public class ArenaWindow extends JFrame {
* @param nbParties nombre de parties à jouer * @param nbParties nombre de parties à jouer
*/ */
private void runArena(String bot1Type, String bot2Type, int depth, int nbParties) { private void runArena(String bot1Type, String bot2Type, int depth, int nbParties) {
// Créer les bots
AbstractGamePlayer bot1 = createBot(bot1Type, Player.PLAYER1, depth);
AbstractGamePlayer bot2 = createBot(bot2Type, Player.PLAYER2, depth);
if (bot1 == null || bot2 == null) {
JOptionPane.showMessageDialog(this, "Erreur lors de la création des bots.");
return;
}
// Vider le tableau // Vider le tableau
tableModel.setRowCount(0); tableModel.setRowCount(0);
results.clear(); results.clear();
// Charger le plateau initial // Créer un dialogue de progression
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt"); JDialog progressDialog = new JDialog(this, "Parties en cours...", true);
JLabel progressLabel = new JLabel("Préparation des parties...", JLabel.CENTER);
progressLabel.setBorder(BorderFactory.createEmptyBorder(20, 40, 20, 40));
progressDialog.add(progressLabel);
progressDialog.setSize(300, 100);
progressDialog.setLocationRelativeTo(this);
progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
// Lancer les parties // Exécuter les parties dans un thread séparé pour ne pas bloquer l'interface
for (int i = 1; i <= nbParties; i++) { Thread arenaThread = new Thread(() -> {
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1); SwingUtilities.invokeLater(() -> progressDialog.setVisible(true));
ArenaGame game = new ArenaGame(board, bot1, bot2);
try { // Statistiques pour déboguer
Result result = game.run(); int bot1Wins = 0;
String winner = getWinnerName(result, bot1Type, bot2Type); int bot2Wins = 0;
int draws = 0;
// Ajouter au tableau int errors = 0;
tableModel.addRow(new Object[]{
"Partie " + i, for (int i = 1; i <= nbParties; i++) {
bot1Type, final int partieNum = i;
bot2Type, SwingUtilities.invokeLater(() -> {
winner progressLabel.setText("Partie " + partieNum + " / " + nbParties + " en cours...");
});
} catch (Exception e) {
tableModel.addRow(new Object[]{
"Partie " + i,
bot1Type,
bot2Type,
"Erreur: " + e.getMessage()
}); });
// Recréer les bots à chaque partie pour garantir l'indépendance complète
// (notamment pour réinitialiser les générateurs aléatoires)
AbstractGamePlayer bot1 = createBot(bot1Type, Player.PLAYER1, depth);
AbstractGamePlayer bot2 = createBot(bot2Type, Player.PLAYER2, depth);
if (bot1 == null || bot2 == null) {
errors++;
SwingUtilities.invokeLater(() -> {
tableModel.addRow(new Object[]{
"Partie " + partieNum,
bot1Type,
bot2Type,
"Erreur: Impossible de créer les bots"
});
});
continue;
}
// Recharger le plateau à chaque partie pour garantir l'indépendance complète
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1);
ArenaGame game = new ArenaGame(board, bot1, bot2);
try {
Result result = game.run();
String winner = getWinnerName(result, bot1Type, bot2Type);
// Mettre à jour les statistiques
if (result == Result.WIN) {
bot1Wins++;
} else if (result == Result.LOSS) {
bot2Wins++;
} else if (result == Result.DRAW) {
draws++;
}
// Ajouter au tableau dans le thread EDT
SwingUtilities.invokeLater(() -> {
tableModel.addRow(new Object[]{
"Partie " + partieNum,
bot1Type,
bot2Type,
winner
});
});
} catch (Exception e) {
errors++;
SwingUtilities.invokeLater(() -> {
tableModel.addRow(new Object[]{
"Partie " + partieNum,
bot1Type,
bot2Type,
"Erreur: " + e.getMessage()
});
});
}
} }
}
// Afficher les statistiques finales
final int finalBot1Wins = bot1Wins;
final int finalBot2Wins = bot2Wins;
final int finalDraws = draws;
final int finalErrors = errors;
// Afficher un message de fin // Fermer le dialogue et afficher le message de fin avec statistiques
JOptionPane.showMessageDialog( SwingUtilities.invokeLater(() -> {
this, progressDialog.dispose();
"Toutes les parties sont terminées !",
"Arène terminée", String statsMessage = String.format(
JOptionPane.INFORMATION_MESSAGE "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;
} }

View File

@@ -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;

View File

@@ -1,8 +1,7 @@
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;
import fr.iut_fbleau.GameAPI.Player; import fr.iut_fbleau.GameAPI.Player;
@@ -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)
* - laffichage du score (ScoreView) * - joueur vs bot idiot (PVBOT)
* - laffichage 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 : * - laffichage du score (<code>ScoreView</code>)
* - laffichage 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,16 +64,8 @@ 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;
// A FAIRE PLUS TARD (PVGOD) /** Bot Divin (utilisé si mode PVGOD). */
// /** Bot Divin (utilisé si mode PVGOD). */ private final DivineBot divineBot;
// 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.
*/
private final Object divineBot = null;
/** 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;
@@ -86,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);
@@ -94,23 +91,24 @@ 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
*/ */
public AvalamWindow(GameMode mode, int alphaDepth) { public AvalamWindow(GameMode mode, int alphaDepth) {
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);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
@@ -168,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;
} }
@@ -219,12 +219,7 @@ 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.
if (mode == GameMode.PVGOD) return;
botAnimating = true; botAnimating = true;
@@ -238,9 +233,7 @@ 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());
botMove = null;
} }
if (botMove == null) { if (botMove == null) {

View File

@@ -0,0 +1,37 @@
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>BackgroundLayer</code>
*
* Sous composant de BoardView affichant limage de fond.
*/
public class BackgroundLayer extends JComponent {
private Image img;
/**
* Construit une couche de fond.
*
* @param resourcePath chemin de l'image de fond
*/
public BackgroundLayer(String resourcePath) {
img = Toolkit.getDefaultToolkit().getImage(
getClass().getClassLoader().getResource(resourcePath)
);
}
/**
* Dessine l'image de fond.
*
* @param g contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
}
}
}

View File

@@ -61,10 +61,10 @@ public class BoardLoader {
switch (value) { switch (value) {
case 1: case 1:
grid[row][col] = new Tower(Color.YELLOW); grid[row][col] = Tower.createTower(Color.YELLOW);
break; break;
case 2: case 2:
grid[row][col] = new Tower(Color.RED); grid[row][col] = Tower.createTower(Color.RED);
break; break;
default: default:
grid[row][col] = null; grid[row][col] = null;

View File

@@ -1,192 +1,159 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
/** /**
* La classe <code>BoardView</code> * La classe <code>BoardView</code>
* *
* Représente la vue principale du plateau Avalam. * Représente la vue principale du plateau Avalam.
* Elle gère : * Elle gère :
* - laffichage des tours (PieceLayer) * - laffichage des tours (PieceLayer)
* - laffichage des coups possibles (HighlightLayer) * - laffichage des coups possibles (HighlightLayer)
* - laffichage du fond graphique * - laffichage du fond graphique (BackgroundLayer)
* - les clics via InteractionController * - les clics via InteractionController
* *
* Cette classe ne contient aucune logique de règles du jeu. * Cette classe ne contient aucune logique de règles du jeu.
* *
* @version 1.0 * @version 1.0
* Date : * Date :
* Licence : * Licence :
*/ */
public class BoardView extends JLayeredPane { public class BoardView extends JLayeredPane {
//Attributs //Attributs
/** Référence au moteur Avalam. */ /** Référence au moteur Avalam. */
private AvalamBoard board; private AvalamBoard board;
/** Couche daffichage du fond. */ /** Couche daffichage du fond. */
private BackgroundLayer backgroundLayer; private BackgroundLayer backgroundLayer;
/** Couche daffichage des coups possibles. */ /** Couche daffichage des coups possibles. */
private HighlightLayer highlightLayer; private HighlightLayer highlightLayer;
/** Couche daffichage des pièces. */ /** Couche daffichage des pièces. */
private PieceLayer pieceLayer; private PieceLayer pieceLayer;
/** Contrôleur des interactions. */ /** Contrôleur des interactions. */
private InteractionController controller; private InteractionController controller;
/** Taille dun pion en pixels. */ /** Taille dun pion en pixels. */
private final int size = 50; private final int size = 50;
/** Espacement entre les cases. */ /** Espacement entre les cases. */
private final int spacing = 70; private final int spacing = 70;
/** Décalage horizontal du plateau. */ /** Décalage horizontal du plateau. */
private final int xBase = 60; private final int xBase = 60;
/** Décalage vertical du plateau. */ /** Décalage vertical du plateau. */
private final int yBase = 60; private final int yBase = 60;
/** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */ /** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */
private Runnable boardUpdateCallback; private Runnable boardUpdateCallback;
//Constructeur //Constructeur
/** /**
* Construit la vue du plateau. * Construit la vue du plateau.
* *
* @param board moteur du jeu * @param board moteur du jeu
* @param boardUpdateCallback callback à appeler après un coup * @param boardUpdateCallback callback à appeler après un coup
*/ */
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) { public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
this.board = board; this.board = board;
this.boardUpdateCallback = boardUpdateCallback; this.boardUpdateCallback = boardUpdateCallback;
setLayout(null); setLayout(null);
// Contrôleur // Contrôleur
this.controller = new InteractionController(board, this); this.controller = new InteractionController(board, this);
// Couche fond // Couche fond
backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png"); backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png");
backgroundLayer.setBounds(0, 0, 725, 725); backgroundLayer.setBounds(0, 0, 725, 725);
add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER); add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER);
// Couche highlight // Couche highlight
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size); highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
add(highlightLayer, JLayeredPane.DEFAULT_LAYER); add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
// Couche pièces // Couche pièces
pieceLayer = new PieceLayer(); pieceLayer = new PieceLayer();
add(pieceLayer, JLayeredPane.PALETTE_LAYER); add(pieceLayer, JLayeredPane.PALETTE_LAYER);
setPreferredSize(new Dimension(725, 725)); setPreferredSize(new Dimension(725, 725));
refresh(); refresh();
} }
//Méthodes //Méthodes
/** /**
* Retourne le contrôleur d'interactions (utile pour simuler les clics du bot). * Retourne le contrôleur d'interactions (utile pour simuler les clics du bot).
* *
* @return contrôleur * @return contrôleur
*/ */
public InteractionController getController() { public InteractionController getController() {
return controller; return controller;
} }
/** /**
* Active/désactive les interactions utilisateur sur le plateau. * Active/désactive les interactions utilisateur sur le plateau.
* Utile pendant l'animation du bot pour éviter des clics concurrents. * Utile pendant l'animation du bot pour éviter des clics concurrents.
* *
* @param enabled true pour activer, false pour désactiver * @param enabled true pour activer, false pour désactiver
*/ */
public void setInteractionEnabled(boolean enabled) { public void setInteractionEnabled(boolean enabled) {
// Désactive la couche des pièces (boutons) principalement // Désactive la couche des pièces (boutons) principalement
pieceLayer.setEnabled(enabled); pieceLayer.setEnabled(enabled);
for (Component c : pieceLayer.getComponents()) { for (Component c : pieceLayer.getComponents()) {
c.setEnabled(enabled); c.setEnabled(enabled);
} }
} }
/** /**
* Appelé par le contrôleur après un coup. * Appelé par le contrôleur après un coup.
*/ */
public void onBoardUpdated() { public void onBoardUpdated() {
if (boardUpdateCallback != null) { if (boardUpdateCallback != null) {
boardUpdateCallback.run(); boardUpdateCallback.run();
} }
} }
/** /**
* Rafraîchit les couches visuelles. * Rafraîchit les couches visuelles.
*/ */
public void refresh() { public void refresh() {
pieceLayer.displayGrid( pieceLayer.displayGrid(
boardGrid(), boardGrid(),
xBase, yBase, spacing, size, xBase, yBase, spacing, size,
(r, c) -> controller.onPieceClicked(r, c) (r, c) -> controller.onPieceClicked(r, c)
); );
highlightLayer.setLegalMoves(controller.getLegalMoves()); highlightLayer.setLegalMoves(controller.getLegalMoves());
backgroundLayer.repaint(); backgroundLayer.repaint();
highlightLayer.repaint(); highlightLayer.repaint();
pieceLayer.repaint(); pieceLayer.repaint();
repaint(); repaint();
} }
/** /**
* Récupère la grille depuis le moteur. * Récupère la grille depuis le moteur.
* *
* @return grille 9x9 de tours * @return grille 9x9 de tours
*/ */
private Tower[][] boardGrid() { private Tower[][] boardGrid() {
Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE]; Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
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++) {
grid[r][c] = board.getTowerAt(r, c); grid[r][c] = board.getTowerAt(r, c);
} }
} }
return grid; return grid;
} }
}
//Affichage
/**
* Composant affichant limage de fond.
*/
private static class BackgroundLayer extends JComponent {
private Image img;
/**
* Construit une couche de fond.
*
* @param resourcePath chemin de l'image de fond
*/
public BackgroundLayer(String resourcePath) {
img = Toolkit.getDefaultToolkit().getImage(
getClass().getClassLoader().getResource(resourcePath)
);
}
/**
* Dessine l'image de fond.
*
* @param g contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
}
}
}
}

View 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 lapplication.
*/
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 linformation et le « Rejouer »)
* @param depth profondeur utilisée (pour les modes avec bot intelligent)
* @param onReplay action à exécuter lorsque lutilisateur clique sur « Rejouer »
* @param onMenu action à exécuter lorsque lutilisateur clique sur « Menu principal »
* @param onQuit action à exécuter lorsque lutilisateur 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();
}
}
}

View File

@@ -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"
}; };

View File

@@ -9,33 +9,21 @@ package fr.iut_fbleau.Avalam;
* - une hauteur (nombre de pions empilés) * - une hauteur (nombre de pions empilés)
* *
* Cette version est volontairement compatible avec le reste du projet : * Cette version est volontairement compatible avec le reste du projet :
* - constructeur Tower(Color) utilisé par BoardLoader
* - constructeur Tower(int, Color) utilisé dans d'autres parties possibles * - constructeur Tower(int, Color) utilisé dans d'autres parties possibles
* - usine createTower(Color) utilisé par BoardLoader
* - méthode mergeTower(Tower) utilisée par AvalamBoard * - méthode mergeTower(Tower) utilisée par AvalamBoard
* - méthode merge(Tower) conservée (si elle est utilisée ailleurs)
*/ */
public class Tower { public class Tower {
//Attributs //Attributs
/** Hauteur de la tour (nombre de pions empilés). */ /** Hauteur de la tour (nombre de pions empilés). */
private int height; private byte height;
/** Couleur du sommet de la tour (propriétaire actuel). */ /** Couleur du sommet de la tour (propriétaire actuel). */
private Color color; private Color color;
//Constructeur //Constructeur
/**
* Construit une tour de hauteur 1 avec la couleur donnée.
* (Constructeur attendu par BoardLoader dans ton projet.)
*
* @param color couleur du sommet
*/
public Tower(Color color) {
this(1, color);
}
/** /**
* Construit une tour avec une hauteur et une couleur données. * Construit une tour avec une hauteur et une couleur données.
* *
@@ -43,10 +31,20 @@ public class Tower {
* @param color couleur du sommet * @param color couleur du sommet
*/ */
public Tower(int height, Color color) { public Tower(int height, Color color) {
this.height = height; this.height = (byte) height;
this.color = color; this.color = color;
} }
/**
* Construit une tour de hauteur 1 avec la couleur donnée.
* (Constructeur attendu par BoardLoader dans le projet.)
*
* @param color couleur du sommet
*/
public static Tower createTower(Color color) {
return new Tower(1, color);
}
//Méthodes //Méthodes
/** /**
@@ -73,20 +71,11 @@ public class Tower {
* *
* @param src tour source empilée sur la destination * @param src tour source empilée sur la destination
*/ */
public void merge(Tower src) { public void mergeTower(Tower src) {
this.height += src.height; this.height += src.height;
this.color = src.color; this.color = src.color;
} }
/**
* Alias de merge() pour compatibilité avec d'autres classes.
*
* @param src tour source empilée sur la destination
*/
public void mergeTower(Tower src) {
merge(src);
}
//Affichage //Affichage
/** /**

View File

@@ -1,22 +1,201 @@
package fr.iut_fbleau.Bot; package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.AvalamBoard; import fr.iut_fbleau.Avalam.*;
import fr.iut_fbleau.Avalam.AvalamPly; import fr.iut_fbleau.GameAPI.*;
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.*; import java.util.*;
/** /**
* Bot "Divin" (fort) pour Avalam. * Bot "Divin" (alpha-beta + évaluateur pondéré).
* * * Idée :
* * - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
* Objectif : trop fort. */ * - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
public class DivineBot{ */
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.
*
* @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);
}
// 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;
List<AbstractPly> moves = listMoves(board);
if (moves.isEmpty()) {
return evaluate(board);
}
if (isMax) {
int best = Integer.MIN_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
best = Math.max(best, val);
alpha = Math.max(alpha, best);
if (alpha >= beta) break; // Coupure Beta
}
return best;
} else {
int best = Integer.MAX_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
best = Math.min(best, val);
beta = Math.min(beta, best);
if (alpha >= beta) break; // Coupure Alpha
}
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.
*/
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;
}
}

View 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));
}
}