19 Commits

Author SHA1 Message Date
f43361a48f Ajout vérification correspondance du Joueur (Test Ok) 2026-02-05 16:06:03 +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
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
20 changed files with 1747 additions and 261 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,6 @@
# === Environnements ===
TEST_ENV = "bin:/usr/share/java/junit.jar:/usr/share/java/hamcrest-core.jar"
# === Répertoires === # === Répertoires ===
SRC_DIR = fr SRC_DIR = fr
BIN_DIR = bin BIN_DIR = bin
@@ -12,12 +15,16 @@ SOURCES := $(shell find $(SRC_DIR) -name "*.java")
# === 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
JCFLAGS = -d $(BIN_DIR) JCFLAGS = -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
@@ -43,6 +50,12 @@ run:
@echo "===> Lancement du jeu Avalam..." @echo "===> Lancement du jeu Avalam..."
@$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS) @$(JAVA) $(JAVAFLAGS) $(MAIN_CLASS)
# === Tests ===
test:
@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,9 +4,11 @@
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**.
@@ -17,6 +19,11 @@ Le jeu de notre groupe est **Avalam**.
make build make build
``` ```
### Tests
```bash
make test
```
### Exécution ### Exécution
```bash ```bash
make run make run
@@ -78,13 +85,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

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

View File

@@ -205,6 +205,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;
@@ -280,12 +281,23 @@ public class AvalamBoard extends AbstractBoard {
*/ */
@Override @Override
public IBoard safeCopy() { public IBoard safeCopy() {
Tower[][] newGrid = new Tower[SIZE][SIZE]; Tower[][] newGrid = new Tower[SIZE][SIZE];
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++) {
newGrid[r][c] = grid[r][c];
return new AvalamBoard(newGrid, getCurrentPlayer(), gameOver, result); Tower t = grid[r][c];
if (t == null) {
newGrid[r][c] = null;
} else {
// Copie profonde : on recrée une nouvelle Tower indépendante
newGrid[r][c] = new Tower(t.getHeight(), t.getColor());
}
}
}
// On conserve le joueur courant
return new AvalamBoard(newGrid, getCurrentPlayer());
} }
} }

View File

@@ -1,5 +1,10 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Bot.AlphaBetaBot;
// A FAIRE PLUS TARD (PVGOD)
import fr.iut_fbleau.Bot.DivineBot;
import fr.iut_fbleau.Bot.IdiotBot;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player; import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result; import fr.iut_fbleau.GameAPI.Result;
@@ -12,10 +17,19 @@ import java.awt.*;
* Fenêtre principale (interface graphique) du jeu Avalam. * Fenêtre principale (interface graphique) du jeu Avalam.
* Elle contient : * Elle contient :
* - le plateau (BoardView) * - le plateau (BoardView)
* - l'affichage du score (ScoreView) * - laffichage du score (ScoreView)
* - l'affichage du joueur courant (TurnView) * - laffichage du joueur courant (TurnView)
* *
* Elle pilote un objet <code>AvalamBoard</code> (moteur du jeu). * 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)
*
* @version 1.0
* Date :
* Licence :
*/ */
public class AvalamWindow extends JFrame { public class AvalamWindow extends JFrame {
@@ -24,36 +38,91 @@ public class AvalamWindow extends JFrame {
/** Moteur du jeu (état + règles). */ /** Moteur du jeu (état + règles). */
private AvalamBoard board; private AvalamBoard board;
/** Vue affichant le score des deux couleurs. */ /** Vue affichant le score. */
private ScoreView scoreView; private ScoreView scoreView;
/** Vue affichant le joueur dont c'est le tour. */ /** Vue affichant le joueur courant. */
private TurnView turnView; private TurnView turnView;
/** Vue affichant le plateau et gérant les interactions de jeu. */ /** Vue affichant le plateau. */
private BoardView boardView; private BoardView boardView;
/** Mode de jeu sélectionné. */
private final GameMode mode;
/** 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;
private final DivineBot divineBot;
// A FAIRE PLUS TARD (PVGOD)
// /** Bot Divin (utilisé si mode PVGOD). */
// private final DivineBot divineBot;
/**
* A FAIRE PLUS TARD (PVGOD)
* On garde l'attribut à null pour ne pas casser la compilation,
* mais toute la logique PVGOD est désactivée/commentée.
*/
/** Indique si une animation de tour de bot est en cours. */
private boolean botAnimating = false;
//Constructeur //Constructeur
/** /**
* Construit la fenêtre et initialise linterface : * Construit la fenêtre en mode joueur vs joueur.
* - charge le plateau initial depuis Plateau.txt
* - construit les vues (score, tour, plateau)
* - affiche la fenêtre
*/ */
public AvalamWindow() { public AvalamWindow() {
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
*/
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.
*
* @param mode mode de jeu
* @param alphaDepth profondeur de recherche pour Alpha-Beta / Bot Divin
*/
public AvalamWindow(GameMode mode, int alphaDepth) {
super("Avalam"); super("Avalam");
this.mode = mode;
this.idiotBot = (mode == GameMode.PVBOT) ? new IdiotBot(botPlayer) : null;
int depth = Math.max(1, alphaDepth);
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;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
// Chargement du plateau initial depuis Plateau.txt // Chargement du plateau initial
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt"); Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
// Initialisation du moteur (PLAYER1 commence) // Initialisation du moteur (PLAYER1 commence)
board = new AvalamBoard(initialGrid); board = new AvalamBoard(initialGrid);
// Création du panneau supérieur (score + tour) // Panneau supérieur (score + tour)
JPanel topPanel = new JPanel(new GridLayout(2, 1)); JPanel topPanel = new JPanel(new GridLayout(2, 1));
topPanel.setBackground(new java.awt.Color(200, 200, 200)); topPanel.setBackground(new java.awt.Color(200, 200, 200));
@@ -69,7 +138,7 @@ public class AvalamWindow extends JFrame {
add(topPanel, BorderLayout.NORTH); add(topPanel, BorderLayout.NORTH);
// Création de la vue plateau (avec callback de mise à jour) // Plateau
boardView = new BoardView(board, this::onBoardUpdated); boardView = new BoardView(board, this::onBoardUpdated);
add(boardView, BorderLayout.CENTER); add(boardView, BorderLayout.CENTER);
@@ -77,35 +146,30 @@ public class AvalamWindow extends JFrame {
setResizable(false); setResizable(false);
setLocationRelativeTo(null); setLocationRelativeTo(null);
setVisible(true); setVisible(true);
// Si un jour le bot devait commencer (pas le cas ici), on le ferait jouer ici.
maybePlayBotTurn();
} }
//Méthodes //Méthodes
/** /**
* Méthode appelée automatiquement après chaque coup (via BoardView). * Appelée après chaque coup (humain ou bot).
* Elle rafraîchit : * Met à jour score, tour, et affiche la fin de partie.
* - les scores
* - le joueur courant
* et affiche un message si la partie est terminée.
*/ */
public void onBoardUpdated() { public void onBoardUpdated() {
// Mise à jour du score
scoreView.updateScores( scoreView.updateScores(
computeScore(Color.YELLOW), computeScore(Color.YELLOW),
computeScore(Color.RED) computeScore(Color.RED)
); );
// Mise à jour du joueur courant
turnView.setTurn(turnMessage()); turnView.setTurn(turnMessage());
// Détection de fin de partie
if (board.isGameOver()) { if (board.isGameOver()) {
Result res = board.getResult(); Result res = board.getResult();
String msg; String msg;
// Correction : ajout des "break" pour éviter le fall-through.
switch (res) { switch (res) {
case WIN: case WIN:
msg = "Le joueur jaune a gagné !"; msg = "Le joueur jaune a gagné !";
@@ -127,11 +191,102 @@ public class AvalamWindow extends JFrame {
"Partie terminée", "Partie terminée",
JOptionPane.INFORMATION_MESSAGE JOptionPane.INFORMATION_MESSAGE
); );
return;
} }
// Si on est contre un bot et que cest son tour, on déclenche son animation.
maybePlayBotTurn();
} }
/** /**
* Calcule le score d'une couleur : nombre de tours contrôlées (sommet de la tour). * 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;
// A FAIRE PLUS TARD (PVGOD)
if (mode == GameMode.PVGOD && divineBot == null) return;
// A FAIRE PLUS TARD (PVGOD)
// Pour l'instant, si PVGOD est sélectionné, on ne joue pas de coup bot.
botAnimating = true;
// 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 * @param c couleur à compter
* @return nombre de tours appartenant à la couleur c * @return nombre de tours appartenant à la couleur c
@@ -150,12 +305,14 @@ public class AvalamWindow extends JFrame {
} }
/** /**
* Retourne le message correspondant au joueur dont c'est le tour. * Retourne le message affiché pour le joueur courant.
* *
* @return message daffichage du tour * @return message du tour
*/ */
private String turnMessage() { private String turnMessage() {
return "Tour du joueur : " + return "Tour du joueur : " +
(board.getCurrentPlayer() == Player.PLAYER1 ? "Jaune" : "Rouge"); (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

@@ -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,8 +1,5 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Avalam.AvalamBoard;
import fr.iut_fbleau.Avalam.Tower;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -13,34 +10,38 @@ import java.awt.*;
* 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)
* - la gestion des interactions via InteractionController * - les clics via InteractionController
* *
* Cette classe ne contient aucune logique de jeu. * Cette classe ne contient aucune logique de règles du jeu.
*
* @version 1.0
* Date :
* Licence :
*/ */
public class BoardView extends JLayeredPane { public class BoardView extends JLayeredPane {
//Attributs //Attributs
/** Référence au moteur du jeu Avalam. */ /** Référence au moteur Avalam. */
private AvalamBoard board; private AvalamBoard board;
/** Couche graphique du fond du plateau. */ /** Couche daffichage du fond. */
private BackgroundLayer backgroundLayer; private BackgroundLayer backgroundLayer;
/** Couche graphique des déplacements possibles. */ /** Couche daffichage des coups possibles. */
private HighlightLayer highlightLayer; private HighlightLayer highlightLayer;
/** Couche graphique des pièces (tours). */ /** Couche daffichage des pièces. */
private PieceLayer pieceLayer; private PieceLayer pieceLayer;
/** Contrôleur des interactions utilisateur. */ /** 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 deux cases du plateau. */ /** Espacement entre les cases. */
private final int spacing = 70; private final int spacing = 70;
/** Décalage horizontal du plateau. */ /** Décalage horizontal du plateau. */
@@ -49,16 +50,16 @@ public class BoardView extends JLayeredPane {
/** Décalage vertical du plateau. */ /** Décalage vertical du plateau. */
private final int yBase = 60; private final int yBase = 60;
/** Callback vers AvalamWindow pour mettre à jour linterface (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 Avalam. * Construit la vue du plateau.
* *
* @param board moteur du jeu Avalam * @param board moteur du jeu
* @param boardUpdateCallback fonction de rappel 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;
@@ -66,19 +67,19 @@ public class BoardView extends JLayeredPane {
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 des pièces --- // Couche pièces
pieceLayer = new PieceLayer(); pieceLayer = new PieceLayer();
add(pieceLayer, JLayeredPane.PALETTE_LAYER); add(pieceLayer, JLayeredPane.PALETTE_LAYER);
@@ -89,6 +90,29 @@ public class BoardView extends JLayeredPane {
//Méthodes //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. * Appelé par le contrôleur après un coup.
*/ */
@@ -99,7 +123,7 @@ public class BoardView extends JLayeredPane {
} }
/** /**
* Rafraîchit laffichage du plateau. * Rafraîchit les couches visuelles.
*/ */
public void refresh() { public void refresh() {
@@ -118,7 +142,7 @@ public class BoardView extends JLayeredPane {
} }
/** /**
* Récupère la grille actuelle du moteur de jeu. * Récupère la grille depuis le moteur.
* *
* @return grille 9x9 de tours * @return grille 9x9 de tours
*/ */
@@ -132,35 +156,4 @@ public class BoardView extends JLayeredPane {
} }
return grid; return grid;
} }
//Affichage
/**
* Classe interne représentant la couche graphique du fond.
*/
private static class BackgroundLayer extends JComponent {
private Image img;
/**
* Construit une couche de fond à partir dune image.
*
* @param resourcePath chemin de limage
*/
public BackgroundLayer(String resourcePath) {
img = Toolkit.getDefaultToolkit().getImage(
getClass().getClassLoader().getResource(resourcePath)
);
}
/**
* Dessine limage de fond.
*/
@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,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

@@ -1,28 +1,82 @@
package fr.iut_fbleau.Avalam; package fr.iut_fbleau.Avalam;
import javax.swing.*;
/** /**
* La classe <code>Main</code> * Point dentrée : propose un menu de sélection de mode, puis lance la fenêtre Avalam.
*
* Point dentrée du programme.
* Lance linterface graphique principale (<code>AvalamWindow</code>).
*/ */
public class Main { public class Main {
//Attributs public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
//Constructeur showModeSelection();
public Main() { });
} }
//Méthodes
/** /**
* Méthode principale : démarre lapplication. * Affiche le menu de sélection du mode de jeu.
* * Peut être appelé depuis d'autres fenêtres pour revenir au menu.
* @param args arguments de la ligne de commande (non utilisés)
*/ */
public static void main(String[] args) { public static void showModeSelection() {
new AvalamWindow(); String[] options = {
"joueur vs joueur",
"joueur vs botidiot",
"joueur vs bot alpha",
"joueur vs bot divin (NON IMPLEMENTE)",
"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

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

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

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