36 Commits

Author SHA1 Message Date
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
felix-vi
b4f93e7647 j'avais oublié de faire la javadoc 2026-02-01 17:24:43 +01:00
felix-vi
be459707ce commit rapide car j'avais déjà le code mais que je n'avais pas push le code d'avant 2026-01-31 14:28:05 +01:00
felix-vi
d907d2d52b ajout du mode Arene où on choisi les bot qui s'affronte et le nombre de partie 2026-01-31 14:26:23 +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
aba891e060 bot alpha-beta + correctif + debut bot divin 2026-01-29 13:34:40 -05:00
27663cd583 Merge pull request 'Menu game mode + bot idiot' (#17) from bot into master
Reviewed-on: #17
2026-01-26 13:43:10 +01:00
031b23c7c5 Menu game mode + bot idiot 2026-01-26 13:41:48 +01:00
b97b9cef69 nettoyage + lisibilité code + changement structure 2026-01-26 06:39:49 -05:00
25f8dcdc76 Fond avalam board sans .class 2025-11-27 14:06:21 -05:00
823b0fcae0 Fond avalam board 2025-11-27 14:06:05 -05:00
9f78dad6e5 Téléverser les fichiers vers "fr/iut_fbleau/Res" 2025-11-27 20:01:28 +01:00
830729ca21 RM temp AvalamWindow 2025-11-27 13:10:03 +01:00
d95d52785e Merge pull request 'Maj BoardLoader + Makefile' (#14) from MecaniqueJeu into master
Reviewed-on: #14
2025-11-27 13:07:55 +01:00
ddf9b00c0a Maj BoardLoader + makefile - .class 2025-11-27 13:06:32 +01:00
c1e5de9ed2 Maj BoardLoader + makefile 2025-11-27 13:06:14 +01:00
1921b523c6 Jeu jouable avec fin de parti 2025-11-25 16:02:23 -05:00
d23aeb266f Merge pull request 'Regle_coup' (#12) from Regle_coup into master
Reviewed-on: #12
2025-11-24 13:01:18 +01:00
39 changed files with 3031 additions and 1253 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,35 +1,98 @@
# === Environnements ===
JUNIT_JAR = /usr/share/java/junit.jar
HAMCREST_JAR = /usr/share/java/hamcrest-core.jar
TEST_ENV = "bin:$(JUNIT_JAR):$(HAMCREST_JAR)"
# === Répertoires ===
SRC_DIR = fr
BIN_DIR = bin
# === Recherche automatique des fichiers .java dans tous les sous-dossiers ===
SOURCES := $(shell find $(SRC_DIR) -name "*.java")
# === Répertoires des ressources ===
RES_SRC = fr/iut_fbleau/Res
RES_BIN = bin/fr/iut_fbleau/Res
# === Recherche automatique des fichiers .java ===
# SOURCES : uniquement le code de l'application (sans les fichiers de tests)
SOURCES := $(shell find $(SRC_DIR) -name "*.java" -not -path "$(SRC_DIR)/iut_fbleau/Tests/*")
# TEST_SOURCES : uniquement les fichiers de tests
TEST_SOURCES := $(shell find $(SRC_DIR)/iut_fbleau/Tests -name "*.java" 2>/dev/null)
# === Classe principale ===
MAIN_CLASS = fr.iut_fbleau.Avalam.Main
# === Classe de test ===
TEST_CLASS = fr.iut_fbleau.Tests.AvalamBoardTest
# === Commandes Java ===
JC = javac
# Compilation normale (application uniquement)
JCFLAGS = -d $(BIN_DIR)
# Compilation des tests (application + JUnit)
JCFLAGS_TESTS = -d $(BIN_DIR) -cp $(TEST_ENV)
JAVA = java
JAVAFLAGS = -cp $(BIN_DIR)
JAVAFLAGS_TESTS = -cp $(TEST_ENV)
# === Règle par défaut ===
all: build
# === Compilation ===
build:
build: compile resources
@echo "✔ Compilation terminée."
compile:
@echo "===> Compilation du projet Avalam..."
@mkdir -p $(BIN_DIR)
@$(JC) $(JCFLAGS) $(SOURCES)
@echo "✔ Compilation terminée."
compile_tests: compile
@echo "===> Compilation des tests..."
@mkdir -p $(BIN_DIR)
ifneq ($(TEST_SOURCES),)
@$(JC) $(JCFLAGS_TESTS) $(TEST_SOURCES)
else
@echo "Aucun fichier de test trouvé dans $(SRC_DIR)/iut_fbleau/Tests"
endif
# === Vérification / installation des dépendances de tests ===
check_test_deps:
@echo "===> Vérification des dépendances de tests (JUnit / Hamcrest)..."
@if [ ! -f "$(JUNIT_JAR)" ] || [ ! -f "$(HAMCREST_JAR)" ]; then \
echo " JUnit ou Hamcrest manquant, tentative d'installation (sudo requis)..."; \
if command -v sudo >/dev/null 2>&1; then \
sudo apt-get update && sudo apt-get install -y junit4 libhamcrest-java; \
else \
apt-get update && apt-get install -y junit4 libhamcrest-java; \
fi; \
if [ ! -f "$(JUNIT_JAR)" ] || [ ! -f "$(HAMCREST_JAR)" ]; then \
echo "✖ Impossible de trouver/installer $(JUNIT_JAR) ou $(HAMCREST_JAR). Vérifiez manuellement vos paquets JUnit/Hamcrest."; \
exit 1; \
else \
echo "✔ Dépendances de tests installées."; \
fi; \
else \
echo "✔ JUnit et Hamcrest trouvés."; \
fi
# === Copie des ressources (.txt) dans bin ===
resources:
@echo "===> Copie des ressources..."
@mkdir -p $(RES_BIN)
@cp $(RES_SRC)/* $(RES_BIN)/
@echo "✔ Ressources copiées."
# === Exécution ===
run:
@echo "===> Lancement du jeu Avalam..."
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
# === Tests ===
test: check_test_deps compile_tests
@echo "===> Lancement des tests..."
@$(JAVA) $(JAVAFLAGS_TESTS) org.junit.runner.JUnitCore $(TEST_CLASS)
@echo "... Fin des tests."
# === Nettoyage ===
clean:
@echo "===> Suppression des fichiers compilés..."

View File

@@ -4,38 +4,63 @@
Date de création : 16/10/25
Le but est de mettre en oeuvre des jeux plus intéressants que le jeu de Nim, toujours en suivant l'API.
Date de rendu : 06/02/26
Dans un second temps, on fera des bots le plus efficace possible (probablement un alpha beta avec cut-off plus fonction d'évaluation qui peut être faite à la main ou par MonteCarlo).
Le but est de mettre en oeuvre un jeu plus intéressants que le jeu de Nim, en suivant l'API de Florent Madelaine.
Dans un second temps, nous développerons des bots les plus efficaces possible, probablement en utilisant un algorithme alpha-bêta avec cut-off et une fonction d'évaluation réalisé à la main ou par MonteCarlo.
Le jeu de notre groupe est **Avalam**.
## Compilation et exécution
## Compilation, exécution et tests
### Compilation
### Compilation (sans tests)
```bash
make build
```
ou simplement :
```bash
make
```
Cette commande :
- compile uniquement le code de l'application (sans les fichiers du dossier `fr/iut_fbleau/Tests`) ;
- copie les ressources dans `bin/`.
### Exécution
### Exécution du jeu
```bash
make run
```
Lance la fenêtre de jeu Avalam après compilation.
### Lancer les tests
```bash
make test
```
Cette commande :
- vérifie d'abord la présence de **JUnit** et **Hamcrest** dans `/usr/share/java` ;
- si nécessaire, tente de les installer automatiquement via `apt-get` (sudo requis sur Debian/Ubuntu) ;
- compile ensuite les fichiers de tests (`fr/iut_fbleau/Tests`) ;
- lance enfin la suite de tests JUnit (`AvalamBoardTest`).
Si l'installation automatique échoue (autre OS, pas de droits sudo, pas d'accès réseau, etc.), un message l'indiquera et il faudra installer JUnit/Hamcrest manuellement.
### Nettoyage
```bash
make clean
```
Supprime le répertoire `bin/` (classes compilées et ressources copiées).
### Recompiler et exécuter
### Recompiler puis exécuter
```bash
make re
```
Équivaut à `make clean` puis `make build` puis `make run`.
### Générer la Javadoc
```bash
make javadoc
```
Génère la documentation dans le dossier `doc/`.
## Architecture du projet
@@ -78,13 +103,13 @@ Le plateau est représenté par une grille 9x9 où chaque case contient :
Sur un plateau de jeu, les joueurs disposent de 24 pions chacun. Le but est de créer de petites tours avec son pion de couleur au-dessus. Pour ce faire, chaque joueur déplace son pion sur une tour ou déplace une tour sur un autre pion. La personne ayant le plus de pions sur le dessus des tours gagne.
### Régles officiel :
### Règles officielles :
**Régle de base** \
Chaque joueur choisit sa couleur.
Le but du jeu est de constituer un maximum de tours de 1 à 5 pions, jamais plus, surmontées par un pion de sa couleur.
Un joueur est le propriétaire d'une tour lorsqu'un pion de sa couleur en occupe le sommet. Un pion isolé constitue également une tour.
**Deplacements** \
**Déplacements** \
Chaque joueur en effectue un seul mouvement, dans n'importe quel sens (horizontal, vertical, diagonal) avec n'importe quel pion (ou pile de pions), quelle qu'en soit la couleur. Ce mouvement consiste à empiler le ou les pions déplacés sur un trou directement voisin déjà occupé par un ou plusieurs pions.
**Mouvement interdit (1)** \

View File

@@ -0,0 +1,44 @@
package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.GameAPI.AbstractGame;
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
import fr.iut_fbleau.GameAPI.IBoard;
import fr.iut_fbleau.GameAPI.Player;
import java.util.EnumMap;
/**
* Classe pour jouer une partie entre deux bots sans interface graphique.
* Utilisée dans le mode Arène.
*
* @version 1.0
*/
public class ArenaGame extends AbstractGame {
/**
* Construit une partie Arène entre deux bots.
*
* @param board plateau initial
* @param bot1 bot pour PLAYER1
* @param bot2 bot pour PLAYER2
*/
public ArenaGame(IBoard board, AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
super(board, createPlayerMap(bot1, bot2));
}
/**
* Crée la map des joueurs pour AbstractGame.
*
* @param bot1 bot pour PLAYER1
* @param bot2 bot pour PLAYER2
* @return une EnumMap associant chaque Player à son bot
*/
private static EnumMap<Player, AbstractGamePlayer> createPlayerMap(
AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
EnumMap<Player, AbstractGamePlayer> map = new EnumMap<>(Player.class);
map.put(Player.PLAYER1, bot1);
map.put(Player.PLAYER2, bot2);
return map;
}
}

View File

@@ -0,0 +1,292 @@
package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Bot.AlphaBetaBot;
import fr.iut_fbleau.Bot.DivineBot;
import fr.iut_fbleau.Bot.IdiotBot;
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* Fenêtre pour le mode Arène (bots vs bots).
*
* 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 ;
* - 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 {
/** Tableau affichant les résultats des parties. */
private JTable resultsTable;
/** Modèle de données pour le tableau des résultats. */
private DefaultTableModel tableModel;
/** Liste des résultats des parties (non utilisée actuellement). */
private List<String> results;
/**
* Construit la fenêtre Arène.
*/
public ArenaWindow() {
super("Arène - Bot vs Bot");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(new BorderLayout());
results = new ArrayList<>();
// Panneau de configuration
JPanel configPanel = createConfigPanel();
add(configPanel, BorderLayout.NORTH);
// Tableau des résultats
createResultsTable();
JScrollPane scrollPane = new JScrollPane(resultsTable);
add(scrollPane, BorderLayout.CENTER);
// Panneau des boutons
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton startButton = new JButton("Lancer les parties");
startButton.addActionListener(e -> showConfigDialog());
buttonPanel.add(startButton);
JButton backButton = new JButton("Retour au menu");
backButton.addActionListener(e -> {
dispose(); // Ferme la fenêtre Arène
Main.showModeSelection(); // Affiche le menu principal
});
buttonPanel.add(backButton);
// 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);
pack();
setSize(600, 400);
setLocationRelativeTo(null);
setVisible(true);
}
/**
* Crée le panneau de configuration (pour l'instant vide, sera rempli par le dialogue).
*
* @return un JPanel contenant les informations de configuration
*/
private JPanel createConfigPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder("Configuration"));
panel.add(new JLabel("Utilisez le bouton 'Lancer les parties' pour configurer et démarrer."));
return panel;
}
/**
* Crée le tableau des résultats avec les colonnes : Partie, Bot 1, Bot 2, Gagnant.
*/
private void createResultsTable() {
String[] columnNames = {"Partie", "Bot 1", "Bot 2", "Gagnant"};
tableModel = new DefaultTableModel(columnNames, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
resultsTable = new JTable(tableModel);
resultsTable.setFillsViewportHeight(true);
}
/**
* Affiche le dialogue de configuration et lance les parties.
*/
private void showConfigDialog() {
// Choix du bot 1
String[] botTypes = {"Bot Idiot", "Bot Alpha-Beta", "Bot Divin"};
String bot1Choice = (String) JOptionPane.showInputDialog(
this,
"Choisissez le Bot 1 (Joueur 1) :",
"Configuration Arène - Bot 1",
JOptionPane.QUESTION_MESSAGE,
null,
botTypes,
botTypes[0]
);
if (bot1Choice == null) return;
// Choix du bot 2
String bot2Choice = (String) JOptionPane.showInputDialog(
this,
"Choisissez le Bot 2 (Joueur 2) :",
"Configuration Arène - Bot 2",
JOptionPane.QUESTION_MESSAGE,
null,
botTypes,
botTypes[0]
);
if (bot2Choice == null) return;
// Profondeur pour Alpha-Beta et Divin
int depth = 4;
if (bot1Choice.contains("Alpha") || bot1Choice.contains("Divin") ||
bot2Choice.contains("Alpha") || bot2Choice.contains("Divin")) {
String depthStr = JOptionPane.showInputDialog(
this,
"Profondeur de recherche pour les bots Alpha-Beta/Divin ?\n(Conseil: 4)",
"Profondeur",
JOptionPane.QUESTION_MESSAGE
);
if (depthStr != null) {
try {
depth = Integer.parseInt(depthStr.trim());
if (depth < 1) depth = 1;
} catch (Exception e) {
depth = 4;
}
}
}
// Nombre de parties
String nbPartiesStr = JOptionPane.showInputDialog(
this,
"Combien de parties voulez-vous jouer ?",
"Nombre de parties",
JOptionPane.QUESTION_MESSAGE
);
if (nbPartiesStr == null) return;
int nbParties;
try {
nbParties = Integer.parseInt(nbPartiesStr.trim());
if (nbParties < 1) {
JOptionPane.showMessageDialog(this, "Le nombre de parties doit être au moins 1.");
return;
}
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Nombre invalide.");
return;
}
// Lancer les parties
runArena(bot1Choice, bot2Choice, depth, nbParties);
}
/**
* Lance les parties entre les deux bots.
*
* @param bot1Type type du bot 1 (Joueur 1)
* @param bot2Type type du bot 2 (Joueur 2)
* @param depth profondeur de recherche pour les bots Alpha-Beta/Divin
* @param nbParties nombre de parties à jouer
*/
private void runArena(String bot1Type, String bot2Type, int depth, int nbParties) {
// Créer les bots
AbstractGamePlayer bot1 = createBot(bot1Type, Player.PLAYER1, depth);
AbstractGamePlayer bot2 = createBot(bot2Type, Player.PLAYER2, depth);
if (bot1 == null || bot2 == null) {
JOptionPane.showMessageDialog(this, "Erreur lors de la création des bots.");
return;
}
// Vider le tableau
tableModel.setRowCount(0);
results.clear();
// Charger le plateau initial
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
// Lancer les parties
for (int i = 1; i <= nbParties; i++) {
AvalamBoard board = new AvalamBoard(initialGrid, Player.PLAYER1);
ArenaGame game = new ArenaGame(board, bot1, bot2);
try {
Result result = game.run();
String winner = getWinnerName(result, bot1Type, bot2Type);
// Ajouter au tableau
tableModel.addRow(new Object[]{
"Partie " + i,
bot1Type,
bot2Type,
winner
});
} catch (Exception e) {
tableModel.addRow(new Object[]{
"Partie " + i,
bot1Type,
bot2Type,
"Erreur: " + e.getMessage()
});
}
}
// Afficher un message de fin avec possibilité de quitter directement
Object[] options = {"OK", "Quitter le jeu"};
int choice = JOptionPane.showOptionDialog(
this,
"Toutes les parties sont terminées !",
"Arène terminée",
JOptionPane.DEFAULT_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null,
options,
options[0]
);
if (choice == 1) {
System.exit(0);
}
}
/**
* Crée un bot selon son type.
*
* @param botType type de bot ("Bot Idiot", "Bot Alpha-Beta", "Bot Divin")
* @param player joueur contrôlé par ce bot (PLAYER1 ou PLAYER2)
* @param depth profondeur de recherche pour Alpha-Beta/Divin
* @return une instance de AbstractGamePlayer correspondant au type, ou null si le type est invalide
*/
private AbstractGamePlayer createBot(String botType, Player player, int depth) {
if (botType.equals("Bot Idiot")) {
return new IdiotBot(player);
} else if (botType.equals("Bot Alpha-Beta")) {
return new AlphaBetaBot(player, depth);
} else if (botType.equals("Bot Divin")) {
return new DivineBot(player, depth);
}
return null;
}
/**
* Détermine le nom du gagnant selon le résultat.
*
* @param result résultat de la partie (WIN, LOSS, ou DRAW du point de vue de PLAYER1)
* @param bot1Type nom du bot 1
* @param bot2Type nom du bot 2
* @return une chaîne indiquant le gagnant ou "Match nul"
*/
private String getWinnerName(Result result, String bot1Type, String bot2Type) {
if (result == Result.WIN) {
return bot1Type + " (Joueur 1)";
} else if (result == Result.LOSS) {
return bot2Type + " (Joueur 2)";
} else {
return "Match nul";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,126 +4,101 @@ import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player;
/**
* Représente un coup dans le jeu Avalam.
* Un coup consiste à déplacer une tour de la position (xFrom, yFrom)
* vers la position (xTo, yTo).
*
* @author AMARY Aurelien, DICK Adrien, FELIX-VIMALARATNAM Patrick, RABAN Hugo
* @version 1.0
*/
* La classe <code>AvalamPly</code>
*
* Représente un coup (ply) dans le jeu Avalam.
* Un coup est défini par :
* - un joueur (PLAYER1 ou PLAYER2)
* - une position source (xFrom, yFrom)
* - une position destination (xTo, yTo)
*
* Cette classe ne vérifie pas la légalité : tout est délégué à <code>AvalamBoard</code>.
*/
public class AvalamPly extends AbstractPly {
/** Coordonnée X de la case source */
private int xFrom;
/** Coordonnée Y de la case source */
private int yFrom;
/** Coordonnée X de la case destination */
private int xTo;
/** Coordonnée Y de la case destination */
private int yTo;
/** Hauteur de la tour source (utilisée pour l'annulation de coup) */
private int sourceHeight;
//Attributs
/** Coordonnée ligne de la case source. */
private final int xFrom;
/** Coordonnée colonne de la case source. */
private final int yFrom;
/** Coordonnée ligne de la case destination. */
private final int xTo;
/** Coordonnée colonne de la case destination. */
private final int yTo;
//Constructeur
/**
* Constructeur par défaut d'un coup Avalam.
* La hauteur de la source sera définie automatiquement lors de l'application du coup.
*
* @param joueur Le joueur qui effectue le coup
* @param xFrom Coordonnée X de la case source
* @param yFrom Coordonnée Y de la case source
* @param xTo Coordonnée X de la case destination
* @param yTo Coordonnée Y de la case destination
*/
public AvalamPly(Player joueur, int xFrom, int yFrom, int xTo, int yTo) {
super(joueur);
* Construit un coup Avalam.
*
* @param player joueur qui joue le coup
* @param xFrom ligne d'origine
* @param yFrom colonne d'origine
* @param xTo ligne de destination
* @param yTo colonne de destination
*/
public AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo) {
super(player);
this.xFrom = xFrom;
this.yFrom = yFrom;
this.xTo = xTo;
this.yTo = yTo;
this.sourceHeight = -1; // Sera défini lors du doPly
}
/**
* Constructeur avec hauteur de la source spécifiée.
* Utilisé principalement pour l'annulation de coups.
*
* @param joueur Le joueur qui effectue le coup
* @param xFrom Coordonnée X de la case source
* @param yFrom Coordonnée Y de la case source
* @param xTo Coordonnée X de la case destination
* @param yTo Coordonnée Y de la case destination
* @param sourceHeight Hauteur de la tour source avant le déplacement
*/
public AvalamPly(Player joueur, int xFrom, int yFrom, int xTo, int yTo, int sourceHeight) {
super(joueur);
this.xFrom = xFrom;
this.yFrom = yFrom;
this.xTo = xTo;
this.yTo = yTo;
this.sourceHeight = sourceHeight;
}
//Méthodes
/**
* Retourne la coordonnée X de la case source.
*
* @return La coordonnée X de la case source
*/
* Retourne la ligne d'origine.
*
* @return ligne source
*/
public int getXFrom() {
return xFrom;
}
/**
* Retourne la coordonnée Y de la case source.
*
* @return La coordonnée Y de la case source
*/
* Retourne la colonne d'origine.
*
* @return colonne source
*/
public int getYFrom() {
return yFrom;
}
/**
* Retourne la coordonnée X de la case destination.
*
* @return La coordonnée X de la case destination
*/
* Retourne la ligne de destination.
*
* @return ligne destination
*/
public int getXTo() {
return xTo;
}
/**
* Retourne la coordonnée Y de la case destination.
*
* @return La coordonnée Y de la case destination
*/
* Retourne la colonne de destination.
*
* @return colonne destination
*/
public int getYTo() {
return yTo;
}
/**
* Retourne la hauteur de la tour source.
*
* @return La hauteur de la tour source, ou -1 si non définie
*/
public int getSourceHeight() {
return sourceHeight;
}
//Affichage
/**
* Définit la hauteur de la tour source.
* Cette méthode est appelée automatiquement lors de l'application du coup.
*
* @param height La hauteur de la tour source
*/
public void setSourceHeight(int height) {
this.sourceHeight = height;
}
* Retourne une représentation textuelle du coup.
*
* @return chaîne décrivant le coup
*/
@Override
public String toString() {
return "AvalamPly{" +
"joueur=" + getPlayer() +
", from=(" + xFrom + "," + yFrom + ")" +
", to=(" + xTo + "," + yTo + ")" +
"player=" + getPlayer() +
", (" + xFrom + "," + yFrom + ") -> (" + xTo + "," + yTo + ")" +
'}';
}
}

View File

@@ -1,42 +1,313 @@
package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Avalam.logic.*;
import fr.iut_fbleau.Avalam.ui.*;
import fr.iut_fbleau.Bot.AlphaBetaBot;
import fr.iut_fbleau.Bot.DivineBot;
import fr.iut_fbleau.Bot.IdiotBot;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result;
import javax.swing.*;
import java.awt.*;
/**
* Fenêtre principale du jeu Avalam.
* La classe <code>AvalamWindow</code>
*
* Fenêtre principale (interface graphique) du jeu Avalam pour tous les modes
* hors Arène :
* - joueur vs joueur (PVP)
* - joueur vs bot idiot (PVBOT)
* - joueur vs bot alpha (PVALPHA)
* - joueur vs bot divin (PVGOD)
*
* Elle contient :
* - le plateau (<code>BoardView</code>)
* - 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 {
//Attributs
/** Moteur du jeu (état + règles). */
private AvalamBoard board;
/** Vue affichant le score. */
private ScoreView scoreView;
/** Vue affichant le joueur courant. */
private TurnView turnView;
/** Vue affichant le plateau. */
private BoardView boardView;
/** Mode de jeu sélectionné. */
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). */
private final Player botPlayer = Player.PLAYER2;
/** Bot idiot (utilisé si mode PVBOT). */
private final IdiotBot idiotBot;
/** Bot Alpha-Beta (utilisé si mode PVALPHA). */
private final AlphaBetaBot alphaBot;
/** Bot Divin (utilisé si mode PVGOD). */
private final DivineBot divineBot;
/** Indique si une animation de tour de bot est en cours. */
private boolean botAnimating = false;
//Constructeur
/**
* Construit la fenêtre en mode joueur vs joueur.
*/
public AvalamWindow() {
super("Avalam - Plateau Graphique");
this(GameMode.PVP, 4);
}
/**
* Construit la fenêtre en fonction du mode choisi.
* Pour PVALPHA/PVGOD, la profondeur par défaut est 4.
*
* @param mode mode de jeu (PVP, PVBOT, PVALPHA ou PVGOD)
*/
public AvalamWindow(GameMode mode) {
this(mode, 4);
}
/**
* Construit la fenêtre en fonction du mode choisi.
* 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 alphaDepth profondeur de recherche pour Alpha-Beta / Bot Divin
*/
public AvalamWindow(GameMode mode, int alphaDepth) {
super("Avalam");
this.mode = mode;
this.searchDepth = Math.max(1, alphaDepth);
this.idiotBot = (mode == GameMode.PVBOT) ? new IdiotBot(botPlayer) : null;
int depth = this.searchDepth;
this.alphaBot = (mode == GameMode.PVALPHA) ? new AlphaBetaBot(botPlayer, depth) : null;
this.divineBot = (mode == GameMode.PVGOD) ? new DivineBot(botPlayer, depth) : null;
setSize(750, 800);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
Tower[][] grid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
GameState gs = new GameState(grid);
ScoreManager sm = new ScoreManager();
// Chargement du plateau initial
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
int y = sm.count(Color.COLOR1, grid);
int r = sm.count(Color.COLOR2, grid);
// Initialisation du moteur (PLAYER1 commence)
board = new AvalamBoard(initialGrid);
ScoreView scoreView = new ScoreView(y, r);
TurnView turnView = new TurnView("Tour du joueur : Jaune");
// Panneau supérieur (score + tour)
JPanel topPanel = new JPanel(new GridLayout(2, 1));
topPanel.setBackground(new java.awt.Color(200, 200, 200));
BoardView boardView = new BoardView(gs);
scoreView = new ScoreView(
computeScore(Color.YELLOW),
computeScore(Color.RED)
);
JPanel top = new JPanel(new GridLayout(2,1));
top.add(scoreView);
top.add(turnView);
turnView = new TurnView(turnMessage());
add(top, BorderLayout.NORTH);
topPanel.add(scoreView);
topPanel.add(turnView);
add(topPanel, BorderLayout.NORTH);
// Plateau
boardView = new BoardView(board, this::onBoardUpdated);
add(boardView, BorderLayout.CENTER);
pack();
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
// Si un jour le bot devait commencer (pas le cas ici), on le ferait jouer ici.
maybePlayBotTurn();
}
//Méthodes
/**
* Appelée après chaque coup (humain ou bot).
* Met à jour score, tour, et affiche la fin de partie.
*/
public void onBoardUpdated() {
scoreView.updateScores(
computeScore(Color.YELLOW),
computeScore(Color.RED)
);
turnView.setTurn(turnMessage());
if (board.isGameOver()) {
Result res = board.getResult();
int scoreJaune = computeScore(Color.YELLOW);
int scoreRouge = computeScore(Color.RED);
EndGameDialog dialog = new EndGameDialog(
this,
res,
scoreJaune,
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;
}
// Si on est contre un bot et que cest son tour, on déclenche son animation.
maybePlayBotTurn();
}
/**
* Fait jouer le bot (idiot / alpha / divin) en deux étapes visibles :
* 1) sélection de la tour (affiche les coups légaux)
* 2) attente 1 seconde
* 3) déplacement vers la destination
*
* Le tout sans bloquer l'interface (Timer Swing).
*/
private void maybePlayBotTurn() {
// Mode joueur vs joueur : aucun bot
if (mode == GameMode.PVP) return;
// Sécurité
if (board.isGameOver()) return;
if (board.getCurrentPlayer() != botPlayer) return;
if (botAnimating) return;
// Vérifier qu'on a bien le bot correspondant
if (mode == GameMode.PVBOT && idiotBot == null) return;
if (mode == GameMode.PVALPHA && alphaBot == null) return;
if (mode == GameMode.PVGOD && divineBot == null) return;
botAnimating = true;
// Désactiver les interactions du joueur pendant le tour du bot.
boardView.setInteractionEnabled(false);
// Choix dun coup sur une copie sûre
AbstractPly botMove;
if (mode == GameMode.PVBOT) {
botMove = idiotBot.giveYourMove(board.safeCopy());
} else if (mode == GameMode.PVALPHA) {
botMove = alphaBot.giveYourMove(board.safeCopy());
} else {
// A FAIRE PLUS TARD (PVGOD)
botMove = divineBot.giveYourMove(board.safeCopy());
}
if (botMove == null) {
botAnimating = false;
boardView.setInteractionEnabled(true);
return;
}
if (!(botMove instanceof AvalamPly)) {
botAnimating = false;
boardView.setInteractionEnabled(true);
return;
}
AvalamPly ap = (AvalamPly) botMove;
// Étape 1 : sélection (comme un clic humain)
InteractionController ctrl = boardView.getController();
ctrl.onPieceClicked(ap.getXFrom(), ap.getYFrom());
boardView.refresh();
// Étape 2 : attendre puis cliquer la destination
javax.swing.Timer t = new javax.swing.Timer(1000, e -> {
// Sécurité : si la partie a changé entre temps
if (board.isGameOver() || board.getCurrentPlayer() != botPlayer) {
botAnimating = false;
boardView.setInteractionEnabled(true);
((javax.swing.Timer) e.getSource()).stop();
return;
}
ctrl.onPieceClicked(ap.getXTo(), ap.getYTo());
boardView.refresh();
botAnimating = false;
boardView.setInteractionEnabled(true);
((javax.swing.Timer) e.getSource()).stop();
});
t.setRepeats(false);
t.start();
}
/**
* Calcule le score d'une couleur : nombre de tours contrôlées.
*
* @param c couleur à compter
* @return nombre de tours appartenant à la couleur c
*/
private int computeScore(Color c) {
int score = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
for (int col = 0; col < AvalamBoard.SIZE; col++) {
Tower t = board.getTowerAt(r, col);
if (t != null && t.getColor() == c) {
score++;
}
}
}
return score;
}
/**
* Retourne le message affiché pour le joueur courant.
*
* @return message du tour
*/
private String turnMessage() {
return "Tour du joueur : " +
(board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge");
}
//Affichage
}

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

@@ -0,0 +1,84 @@
package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* La classe <code>BoardLoader</code>
*
* Permet de charger un plateau Avalam à partir dun fichier texte.
* Le fichier contient une grille 9x9 de valeurs numériques :
* - 0 : case vide
* - 1 : tour jaune
* - 2 : tour rouge
*
* Cette classe fournit une méthode statique pour construire
* une grille de tours (<code>Tower[][]</code>) à partir dune ressource.
*/
public class BoardLoader {
//Attributs
//Constructeur
//Méthodes
/**
* Charge un plateau Avalam depuis un fichier de ressources.
*
* @param resourcePath chemin du fichier de plateau (dans les ressources)
* @return une grille 9x9 de tours (Tower) ou null pour les cases vides
*/
public static Tower[][] loadFromFile(String resourcePath) {
Tower[][] grid = new Tower[9][9];
InputStream in = BoardLoader.class.getResourceAsStream("/" + resourcePath);
if (in == null) {
System.err.println("Ressource introuvable : /" + resourcePath);
return grid;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String line;
int row = 0;
while ((line = reader.readLine()) != null && row < 9) {
// Accepte SOIT les espaces, SOIT les virgules
line = line.replace(",", " ");
String[] parts = line.trim().split("\\s+");
for (int col = 0; col < 9; col++) {
int value = Integer.parseInt(parts[col]);
switch (value) {
case 1:
grid[row][col] = Tower.createTower(Color.YELLOW);
break;
case 2:
grid[row][col] = Tower.createTower(Color.RED);
break;
default:
grid[row][col] = null;
break;
}
}
row++;
}
} catch (IOException e) {
e.printStackTrace();
}
return grid;
}
}

View File

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

View File

@@ -1,28 +1,60 @@
package fr.iut_fbleau.Avalam;
/**
* L'énumération <code>Color</code> représente la couleur du sommet d'une tour Avalam.
*
* COLOR1 : couleur du Joueur Jaune
* COLOR2 : couleur du Joueur Rouge
*
* Les couleurs Swing associées peuvent être modifiées ici pour changer tout le jeu.
*
* @version 3.0
*/
* L'énumération <code>Color</code>
*
* Représente les deux couleurs utilisées dans Avalam :
* - YELLOW (jaune)
* - RED (rouge)
*
* Chaque valeur est associée à une couleur Swing (<code>java.awt.Color</code>)
* pour laffichage graphique et peut être convertie en <code>Player</code> (GameAPI).
*/
public enum Color {
COLOR1(java.awt.Color.YELLOW), // Joueur Jaune
COLOR2(java.awt.Color.RED); // Joueur Rouge
//Attributs
/** Couleur Swing associée */
/** Couleur jaune (associée à PLAYER1). */
YELLOW(255, 220, 0),
/** Couleur rouge (associée à PLAYER2). */
RED(200, 40, 40);
/** Couleur Swing utilisée pour l'affichage dans l'interface graphique. */
private final java.awt.Color swingColor;
Color(java.awt.Color col) {
this.swingColor = col;
//Constructeur
/**
* Construit une couleur Avalam en initialisant sa couleur Swing associée.
*
* @param r composante rouge [0..255]
* @param g composante verte [0..255]
* @param b composante bleue [0..255]
*/
Color(int r, int g, int b) {
this.swingColor = new java.awt.Color(r, g, b);
}
//Méthodes
/**
* Retourne la couleur Swing associée (pour l'affichage).
*
* @return java.awt.Color
*/
public java.awt.Color getSwingColor() {
return this.swingColor;
return swingColor;
}
/**
* Convertit la couleur Avalam en joueur GameAPI.
*
* @return PLAYER1 si YELLOW, sinon PLAYER2
*/
public fr.iut_fbleau.GameAPI.Player toPlayer() {
return (this == YELLOW ?
fr.iut_fbleau.GameAPI.Player.PLAYER1 :
fr.iut_fbleau.GameAPI.Player.PLAYER2);
}
}

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

@@ -0,0 +1,12 @@
package fr.iut_fbleau.Avalam;
/**
* Mode de jeu au lancement.
*/
public enum GameMode {
PVP, // joueur vs joueur
PVBOT, // joueur vs bot idiot
PVALPHA, // joueur vs bot alpha
PVGOD, // joueur vs bot stratégique
ARENA // bot vs bot (mode arène)
}

View File

@@ -0,0 +1,94 @@
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
import java.util.List;
/**
* La classe <code>HighlightLayer</code>
*
* Gère l'affichage graphique des cases jouables sur le plateau Avalam.
* Les cases autorisées sont représentées par des cercles verts semi-transparents.
*
* Cette classe ne contient aucune logique de jeu.
*/
public class HighlightLayer extends JPanel {
//Attributs
/** Position X de base du plateau. */
private int xBase;
/** Position Y de base du plateau. */
private int yBase;
/** Espacement entre les cases du plateau. */
private int spacing;
/** Taille dun pion en pixels. */
private int size;
/** Liste des positions jouables (cases en vert). */
private List<Point> legalMoves;
//Constructeur
/**
* Construit la couche d'affichage des coups légaux.
*
* @param xBase position X de base du plateau
* @param yBase position Y de base du plateau
* @param spacing espacement entre cases
* @param size taille des pions
*/
public HighlightLayer(int xBase, int yBase, int spacing, int size) {
this.xBase = xBase;
this.yBase = yBase;
this.spacing = spacing;
this.size = size;
setOpaque(false);
setBounds(0, 0, 800, 800);
}
//Méthodes
/**
* Définit la liste des cases légales à afficher.
*
* @param moves liste des positions jouables
*/
public void setLegalMoves(List<Point> moves) {
this.legalMoves = moves;
repaint();
}
//Affichage
/**
* Dessine les cercles verts autour des cases autorisées.
*
* @param g contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (legalMoves == null) return;
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new java.awt.Color(0, 255, 0, 120));
for (Point p : legalMoves) {
int r = p.x;
int c = p.y;
int x = xBase + c * spacing;
int y = yBase + r * spacing;
int highlight = size + 20; // Cercle plus grand que le pion
g2.fillOval(x - 10, y - 10, highlight, highlight);
}
}
}

View File

@@ -0,0 +1,188 @@
package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* La classe <code>InteractionController</code>
*
* Gère l'interaction entre l'utilisateur et le moteur du jeu Avalam.
* Elle permet :
* - la sélection d'une tour
* - le calcul des coups légaux
* - la validation d'un déplacement
* - l'application d'un coup
* - la mise à jour de l'affichage via BoardView
*
* Cette classe ne réalise aucun affichage direct.
*/
public class InteractionController {
//Attributs
/** Référence au moteur du jeu Avalam. */
private AvalamBoard board;
/** Ligne de la tour sélectionnée (-1 si aucune). */
private int selectedRow = -1;
/** Colonne de la tour sélectionnée (-1 si aucune). */
private int selectedCol = -1;
/** Liste des coups légaux (sous forme de points). */
private List<Point> legalMoves = new ArrayList<>();
/** Référence à la vue du plateau pour rafraîchir l'affichage. */
private BoardView view;
//Constructeur
/**
* Construit le contrôleur d'interactions.
*
* @param board moteur du jeu Avalam
* @param view vue du plateau
*/
public InteractionController(AvalamBoard board, BoardView view) {
this.board = board;
this.view = view;
}
//Méthodes
/**
* Retourne la liste des cases jouables.
*
* @return liste des coups légaux
*/
public List<Point> getLegalMoves() {
return legalMoves;
}
/**
* Appelé lorsquun pion est cliqué dans BoardView.
* Gère :
* - la sélection dune tour
* - la désélection
* - la tentative de déplacement
*
* @param r ligne cliquée
* @param c colonne cliquée
*/
public void onPieceClicked(int r, int c) {
// Si on clique la même case ⇒ désélection
if (r == selectedRow && c == selectedCol) {
clearSelection();
view.refresh();
return;
}
// Si aucune sélection : on sélectionne un pion appartenant au joueur courant
Tower t = board.getTowerAt(r, c);
if (t != null && t.getColor().toPlayer() == board.getCurrentPlayer()) {
selectTower(r, c);
view.refresh();
return;
}
// Sinon : tentons de jouer un coup (déplacement)
if (selectedRow != -1 && selectedCol != -1) {
tryMove(r, c);
}
}
/**
* Sélectionne une tour à la position (r,c).
*
* @param r ligne
* @param c colonne
*/
private void selectTower(int r, int c) {
selectedRow = r;
selectedCol = c;
computeLegalMoves();
}
/**
* Annule la sélection actuelle.
*/
private void clearSelection() {
selectedRow = -1;
selectedCol = -1;
legalMoves.clear();
}
/**
* Calcule les coups légaux à partir de la tour sélectionnée.
* Utilise l'itérateur fourni par AvalamBoard.
*/
private void computeLegalMoves() {
legalMoves.clear();
Iterator<AbstractPly> it = board.iterator();
while (it.hasNext()) {
AbstractPly p = it.next();
if (!(p instanceof AvalamPly)) continue;
AvalamPly ap = (AvalamPly) p;
// On ne garde que les coups dont la source correspond à la sélection
if (ap.getXFrom() == selectedRow && ap.getYFrom() == selectedCol) {
legalMoves.add(new Point(ap.getXTo(), ap.getYTo()));
}
}
}
/**
* Tente dexécuter un déplacement vers la case (r,c).
*
* @param r ligne de destination
* @param c colonne de destination
*/
private void tryMove(int r, int c) {
// Vérifier si (r,c) est une destination légale
boolean isLegalDest = false;
for (Point p : legalMoves) {
if (p.x == r && p.y == c) {
isLegalDest = true;
break;
}
}
if (!isLegalDest) {
clearSelection(); // clic ailleurs = désélection
view.refresh();
return;
}
// Construire le coup
Player cur = board.getCurrentPlayer();
AvalamPly ply = new AvalamPly(cur, selectedRow, selectedCol, r, c);
// Vérifier via lAPI
if (board.isLegal(ply)) {
// Appliquer via lAPI
board.doPly(ply);
// Réinitialiser la sélection
clearSelection();
// Mise à jour de l'interface
view.onBoardUpdated();
} else {
// Coup impossible
clearSelection();
}
view.refresh();
}
}

View File

@@ -1,7 +1,82 @@
package fr.iut_fbleau.Avalam;
import javax.swing.*;
/**
* Point dentrée : propose un menu de sélection de mode, puis lance la fenêtre Avalam.
*/
public class Main {
public static void main(String[] args) {
new AvalamWindow();
SwingUtilities.invokeLater(() -> {
showModeSelection();
});
}
/**
* Affiche le menu de sélection du mode de jeu.
* Peut être appelé depuis d'autres fenêtres pour revenir au menu.
*/
public static void showModeSelection() {
String[] options = {
"joueur vs joueur",
"joueur vs botidiot",
"joueur vs bot alpha",
"joueur vs bot divin",
"Arène"
};
int choice = JOptionPane.showOptionDialog(
null,
"Choisissez un mode de jeu :",
"Avalam - Mode de jeu",
JOptionPane.DEFAULT_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]
);
if (choice == -1) System.exit(0);
// Mode Arène
if (choice == 4) {
new ArenaWindow();
return;
}
GameMode mode;
if (choice == 1) mode = GameMode.PVBOT;
else if (choice == 2) mode = GameMode.PVALPHA;
else if (choice == 3) mode = GameMode.PVGOD;
else mode = GameMode.PVP;
// Pour ALPHA et GOD : demander une profondeur
if (mode == GameMode.PVALPHA || mode == GameMode.PVGOD) {
String s = JOptionPane.showInputDialog(
null,
"Profondeur de recherche ?\n(Conseil 4)",
(mode == GameMode.PVGOD ? "Bot Divin (PVGOD)" : "Bot Alpha-Beta"),
JOptionPane.QUESTION_MESSAGE
);
int depth = 4; // défaut
if (s != null) {
try { depth = Integer.parseInt(s.trim()); }
catch (Exception ignored) { depth = 4; }
} else {
// Annulation : on revient en PVP
new AvalamWindow(GameMode.PVP);
return;
}
if (depth < 1) depth = 1;
new AvalamWindow(mode, depth);
return;
}
new AvalamWindow(mode);
}
}

View File

@@ -1,40 +1,47 @@
package fr.iut_fbleau.Avalam.ui;
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>PieceButton</code> représente graphiquement une tour Avalam.
* Il s'agit d'un bouton rond :
*
* <ul>
* <li>coloré selon le joueur</li>
* <li>affichant sa hauteur</li>
* <li>avec un effet de survol visuel</li>
* </ul>
*
* Ce composant sert d'interaction principale pour cliquer les pions.
*
* @author
* @version 1.0
*/
* La classe <code>PieceButton</code>
*
* Représente graphiquement une tour Avalam sous forme dun bouton rond.
* Chaque bouton :
* - possède une couleur correspondant au joueur,
* - affiche la hauteur de la tour,
* - réagit au survol de la souris.
*
* Ce composant permet linteraction avec les pièces du plateau.
*/
public class PieceButton extends JButton {
private Color color;
//Attributs
/** Couleur graphique du pion. */
private java.awt.Color color;
/** Hauteur de la tour affichée. */
private int height;
/** Indique si la souris survole le bouton. */
private boolean hover = false;
/** Position de la tour sur la grille. */
public final int row, col;
/** Position de la tour sur la grille (ligne). */
public final int row;
/** Position de la tour sur la grille (colonne). */
public final int col;
//Constructeur
/**
* Constructeur.
*
* @param c couleur graphique du pion
* @param height hauteur de la tour
* @param row ligne du pion
* @param col colonne du pion
*/
* Construit un bouton représentant une tour Avalam.
*
* @param c couleur graphique du pion
* @param height hauteur de la tour
* @param row ligne du pion
* @param col colonne du pion
*/
public PieceButton(java.awt.Color c, int height, int row, int col) {
this.color = c;
this.height = height;
@@ -52,9 +59,13 @@ public class PieceButton extends JButton {
});
}
//Méthodes
/**
* Dessine le pion rond ainsi que son chiffre au centre.
*/
* Dessine le pion rond ainsi que sa hauteur au centre.
*
* @param g contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
@@ -78,9 +89,15 @@ public class PieceButton extends JButton {
g2.dispose();
}
//Affichage
/**
* Rend le bouton réellement rond (zone cliquable circulaire).
*/
* Rend le bouton réellement rond (zone cliquable circulaire).
*
* @param x coordonnée X
* @param y coordonnée Y
* @return true si le point est dans le cercle
*/
@Override
public boolean contains(int x, int y) {
double dx = x - getWidth()/2.0;

View File

@@ -1,48 +1,44 @@
package fr.iut_fbleau.Avalam.ui;
import fr.iut_fbleau.Avalam.Tower;
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>PieceLayer</code> gère l'affichage des pions
* (sous forme de <code>PieceButton</code>) sur la grille.
*
* Elle s'occupe uniquement :
* <ul>
* <li>d'afficher les pièces</li>
* <li>de positionner correctement les boutons</li>
* <li>d'attacher un callback à chaque clic</li>
* </ul>
*
* Aucune logique de jeu n'est réalisée ici.
*
* @author
* @version 1.0
*/
* La classe <code>PieceLayer</code>
*
* Gère l'affichage des tours du plateau Avalam sous forme de boutons.
* Elle :
* - affiche les pièces,
* - positionne les boutons sur la grille,
* - associe une action de clic à chaque pièce.
*
* Aucune logique de jeu n'est implémentée ici.
*/
public class PieceLayer extends JPanel {
//Constructeur
/**
* Constructeur.
* Initialise un panneau transparent prêt à recevoir des pions.
*/
* Construit la couche graphique des pièces.
*/
public PieceLayer() {
setLayout(null);
setOpaque(false);
setBounds(0, 0, 800, 800);
}
//Méthodes
/**
* Affiche la grille de tours sous forme de boutons.
*
* @param grid grille 9×9 des tours
* @param xBase offset X du plateau
* @param yBase offset Y du plateau
* @param spacing espacement entre cases
* @param size taille d'un pion
* @param clickCallback fonction appelée lors dun clic sur un pion
*/
* Affiche la grille de tours sous forme de boutons interactifs.
*
* @param grid grille 9×9 des tours
* @param xBase décalage horizontal du plateau
* @param yBase décalage vertical du plateau
* @param spacing espacement entre cases
* @param size taille d'un pion
* @param clickCallback fonction appelée lors dun clic sur un pion
*/
public void displayGrid(Tower[][] grid, int xBase, int yBase,
int spacing, int size,
java.util.function.BiConsumer<Integer, Integer> clickCallback) {

View File

@@ -0,0 +1,57 @@
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>ScoreView</code>
*
* Affiche les scores des deux joueurs du jeu Avalam.
* Cette classe est purement graphique : elle ne calcule pas les scores.
*/
public class ScoreView extends JPanel {
//Attributs
/** Label affichant le score du joueur jaune. */
private JLabel scoreY;
/** Label affichant le score du joueur rouge. */
private JLabel scoreR;
//Constructeur
/**
* Construit la vue des scores.
*
* @param y score initial du joueur jaune
* @param r score initial du joueur rouge
*/
public ScoreView(int y, int r) {
setBackground(new java.awt.Color(200,200,200));
setLayout(new FlowLayout());
scoreY = new JLabel("Score Jaune : " + y);
scoreR = new JLabel("Score Rouge : " + r);
scoreY.setFont(new Font("Arial", Font.BOLD, 18));
scoreR.setFont(new Font("Arial", Font.BOLD, 18));
add(scoreY);
add(new JLabel(" | "));
add(scoreR);
}
//Méthodes
/**
* Met à jour l'affichage des scores.
*
* @param y nouveau score du joueur jaune
* @param r nouveau score du joueur rouge
*/
public void updateScores(int y, int r) {
scoreY.setText("Score Jaune : " + y);
scoreR.setText("Score Rouge : " + r);
}
}

View File

@@ -1,41 +1,90 @@
package fr.iut_fbleau.Avalam ;
package fr.iut_fbleau.Avalam;
/**
* La classe <code>Tower</code> stocke la couleur de son pion haut et la hauteur de la tour.
* La classe <code>Tower</code>
*
* @version 1.0
* @author Aurélien
* Représente une tour dans le jeu Avalam.
* Une tour est caractérisée par :
* - une couleur (couleur du sommet)
* - une hauteur (nombre de pions empilés)
*
* Cette version est volontairement compatible avec le reste du projet :
* - constructeur Tower(int, Color) utilisé dans d'autres parties possibles
* - usine createTower(Color) utilisé par BoardLoader
* - méthode mergeTower(Tower) utilisée par AvalamBoard
*/
public class Tower {
//Attributs
private Color color ;
private byte height = 1 ;
//Attributs
//Constructeur
public Tower(Color color) {
this.color = color ;
}
/** Hauteur de la tour (nombre de pions empilés). */
private byte height;
//Méthodes
public Color getColor() {
return this.color ;
}
/** Couleur du sommet de la tour (propriétaire actuel). */
private Color color;
public byte getHeight() {
return this.height ;
}
//Constructeur
/**
* Méthode qui empile une autre tour sur l'objet sur lequel le méthode est appelée.
* Aucune vérification de hauteur n'est effectuée.
/**
* Construit une tour avec une hauteur et une couleur données.
*
* @param height hauteur initiale
* @param color couleur du sommet
*/
public void mergeTower(Tower tower) {
this.color = tower.getColor();
this.height += tower.getHeight();
}
public Tower(int height, Color color) {
this.height = (byte) height;
this.color = color;
}
@Override
public String toString() {
return "" ;
}
/**
* 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
/**
* Retourne la hauteur de la tour.
*
* @return hauteur
*/
public int getHeight() {
return height;
}
/**
* Retourne la couleur du sommet.
*
* @return couleur
*/
public Color getColor() {
return color;
}
/**
* Fusionne la tour <code>src</code> sur la tour courante (destination).
* La couleur du sommet devient celle de <code>src</code>.
*
* @param src tour source empilée sur la destination
*/
public void mergeTower(Tower src) {
this.height += src.height;
this.color = src.color;
}
//Affichage
/**
* Représentation textuelle de la tour.
*
* @return chaîne représentant la tour
*/
@Override
public String toString() {
return color + "(" + height + ")";
}
}

View File

@@ -0,0 +1,45 @@
package fr.iut_fbleau.Avalam;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>TurnView</code>
*
* Affiche le joueur dont c'est le tour dans le jeu Avalam.
* Cette classe est uniquement graphique.
*/
public class TurnView extends JPanel {
//Attributs
/** Label affichant le joueur courant. */
private JLabel text;
//Constructeur
/**
* Construit la vue du tour de jeu.
*
* @param initial message initial à afficher
*/
public TurnView(String initial) {
setBackground(new java.awt.Color(220,220,220));
text = new JLabel(initial);
text.setFont(new Font("Arial", Font.BOLD, 20));
add(text);
}
//Méthodes
/**
* Met à jour le texte affichant le joueur courant.
*
* @param s message à afficher
*/
public void setTurn(String s) {
text.setText(s);
}
}

View File

@@ -1,60 +0,0 @@
package fr.iut_fbleau.Avalam.logic;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
import java.io.*;
/**
* La classe <code>BoardLoader</code> est responsable du chargement d'un plateau Avalam
* depuis un fichier texte externe (généralement Plateau.txt).
*
* Le fichier doit contenir une matrice 9×9 de valeurs numériques séparées par des virgules :
* - 0 : case vide (trou)
* - 1 : pion appartenant au joueur COLOR1 (Jaune)
* - 2 : pion appartenant au joueur COLOR2 (Rouge)
*
* Aucun contrôle de cohérence avancé n'est effectué, le chargeur se contente
* dinterpréter les valeurs comme demandé.
*
* @author
* @version 1.0
*/
public class BoardLoader {
/**
* Charge un plateau Avalam depuis un fichier.
*
* @param path chemin du fichier plateau.
* @return un tableau 9×9 contenant des objets <code>Tower</code> ou <code>null</code>.
*/
public static Tower[][] loadFromFile(String path) {
Tower[][] grid = new Tower[9][9];
try (BufferedReader br = new BufferedReader(new FileReader(new File(path)))) {
String line;
int row = 0;
while ((line = br.readLine()) != null && row < 9) {
String[] parts = line.split(",");
for (int col = 0; col < 9; col++) {
int v = Integer.parseInt(parts[col]);
// Interprétation des valeurs
if (v == 1) grid[row][col] = new Tower(Color.COLOR1);
else if (v == 2) grid[row][col] = new Tower(Color.COLOR2);
else grid[row][col] = null; // Case vide
}
row++;
}
} catch (Exception e) {
e.printStackTrace();
}
return grid;
}
}

View File

@@ -1,70 +0,0 @@
package fr.iut_fbleau.Avalam.logic;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
/**
* La classe <code>GameState</code> représente l'état courant du jeu Avalam :
* - la grille 9×9
* - le joueur dont c'est le tour
*
* Elle ne contient aucune logique de déplacement ni de vérification des règles ;
* son rôle est uniquement de stocker et de fournir l'état du jeu.
*
* @author
* @version 1.0
*/
public class GameState {
/** Grille du plateau : 9×9 tours ou cases vides. */
private Tower[][] grid;
/** Joueur dont cest le tour (COLOR1 ou COLOR2). */
private Color currentPlayer = Color.COLOR1;
/**
* Constructeur.
*
* @param grid la grille initiale du plateau.
*/
public GameState(Tower[][] grid) {
this.grid = grid;
}
/**
* Retourne la tour présente à la position (r,c), ou null.
*/
public Tower get(int r, int c) {
return grid[r][c];
}
/**
* Retourne la grille complète.
*/
public Tower[][] getGrid() {
return grid;
}
/**
* Retourne le joueur dont c'est le tour.
*/
public Color getCurrentPlayer() {
return currentPlayer;
}
/**
* Change le joueur courant : COLOR1 → COLOR2 → COLOR1.
*/
public void switchPlayer() {
currentPlayer = (currentPlayer == Color.COLOR1) ? Color.COLOR2 : Color.COLOR1;
}
/**
* Vérifie si une position (r,c) est dans les limites du plateau.
*
* @return vrai si la position est comprise dans un plateau 9×9.
*/
public boolean isInside(int r, int c) {
return r >= 0 && r < 9 && c >= 0 && c < 9;
}
}

View File

@@ -1,37 +0,0 @@
package fr.iut_fbleau.Avalam.logic;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
/**
* La classe <code>ScoreManager</code> gère le calcul des scores
* selon les règles officielles dAvalam :
*
* Un joueur gagne 1 point par tour dont le sommet (couleur) lui appartient,
* indépendamment de la hauteur de la tour.
*
* Cette classe na aucune responsabilité autre que compter.
*
* @author
* @version 1.0
*/
public class ScoreManager {
/**
* Compte le nombre de tours dont le sommet appartient à la couleur donnée.
*
* @param c couleur du joueur (COLOR1 ou COLOR2)
* @param grid grille 9×9 contenant des tours ou null
* @return score du joueur
*/
public static int count(Color c, Tower[][] grid) {
int score = 0;
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (grid[i][j] != null && grid[i][j].getColor() == c)
score++;
return score;
}
}

View File

@@ -1,35 +0,0 @@
package fr.iut_fbleau.Avalam.logic;
import fr.iut_fbleau.Avalam.Color;
/**
* La classe <code>TurnManager</code> gère le déroulement des tours dAvalam.
*
* Son rôle est simple :
* - identifier le joueur dont cest le tour
* - passer au joueur suivant
*
* Elle ne contient pas de logique de mouvement, ni de validation.
*
* @author
* @version 1.0
*/
public class TurnManager {
/** Joueur dont c'est le tour (COLOR1 commence la partie). */
private Color current = Color.COLOR1;
/**
* Retourne le joueur actuel.
*/
public Color getPlayer() {
return current;
}
/**
* Passe au joueur suivant.
*/
public void next() {
current = (current == Color.COLOR1) ? Color.COLOR2 : Color.COLOR1;
}
}

View File

@@ -1,84 +0,0 @@
package fr.iut_fbleau.Avalam.ui;
import fr.iut_fbleau.Avalam.logic.GameState;
import javax.swing.*;
/**
* La classe <code>BoardView</code> représente l'affichage complet du plateau Avalam.
* Elle sappuie sur une architecture en couches (layered pane) pour séparer proprement :
*
* <ul>
* <li><b>HighlightLayer</b> : les cases jouables mises en surbrillance</li>
* <li><b>PieceLayer</b> : les pions affichés sous forme de boutons ronds</li>
* </ul>
*
* La vue ne contient pas la logique de jeu : elle la délègue entièrement
* à un <code>InteractionController</code>.
*
* @author
* @version 1.0
*/
public class BoardView extends JLayeredPane {
/** Taille des pions affichés. */
private final int size = 50;
/** Distance entre deux cases de la grille. */
private final int spacing = 70;
/** Décalages pour centrer l'affichage. */
private final int xBase = 60, yBase = 60;
/** Accès à l'état du jeu. */
private GameState state;
/** Couche d'affichage des ronds verts. */
private HighlightLayer highlightLayer;
/** Couche d'affichage des pions. */
private PieceLayer pieceLayer;
/** Gestionnaire d'interactions utilisateur. */
private InteractionController controller;
/**
* Constructeur de la vue du plateau.
*
* @param state l'état du jeu Avalam
*/
public BoardView(GameState state) {
this.state = state;
setLayout(null);
controller = new InteractionController(state);
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
pieceLayer = new PieceLayer();
add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
add(pieceLayer, JLayeredPane.PALETTE_LAYER);
refresh();
}
/**
* Met à jour l'affichage des couches en fonction de l'état actuel du jeu.
*/
public void refresh() {
pieceLayer.displayGrid(
state.getGrid(),
xBase, yBase, spacing, size,
// Callback appelé lorsquun pion est cliqué
(r, c) -> {
controller.onPieceClicked(r, c);
highlightLayer.setLegalMoves(controller.getLegalMoves());
highlightLayer.repaint();
}
);
highlightLayer.repaint();
}
}

View File

@@ -1,74 +0,0 @@
package fr.iut_fbleau.Avalam.ui;
import javax.swing.*;
import java.awt.*;
import java.util.List;
/**
* La classe <code>HighlightLayer</code> est responsable de l'affichage des
* cases de déplacement autorisées sous forme de cercles verts.
*
* Elle n'interagit pas directement avec les pièces, mais se contente
* de dessiner en arrière-plan selon une liste de positions légales.
*
* @author
* @version 1.0
*/
public class HighlightLayer extends JPanel {
private int xBase, yBase, spacing, size;
/** Liste des positions jouables (cases en vert). */
private List<Point> legalMoves;
/**
* Constructeur.
*
* @param xBase position X de base du plateau
* @param yBase position Y de base du plateau
* @param spacing espacement entre cases
* @param size taille des pions
*/
public HighlightLayer(int xBase, int yBase, int spacing, int size) {
this.xBase = xBase;
this.yBase = yBase;
this.spacing = spacing;
this.size = size;
setOpaque(false);
setBounds(0, 0, 800, 800);
}
/**
* Définit la liste des cases légales à afficher.
*/
public void setLegalMoves(List<Point> moves) {
this.legalMoves = moves;
repaint();
}
/**
* Dessine les cercles verts autour des cases autorisées.
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (legalMoves == null) return;
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(0, 255, 0, 120));
for (Point p : legalMoves) {
int r = p.x;
int c = p.y;
int x = xBase + c * spacing;
int y = yBase + r * spacing;
int highlight = size + 20; // Cercle plus grand que le pion
g2.fillOval(x - 10, y - 10, highlight, highlight);
}
}
}

View File

@@ -1,116 +0,0 @@
package fr.iut_fbleau.Avalam.ui;
import fr.iut_fbleau.Avalam.Tower;
import fr.iut_fbleau.Avalam.logic.GameState;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* La classe <code>InteractionController</code> gère entièrement
* la logique d'interaction de l'utilisateur :
*
* <ul>
* <li>Sélection d'un pion</li>
* <li>Annulation de la sélection</li>
* <li>Calcul des cases de déplacement légales</li>
* </ul>
*
* Aucun affichage n'est réalisé ici ; la vue (BoardView) récupère
* simplement les données calculées pour les afficher.
*
* @author
* @version 1.0
*/
public class InteractionController {
private GameState state;
/** Position du pion sélectionné (ou -1 s'il n'y en a pas). */
private int selectedRow = -1, selectedCol = -1;
/** Liste des déplacements possibles depuis la sélection. */
private List<Point> legalMoves = new ArrayList<>();
/**
* Constructeur.
*
* @param state état du jeu à manipuler
*/
public InteractionController(GameState state) {
this.state = state;
}
/**
* Retourne les cases jouables calculées.
*/
public List<Point> getLegalMoves() {
return legalMoves;
}
/**
* Gère le clic sur un pion donné.
*
* @param r ligne de la case cliquée
* @param c colonne de la case cliquée
*/
public void onPieceClicked(int r, int c) {
Tower t = state.get(r, c);
if (t == null) return;
// Annulation si on reclique la sélection
if (r == selectedRow && c == selectedCol) {
selectedRow = -1;
selectedCol = -1;
legalMoves.clear();
return;
}
// Interdiction de jouer le pion adverse
if (t.getColor() != state.getCurrentPlayer()) {
selectedRow = -1;
selectedCol = -1;
legalMoves.clear();
return;
}
selectedRow = r;
selectedCol = c;
computeLegalMoves();
}
/**
* Calcule toutes les cases accessibles à partir du pion sélectionné.
*/
private void computeLegalMoves() {
legalMoves.clear();
Tower[][] grid = state.getGrid();
Tower src = grid[selectedRow][selectedCol];
int h = src.getHeight();
int[] d = {-1, 0, 1};
for (int dr : d) {
for (int dc : d) {
if (dr == 0 && dc == 0) continue;
int nr = selectedRow + dr;
int nc = selectedCol + dc;
if (!state.isInside(nr, nc)) continue;
Tower dest = grid[nr][nc];
if (dest == null) continue;
if (h + dest.getHeight() > 5) continue;
legalMoves.add(new Point(nr, nc));
}
}
}
}

View File

@@ -1,46 +0,0 @@
package fr.iut_fbleau.Avalam.ui;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>ScoreView</code> affiche les scores actuels des deux joueurs.
*
* Elle est purement graphique : aucune logique de calcul n'est présente.
*
* @author
* @version 1.0
*/
public class ScoreView extends JPanel {
private JLabel scoreY, scoreR;
/**
* Constructeur.
*
* @param y score initial du joueur jaune
* @param r score initial du joueur rouge
*/
public ScoreView(int y, int r) {
setBackground(new Color(200,200,200));
setLayout(new FlowLayout());
scoreY = new JLabel("Score Jaune : " + y);
scoreR = new JLabel("Score Rouge : " + r);
scoreY.setFont(new Font("Arial", Font.BOLD, 18));
scoreR.setFont(new Font("Arial", Font.BOLD, 18));
add(scoreY);
add(new JLabel(" | "));
add(scoreR);
}
/**
* Met à jour l'affichage des scores.
*/
public void updateScores(int y, int r) {
scoreY.setText("Score Jaune : " + y);
scoreR.setText("Score Rouge : " + r);
}
}

View File

@@ -1,41 +0,0 @@
package fr.iut_fbleau.Avalam.ui;
import javax.swing.*;
import java.awt.*;
/**
* La classe <code>TurnView</code> affiche le joueur à qui c'est le tour.
*
* Elle agit comme une simple bannière dinformation,
* mise à jour par la logique du jeu.
*
* @author
* @version 1.0
*/
public class TurnView extends JPanel {
private JLabel text;
/**
* Constructeur.
*
* @param initial message initial à afficher
*/
public TurnView(String initial) {
setBackground(new Color(220,220,220));
text = new JLabel(initial);
text.setFont(new Font("Arial", Font.BOLD, 20));
add(text);
}
/**
* Met à jour le texte affichant le joueur courant.
*
* @param s message à afficher
*/
public void setTurn(String s) {
text.setText(s);
}
}

View File

@@ -0,0 +1,217 @@
package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.AvalamBoard;
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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Bot Alpha-Beta avec cut-off (profondeur maximale).
*
* Idée :
* - si on atteint la profondeur limite, on évalue la position (heuristique).
* - sinon, on explore les coups avec Alpha-Beta (minimax optimisé).
*
*
*/
public class AlphaBetaBot extends AbstractGamePlayer {
//Attributs
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
private final Player me;
/** Profondeur maximale de recherche (cut-off). */
private final int maxDepth;
/** Générateur aléatoire pour départager des coups de même valeur. */
private final Random rng = new Random();
//Constructeur
/**
* Construit un bot Alpha-Beta.
*
* @param p joueur contrôlé par ce bot
* @param maxDepth profondeur maximale de recherche
*/
public AlphaBetaBot(Player p, int maxDepth) {
super(p);
this.me = p;
this.maxDepth = Math.max(1, maxDepth);
}
//Méthodes
/**
* Méthode appelée par GameAPI : le bot doit choisir un coup.
*
* @param board copie sûre de l'état de jeu (IBoard)
* @return un coup (AbstractPly) ou null si aucun coup possible
*/
@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);
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);
}
}
return bestMoves.get(rng.nextInt(bestMoves.size()));
}
/**
* Fonction récursive Alpha-Beta.
*/
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
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;
}
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;
}
return best;
}
}
/**
* Convertit le résultat final en valeur très grande (victoire/défaite).
* Result est du point de vue de PLAYER1.
*/
private int terminalValue(IBoard board) {
Result r = board.getResult();
if (r == null) return 0;
boolean botIsP1 = (me == Player.PLAYER1);
if (r == Result.DRAW) return 0;
if (botIsP1) {
return (r == Result.WIN) ? 100000 : -100000;
} else {
return (r == Result.LOSS) ? 100000 : -100000;
}
}
/**
* Heuristique simple Avalam :
* score = (tours du bot) - (tours adverses)
*/
private int evaluate(IBoard board) {
if (!(board instanceof AvalamBoard)) return 0;
AvalamBoard b = (AvalamBoard) board;
Color botColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
Color oppColor = (me == Player.PLAYER1) ? Color.RED : Color.YELLOW;
int botTowers = 0;
int oppTowers = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
for (int c = 0; c < AvalamBoard.SIZE; c++) {
Tower t = b.getTowerAt(r, c);
if (t == null) continue;
if (t.getColor() == botColor) botTowers++;
else if (t.getColor() == oppColor) oppTowers++;
}
}
return botTowers - oppTowers;
}
/**
* Récupère tous les coups légaux via iterator().
*/
private List<AbstractPly> listMoves(IBoard board) {
List<AbstractPly> moves = new ArrayList<>();
Iterator<AbstractPly> it = board.iterator();
while (it.hasNext()) moves.add(it.next());
return moves;
}
//Affichage
}

View File

@@ -0,0 +1,185 @@
package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.*;
import fr.iut_fbleau.GameAPI.*;
import java.util.*;
/**
* Bot "Divin" (alpha-beta + évaluateur pondéré).
* * Idée :
* - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
* - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
*/
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;
for (AbstractPly m : listMoves(board)) {
IBoard next = board.safeCopy();
next.doPly(m);
int val = alphaBeta(next, depth - 1, alpha, beta);
if (isMax) {
alpha = Math.max(alpha, val);
if (alpha >= beta) break; // Coupure Beta
} else {
beta = Math.min(beta, val);
if (alpha >= beta) break; // Coupure Alpha
}
}
return isMax ? alpha : beta;
}
/**
* Calcule la valeur de l'état final (Victoire / Défaite).
*/
private int terminalValue(IBoard board) {
Result r = board.getResult();
if (r == null) return 0;
if (r == Result.DRAW) return 0;
boolean botIsP1 = (me == Player.PLAYER1);
// Si le bot gagne, valeur positive élevée, sinon valeur négative
return ((r == Result.WIN) == botIsP1) ? 100000 : -100000;
}
/**
* Heuristique évoluée pour Avalam :
* Calcule un score basé sur le contrôle des tours et leur hauteur.
* Les tours de hauteur 5 sont prioritaires car elles sont bloquées.
*/
private int evaluate(IBoard board) {
if (!(board instanceof AvalamBoard)) return 0;
AvalamBoard b = (AvalamBoard) board;
Color myColor = (me == Player.PLAYER1) ? Color.YELLOW : Color.RED;
Color oppColor = (myColor == Color.YELLOW) ? Color.RED : Color.YELLOW;
int score = 0;
for (int r = 0; r < AvalamBoard.SIZE; r++) {
for (int c = 0; c < AvalamBoard.SIZE; c++) {
Tower t = b.getTowerAt(r, c);
if (t == null) continue;
int h = t.getHeight();
// Pondération selon la hauteur (heuristique "Divine")
int value =
(h == 5) ? 1000 :
(h == 4) ? 300 :
(h == 3) ? 120 :
(h == 2) ? 40 : 10;
if (t.getColor() == myColor) score += value;
else score -= value;
}
}
return score;
}
/**
* Génère la liste de tous les coups possibles sur le plateau donné.
*/
private List<AbstractPly> listMoves(IBoard board) {
List<AbstractPly> moves = new ArrayList<>();
board.iterator().forEachRemaining(moves::add);
return moves;
}
}

View File

@@ -0,0 +1,43 @@
package fr.iut_fbleau.Bot;
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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* Bot idiot : choisit un coup légal au hasard parmi ceux retournés par IBoard.iterator().
* Compatible avec n'importe quel jeu respectant GameAPI (dont AvalamBoard).
*/
public class IdiotBot extends AbstractGamePlayer {
private final Random rng;
public IdiotBot(Player p) {
super(p);
this.rng = new Random();
}
@Override
public AbstractPly giveYourMove(IBoard board) {
// Si la partie est terminée ou qu'il n'y a pas de coups, on ne joue rien.
if (board == null || board.isGameOver()) return null;
Iterator<AbstractPly> it = board.iterator();
List<AbstractPly> moves = new ArrayList<>();
while (it.hasNext()) {
moves.add(it.next());
}
if (moves.isEmpty()) return null;
return moves.get(rng.nextInt(moves.size()));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

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