Compare commits
4 Commits
bot
...
c0cd120b1e
| Author | SHA1 | Date | |
|---|---|---|---|
| c0cd120b1e | |||
| fa578b86d2 | |||
| 14e5df4332 | |||
| 6eb63cacaa |
15
Makefile
15
Makefile
@@ -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..."
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -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)** \
|
||||||
|
|||||||
@@ -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,8 +59,6 @@ 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;
|
||||||
@@ -70,6 +68,7 @@ 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;
|
||||||
@@ -111,7 +110,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());
|
||||||
@@ -221,10 +220,11 @@ 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,7 +239,8 @@ 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) {
|
||||||
|
|||||||
37
fr/iut_fbleau/Avalam/BackgroundLayer.java
Normal file
37
fr/iut_fbleau/Avalam/BackgroundLayer.java
Normal 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 l’image 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,192 +1,159 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>BoardView</code>
|
* La classe <code>BoardView</code>
|
||||||
*
|
*
|
||||||
* Représente la vue principale du plateau Avalam.
|
* Représente la vue principale du plateau Avalam.
|
||||||
* Elle gère :
|
* Elle gère :
|
||||||
* - l’affichage des tours (PieceLayer)
|
* - l’affichage des tours (PieceLayer)
|
||||||
* - l’affichage des coups possibles (HighlightLayer)
|
* - l’affichage des coups possibles (HighlightLayer)
|
||||||
* - l’affichage du fond graphique
|
* - l’affichage du fond graphique (BackgroundLayer)
|
||||||
* - les clics via InteractionController
|
* - les clics via InteractionController
|
||||||
*
|
*
|
||||||
* Cette classe ne contient aucune logique de règles du jeu.
|
* Cette classe ne contient aucune logique de règles du jeu.
|
||||||
*
|
*
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
* Date :
|
* Date :
|
||||||
* Licence :
|
* Licence :
|
||||||
*/
|
*/
|
||||||
public class BoardView extends JLayeredPane {
|
public class BoardView extends JLayeredPane {
|
||||||
|
|
||||||
//Attributs
|
//Attributs
|
||||||
|
|
||||||
/** Référence au moteur Avalam. */
|
/** Référence au moteur Avalam. */
|
||||||
private AvalamBoard board;
|
private AvalamBoard board;
|
||||||
|
|
||||||
/** Couche d’affichage du fond. */
|
/** Couche d’affichage du fond. */
|
||||||
private BackgroundLayer backgroundLayer;
|
private BackgroundLayer backgroundLayer;
|
||||||
|
|
||||||
/** Couche d’affichage des coups possibles. */
|
/** Couche d’affichage des coups possibles. */
|
||||||
private HighlightLayer highlightLayer;
|
private HighlightLayer highlightLayer;
|
||||||
|
|
||||||
/** Couche d’affichage des pièces. */
|
/** Couche d’affichage des pièces. */
|
||||||
private PieceLayer pieceLayer;
|
private PieceLayer pieceLayer;
|
||||||
|
|
||||||
/** Contrôleur des interactions. */
|
/** Contrôleur des interactions. */
|
||||||
private InteractionController controller;
|
private InteractionController controller;
|
||||||
|
|
||||||
/** Taille d’un pion en pixels. */
|
/** Taille d’un pion en pixels. */
|
||||||
private final int size = 50;
|
private final int size = 50;
|
||||||
|
|
||||||
/** Espacement entre les cases. */
|
/** Espacement entre les cases. */
|
||||||
private final int spacing = 70;
|
private final int spacing = 70;
|
||||||
|
|
||||||
/** Décalage horizontal du plateau. */
|
/** Décalage horizontal du plateau. */
|
||||||
private final int xBase = 60;
|
private final int xBase = 60;
|
||||||
|
|
||||||
/** Décalage vertical du plateau. */
|
/** Décalage vertical du plateau. */
|
||||||
private final int yBase = 60;
|
private final int yBase = 60;
|
||||||
|
|
||||||
/** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */
|
/** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */
|
||||||
private Runnable boardUpdateCallback;
|
private Runnable boardUpdateCallback;
|
||||||
|
|
||||||
//Constructeur
|
//Constructeur
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construit la vue du plateau.
|
* Construit la vue du plateau.
|
||||||
*
|
*
|
||||||
* @param board moteur du jeu
|
* @param board moteur du jeu
|
||||||
* @param boardUpdateCallback callback à appeler après un coup
|
* @param boardUpdateCallback callback à appeler après un coup
|
||||||
*/
|
*/
|
||||||
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
|
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
|
||||||
this.board = board;
|
this.board = board;
|
||||||
this.boardUpdateCallback = boardUpdateCallback;
|
this.boardUpdateCallback = boardUpdateCallback;
|
||||||
|
|
||||||
setLayout(null);
|
setLayout(null);
|
||||||
|
|
||||||
// Contrôleur
|
// Contrôleur
|
||||||
this.controller = new InteractionController(board, this);
|
this.controller = new InteractionController(board, this);
|
||||||
|
|
||||||
// Couche fond
|
// Couche fond
|
||||||
backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png");
|
backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png");
|
||||||
backgroundLayer.setBounds(0, 0, 725, 725);
|
backgroundLayer.setBounds(0, 0, 725, 725);
|
||||||
add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER);
|
add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER);
|
||||||
|
|
||||||
// Couche highlight
|
// Couche highlight
|
||||||
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
|
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
|
||||||
add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
|
add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
|
||||||
|
|
||||||
// Couche pièces
|
// Couche pièces
|
||||||
pieceLayer = new PieceLayer();
|
pieceLayer = new PieceLayer();
|
||||||
add(pieceLayer, JLayeredPane.PALETTE_LAYER);
|
add(pieceLayer, JLayeredPane.PALETTE_LAYER);
|
||||||
|
|
||||||
setPreferredSize(new Dimension(725, 725));
|
setPreferredSize(new Dimension(725, 725));
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Méthodes
|
//Méthodes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne le contrôleur d'interactions (utile pour simuler les clics du bot).
|
* Retourne le contrôleur d'interactions (utile pour simuler les clics du bot).
|
||||||
*
|
*
|
||||||
* @return contrôleur
|
* @return contrôleur
|
||||||
*/
|
*/
|
||||||
public InteractionController getController() {
|
public InteractionController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Active/désactive les interactions utilisateur sur le plateau.
|
* Active/désactive les interactions utilisateur sur le plateau.
|
||||||
* Utile pendant l'animation du bot pour éviter des clics concurrents.
|
* Utile pendant l'animation du bot pour éviter des clics concurrents.
|
||||||
*
|
*
|
||||||
* @param enabled true pour activer, false pour désactiver
|
* @param enabled true pour activer, false pour désactiver
|
||||||
*/
|
*/
|
||||||
public void setInteractionEnabled(boolean enabled) {
|
public void setInteractionEnabled(boolean enabled) {
|
||||||
// Désactive la couche des pièces (boutons) principalement
|
// Désactive la couche des pièces (boutons) principalement
|
||||||
pieceLayer.setEnabled(enabled);
|
pieceLayer.setEnabled(enabled);
|
||||||
for (Component c : pieceLayer.getComponents()) {
|
for (Component c : pieceLayer.getComponents()) {
|
||||||
c.setEnabled(enabled);
|
c.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appelé par le contrôleur après un coup.
|
* Appelé par le contrôleur après un coup.
|
||||||
*/
|
*/
|
||||||
public void onBoardUpdated() {
|
public void onBoardUpdated() {
|
||||||
if (boardUpdateCallback != null) {
|
if (boardUpdateCallback != null) {
|
||||||
boardUpdateCallback.run();
|
boardUpdateCallback.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rafraîchit les couches visuelles.
|
* Rafraîchit les couches visuelles.
|
||||||
*/
|
*/
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
|
|
||||||
pieceLayer.displayGrid(
|
pieceLayer.displayGrid(
|
||||||
boardGrid(),
|
boardGrid(),
|
||||||
xBase, yBase, spacing, size,
|
xBase, yBase, spacing, size,
|
||||||
(r, c) -> controller.onPieceClicked(r, c)
|
(r, c) -> controller.onPieceClicked(r, c)
|
||||||
);
|
);
|
||||||
|
|
||||||
highlightLayer.setLegalMoves(controller.getLegalMoves());
|
highlightLayer.setLegalMoves(controller.getLegalMoves());
|
||||||
|
|
||||||
backgroundLayer.repaint();
|
backgroundLayer.repaint();
|
||||||
highlightLayer.repaint();
|
highlightLayer.repaint();
|
||||||
pieceLayer.repaint();
|
pieceLayer.repaint();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère la grille depuis le moteur.
|
* Récupère la grille depuis le moteur.
|
||||||
*
|
*
|
||||||
* @return grille 9x9 de tours
|
* @return grille 9x9 de tours
|
||||||
*/
|
*/
|
||||||
private Tower[][] boardGrid() {
|
private Tower[][] boardGrid() {
|
||||||
Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
|
Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
|
||||||
|
|
||||||
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
||||||
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
||||||
grid[r][c] = board.getTowerAt(r, c);
|
grid[r][c] = board.getTowerAt(r, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//Affichage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Composant affichant l’image 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,185 +1,22 @@
|
|||||||
package fr.iut_fbleau.Bot;
|
package fr.iut_fbleau.Bot;
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.*;
|
import fr.iut_fbleau.Avalam.AvalamBoard;
|
||||||
import fr.iut_fbleau.GameAPI.*;
|
import fr.iut_fbleau.Avalam.AvalamPly;
|
||||||
|
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" (alpha-beta + évaluateur pondéré).
|
* Bot "Divin" (fort) pour Avalam.
|
||||||
* * Idée :
|
*
|
||||||
* - Utilise l'algorithme Alpha-Beta pour anticiper les coups.
|
*
|
||||||
* - Évalue les plateaux non terminaux en accordant plus d'importance aux tours hautes.
|
* Objectif : trop fort. */
|
||||||
*/
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
123
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal file
123
fr/iut_fbleau/Tests/AvalamBoardTest.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user