11 Commits

14 changed files with 1103 additions and 290 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

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

@@ -2,7 +2,7 @@ package fr.iut_fbleau.Avalam;
import fr.iut_fbleau.Bot.AlphaBetaBot; import fr.iut_fbleau.Bot.AlphaBetaBot;
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// import fr.iut_fbleau.Bot.DivineBot; import fr.iut_fbleau.Bot.DivineBot;
import fr.iut_fbleau.Bot.IdiotBot; import fr.iut_fbleau.Bot.IdiotBot;
import fr.iut_fbleau.GameAPI.AbstractPly; import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.Player; import fr.iut_fbleau.GameAPI.Player;
@@ -59,6 +59,8 @@ public class AvalamWindow extends JFrame {
/** Bot Alpha-Beta (utilisé si mode PVALPHA). */ /** Bot Alpha-Beta (utilisé si mode PVALPHA). */
private final AlphaBetaBot alphaBot; private final AlphaBetaBot alphaBot;
private final DivineBot divineBot;
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// /** Bot Divin (utilisé si mode PVGOD). */ // /** Bot Divin (utilisé si mode PVGOD). */
// private final DivineBot divineBot; // private final DivineBot divineBot;
@@ -68,7 +70,6 @@ public class AvalamWindow extends JFrame {
* On garde l'attribut à null pour ne pas casser la compilation, * On garde l'attribut à null pour ne pas casser la compilation,
* mais toute la logique PVGOD est désactivée/commentée. * mais toute la logique PVGOD est désactivée/commentée.
*/ */
private final Object divineBot = null;
/** Indique si une animation de tour de bot est en cours. */ /** Indique si une animation de tour de bot est en cours. */
private boolean botAnimating = false; private boolean botAnimating = false;
@@ -110,7 +111,7 @@ public class AvalamWindow extends JFrame {
this.alphaBot = (mode == GameMode.PVALPHA) ? new AlphaBetaBot(botPlayer, depth) : null; this.alphaBot = (mode == GameMode.PVALPHA) ? new AlphaBetaBot(botPlayer, depth) : null;
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// this.divineBot = (mode == GameMode.PVGOD) ? new DivineBot(botPlayer, depth) : null; this.divineBot = (mode == GameMode.PVGOD) ? new DivineBot(botPlayer, depth) : null;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
@@ -220,11 +221,10 @@ public class AvalamWindow extends JFrame {
if (mode == GameMode.PVALPHA && alphaBot == null) return; if (mode == GameMode.PVALPHA && alphaBot == null) return;
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// if (mode == GameMode.PVGOD && divineBot == null) return; if (mode == GameMode.PVGOD && divineBot == null) return;
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// Pour l'instant, si PVGOD est sélectionné, on ne joue pas de coup bot. // Pour l'instant, si PVGOD est sélectionné, on ne joue pas de coup bot.
if (mode == GameMode.PVGOD) return;
botAnimating = true; botAnimating = true;
@@ -239,8 +239,7 @@ public class AvalamWindow extends JFrame {
botMove = alphaBot.giveYourMove(board.safeCopy()); botMove = alphaBot.giveYourMove(board.safeCopy());
} else { } else {
// A FAIRE PLUS TARD (PVGOD) // A FAIRE PLUS TARD (PVGOD)
// botMove = divineBot.giveYourMove(board.safeCopy()); botMove = divineBot.giveYourMove(board.safeCopy());
botMove = null;
} }
if (botMove == null) { if (botMove == null) {

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ 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)
* - les clics via InteractionController * - les clics via InteractionController
* *
* Cette classe ne contient aucune logique de règles du jeu. * Cette classe ne contient aucune logique de règles du jeu.
@@ -156,37 +156,4 @@ public class BoardView extends JLayeredPane {
} }
return grid; return grid;
} }
//Affichage
/**
* Composant affichant limage de fond.
*/
private static class BackgroundLayer extends JComponent {
private Image img;
/**
* Construit une couche de fond.
*
* @param resourcePath chemin de l'image de fond
*/
public BackgroundLayer(String resourcePath) {
img = Toolkit.getDefaultToolkit().getImage(
getClass().getClassLoader().getResource(resourcePath)
);
}
/**
* Dessine l'image de fond.
*
* @param g contexte graphique
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
}
}
}
} }

View File

@@ -7,5 +7,6 @@ public enum GameMode {
PVP, // joueur vs joueur PVP, // joueur vs joueur
PVBOT, // joueur vs bot idiot PVBOT, // joueur vs bot idiot
PVALPHA, // joueur vs bot alpha PVALPHA, // joueur vs bot alpha
PVGOD // joueur vs bot stratégique PVGOD, // joueur vs bot stratégique
ARENA // bot vs bot (mode arène)
} }

View File

@@ -8,14 +8,22 @@ import javax.swing.*;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { 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 = { String[] options = {
"joueur vs joueur", "joueur vs joueur",
"joueur vs botidiot", "joueur vs botidiot",
"joueur vs bot alpha", "joueur vs bot alpha",
"joueur vs bot divin (NON IMPLEMENTE)" "joueur vs bot divin (NON IMPLEMENTE)",
"Arène"
}; };
int choice = JOptionPane.showOptionDialog( int choice = JOptionPane.showOptionDialog(
@@ -31,6 +39,12 @@ public class Main {
if (choice == -1) System.exit(0); if (choice == -1) System.exit(0);
// Mode Arène
if (choice == 4) {
new ArenaWindow();
return;
}
GameMode mode; GameMode mode;
if (choice == 1) mode = GameMode.PVBOT; if (choice == 1) mode = GameMode.PVBOT;
else if (choice == 2) mode = GameMode.PVALPHA; else if (choice == 2) mode = GameMode.PVALPHA;
@@ -64,6 +78,5 @@ public class Main {
} }
new AvalamWindow(mode); 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

@@ -1,22 +1,185 @@
package fr.iut_fbleau.Bot; package fr.iut_fbleau.Bot;
import fr.iut_fbleau.Avalam.AvalamBoard; import fr.iut_fbleau.Avalam.*;
import fr.iut_fbleau.Avalam.AvalamPly; import fr.iut_fbleau.GameAPI.*;
import fr.iut_fbleau.Avalam.Color;
import fr.iut_fbleau.Avalam.Tower;
import fr.iut_fbleau.GameAPI.AbstractGamePlayer;
import fr.iut_fbleau.GameAPI.AbstractPly;
import fr.iut_fbleau.GameAPI.IBoard;
import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result;
import java.util.*; import java.util.*;
/** /**
* Bot "Divin" (fort) pour Avalam. * Bot "Divin" (alpha-beta + évaluateur pondéré).
* * * Idée :
* * - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
* Objectif : trop fort. */ * - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
public class DivineBot{ */
public class DivineBot extends AbstractGamePlayer {
// Attributs
/** Joueur contrôlé par ce bot (PLAYER1 ou PLAYER2). */
private final Player me;
/** Profondeur maximale de recherche avant évaluation. */
private final int maxDepth;
/** Générateur aléatoire pour choisir parmi les meilleurs coups équivalents. */
private final Random rng = new Random();
// Constructeur
/**
* Construit le bot Divine.
*
* @param p joueur contrôlé par ce bot
* @param maxDepth profondeur de l'arbre de recherche
*/
public DivineBot(Player p, int maxDepth) {
super(p);
this.me = p;
this.maxDepth = Math.max(1, maxDepth);
}
// Méthodes
/**
* Méthode principale de décision du bot.
* Explore le premier niveau de l'arbre et lance les appels Alpha-Beta.
* * @param board état actuel du jeu
* @return le meilleur coup calculé (AbstractPly)
*/
@Override
public AbstractPly giveYourMove(IBoard board) {
if (board == null || board.isGameOver()) return null;
List<AbstractPly> moves = listMoves(board);
if (moves.isEmpty()) return null;
boolean isMax = board.getCurrentPlayer() == me;
int bestValue = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
List<AbstractPly> bestMoves = new ArrayList<>();
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
for (AbstractPly m : moves) {
IBoard next = board.safeCopy();
next.doPly(m);
// Appel récursif pour évaluer la suite du coup
int value = alphaBeta(next, maxDepth - 1, alpha, beta);
if (isMax) {
if (value > bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
alpha = Math.max(alpha, bestValue);
} else {
if (value < bestValue) {
bestValue = value;
bestMoves.clear();
bestMoves.add(m);
} else if (value == bestValue) {
bestMoves.add(m);
}
beta = Math.min(beta, bestValue);
}
}
// Retourne un coup au hasard parmi les meilleurs ex-aequo
return bestMoves.get(rng.nextInt(bestMoves.size()));
}
/**
* Algorithme récursif de recherche avec élagage Alpha-Beta.
*/
private int alphaBeta(IBoard board, int depth, int alpha, int beta) {
// Cas de base : fin de partie ou limite de profondeur atteinte
if (board.isGameOver()) return terminalValue(board);
if (depth == 0) return evaluate(board);
boolean isMax = board.getCurrentPlayer() == me;
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;
}
} }