Compare commits
23 Commits
d95d52785e
...
tests
| Author | SHA1 | Date | |
|---|---|---|---|
| dea162182b | |||
| c0cd120b1e | |||
| fa578b86d2 | |||
| 32f77e5495 | |||
| 7ae0d69aaa | |||
| fb0b8da097 | |||
| 14e5df4332 | |||
| 6eb63cacaa | |||
|
|
b4f93e7647 | ||
|
|
be459707ce | ||
|
|
d907d2d52b | ||
| fdba5f0f2f | |||
| 6269404e7d | |||
| 4e8528fd58 | |||
| fa493f2fc1 | |||
| aba891e060 | |||
| 27663cd583 | |||
| 031b23c7c5 | |||
| b97b9cef69 | |||
| 25f8dcdc76 | |||
| 823b0fcae0 | |||
| 9f78dad6e5 | |||
| 830729ca21 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Ignorer le répertoire bin
|
||||||
|
/bin
|
||||||
218
Diagrammes/Diagramme_Avalam.mmd
Normal file
218
Diagrammes/Diagramme_Avalam.mmd
Normal 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
|
||||||
27
Diagrammes/Diagramme_Bot.mmd
Normal file
27
Diagrammes/Diagramme_Bot.mmd
Normal 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
|
||||||
|
}
|
||||||
73
Diagrammes/Diagramme_GameAPI
Normal file
73
Diagrammes/Diagramme_GameAPI
Normal 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
|
||||||
|
}
|
||||||
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)** \
|
||||||
|
|||||||
44
fr/iut_fbleau/Avalam/ArenaGame.java
Normal file
44
fr/iut_fbleau/Avalam/ArenaGame.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
280
fr/iut_fbleau/Avalam/ArenaWindow.java
Normal file
280
fr/iut_fbleau/Avalam/ArenaWindow.java
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,15 +9,44 @@ import fr.iut_fbleau.GameAPI.Result;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>AvalamBoard</code>
|
||||||
|
*
|
||||||
|
* Représente le plateau et les règles du jeu Avalam.
|
||||||
|
* Cette classe étend <code>AbstractBoard</code> (GameAPI) et fournit :
|
||||||
|
* - la génération des coups (iterator)
|
||||||
|
* - le test de légalité (isLegal)
|
||||||
|
* - l’application d’un coup (doPly)
|
||||||
|
* - la détection de fin de partie (isGameOver)
|
||||||
|
* - le calcul du résultat (getResult)
|
||||||
|
*/
|
||||||
public class AvalamBoard extends AbstractBoard {
|
public class AvalamBoard extends AbstractBoard {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Taille du plateau Avalam (9x9). */
|
||||||
public static final int SIZE = 9;
|
public static final int SIZE = 9;
|
||||||
|
|
||||||
|
/** Hauteur maximale autorisée pour une tour après fusion. */
|
||||||
private static final int MAX_HEIGHT = 5;
|
private static final int MAX_HEIGHT = 5;
|
||||||
|
|
||||||
|
/** Grille du plateau : chaque case contient une tour (Tower) ou null si vide. */
|
||||||
private final Tower[][] grid;
|
private final Tower[][] grid;
|
||||||
|
|
||||||
|
/** Indique si la partie est terminée (mémoïsation). */
|
||||||
private boolean gameOver = false;
|
private boolean gameOver = false;
|
||||||
|
|
||||||
|
/** Résultat de la partie si elle est terminée (mémoïsation). */
|
||||||
private Result result = null;
|
private Result result = null;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit un plateau Avalam à partir d’une grille initiale et d’un joueur qui commence.
|
||||||
|
*
|
||||||
|
* @param initialGrid grille initiale (Tower ou null)
|
||||||
|
* @param startingPlayer joueur qui commence (PLAYER1 ou PLAYER2)
|
||||||
|
*/
|
||||||
public AvalamBoard(Tower[][] initialGrid, Player startingPlayer) {
|
public AvalamBoard(Tower[][] initialGrid, Player startingPlayer) {
|
||||||
super(startingPlayer, new ArrayDeque<>());
|
super(startingPlayer, new ArrayDeque<>());
|
||||||
this.grid = new Tower[SIZE][SIZE];
|
this.grid = new Tower[SIZE][SIZE];
|
||||||
@@ -27,10 +56,24 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
this.grid[r][c] = initialGrid[r][c];
|
this.grid[r][c] = initialGrid[r][c];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit un plateau Avalam à partir d’une grille initiale.
|
||||||
|
* Par défaut, PLAYER1 commence.
|
||||||
|
*
|
||||||
|
* @param initialGrid grille initiale (Tower ou null)
|
||||||
|
*/
|
||||||
public AvalamBoard(Tower[][] initialGrid) {
|
public AvalamBoard(Tower[][] initialGrid) {
|
||||||
this(initialGrid, Player.PLAYER1);
|
this(initialGrid, Player.PLAYER1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur interne utilisé par safeCopy().
|
||||||
|
*
|
||||||
|
* @param grid grille à réutiliser
|
||||||
|
* @param current joueur courant
|
||||||
|
* @param gameOver état “partie terminée”
|
||||||
|
* @param result résultat mémorisé
|
||||||
|
*/
|
||||||
private AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result) {
|
private AvalamBoard(Tower[][] grid, Player current, boolean gameOver, Result result) {
|
||||||
super(current, new ArrayDeque<>());
|
super(current, new ArrayDeque<>());
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
@@ -38,24 +81,61 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la tour située à la position (row, col), ou null si hors bornes ou vide.
|
||||||
|
*
|
||||||
|
* @param row ligne
|
||||||
|
* @param col colonne
|
||||||
|
* @return tour présente ou null
|
||||||
|
*/
|
||||||
public Tower getTowerAt(int row, int col) {
|
public Tower getTowerAt(int row, int col) {
|
||||||
return inBounds(row, col) ? grid[row][col] : null;
|
return inBounds(row, col) ? grid[row][col] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste si une position est à l’intérieur du plateau.
|
||||||
|
*
|
||||||
|
* @param r ligne
|
||||||
|
* @param c colonne
|
||||||
|
* @return true si (r,c) est dans [0..SIZE-1]
|
||||||
|
*/
|
||||||
private boolean inBounds(int r, int c) {
|
private boolean inBounds(int r, int c) {
|
||||||
return r >= 0 && r < SIZE && c >= 0 && c < SIZE;
|
return r >= 0 && r < SIZE && c >= 0 && c < SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste si deux cases sont adjacentes (8-voisinage).
|
||||||
|
*
|
||||||
|
* @param r1 ligne source
|
||||||
|
* @param c1 colonne source
|
||||||
|
* @param r2 ligne destination
|
||||||
|
* @param c2 colonne destination
|
||||||
|
* @return true si les cases sont voisines et différentes
|
||||||
|
*/
|
||||||
private boolean areAdjacent(int r1, int c1, int r2, int c2) {
|
private boolean areAdjacent(int r1, int c1, int r2, int c2) {
|
||||||
int dr = Math.abs(r1 - r2);
|
int dr = Math.abs(r1 - r2);
|
||||||
int dc = Math.abs(c1 - c2);
|
int dc = Math.abs(c1 - c2);
|
||||||
return (dr <= 1 && dc <= 1 && !(dr == 0 && dc == 0));
|
return (dr <= 1 && dc <= 1 && !(dr == 0 && dc == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associe un joueur GameAPI à une couleur Avalam.
|
||||||
|
*
|
||||||
|
* @param p joueur (PLAYER1/PLAYER2)
|
||||||
|
* @return couleur correspondante (YELLOW/RED)
|
||||||
|
*/
|
||||||
private Color colorForPlayer(Player p) {
|
private Color colorForPlayer(Player p) {
|
||||||
return (p == Player.PLAYER1 ? Color.YELLOW : Color.RED);
|
return (p == Player.PLAYER1 ? Color.YELLOW : Color.RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indique si la partie est terminée.
|
||||||
|
* Ici : fin lorsque l’itérateur de coups légaux ne produit plus aucun coup.
|
||||||
|
*
|
||||||
|
* @return true si aucun coup n’est possible
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isGameOver() {
|
public boolean isGameOver() {
|
||||||
if (gameOver) return true;
|
if (gameOver) return true;
|
||||||
@@ -67,6 +147,12 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le résultat si la partie est terminée.
|
||||||
|
* Règle utilisée ici : comparaison du nombre de tours contrôlées par chaque couleur.
|
||||||
|
*
|
||||||
|
* @return WIN / LOSS / DRAW ou null si partie non terminée
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result getResult() {
|
public Result getResult() {
|
||||||
if (!isGameOver()) return null;
|
if (!isGameOver()) return null;
|
||||||
@@ -91,6 +177,19 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste si un coup est légal selon les règles implémentées :
|
||||||
|
* - coup de type AvalamPly
|
||||||
|
* - source/destination dans le plateau et différentes
|
||||||
|
* - source et destination non null
|
||||||
|
* - la tour source appartient au joueur courant (couleur du sommet)
|
||||||
|
* - cases adjacentes
|
||||||
|
* - couleurs différentes entre source et destination (règle de ce projet)
|
||||||
|
* - hauteur finale <= MAX_HEIGHT
|
||||||
|
*
|
||||||
|
* @param c coup à tester
|
||||||
|
* @return true si le coup est légal
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isLegal(AbstractPly c) {
|
public boolean isLegal(AbstractPly c) {
|
||||||
if (!(c instanceof AvalamPly)) return false;
|
if (!(c instanceof AvalamPly)) return false;
|
||||||
@@ -114,6 +213,14 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applique un coup légal :
|
||||||
|
* - fusion de la tour source sur la destination
|
||||||
|
* - la case source devient vide
|
||||||
|
* - passage au joueur suivant via super.doPly
|
||||||
|
*
|
||||||
|
* @param c coup à jouer
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void doPly(AbstractPly c) {
|
public void doPly(AbstractPly c) {
|
||||||
if (!isLegal(c)) throw new IllegalArgumentException("Coup illégal : " + c);
|
if (!isLegal(c)) throw new IllegalArgumentException("Coup illégal : " + c);
|
||||||
@@ -135,6 +242,12 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne un itérateur sur tous les coups légaux du joueur courant.
|
||||||
|
* Génération brute : pour chaque case et chaque voisin (8 directions), on teste isLegal().
|
||||||
|
*
|
||||||
|
* @return itérateur de coups possibles (AbstractPly)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Iterator<AbstractPly> iterator() {
|
public Iterator<AbstractPly> iterator() {
|
||||||
java.util.List<AbstractPly> moves = new java.util.ArrayList<>();
|
java.util.List<AbstractPly> moves = new java.util.ArrayList<>();
|
||||||
@@ -159,14 +272,31 @@ public class AvalamBoard extends AbstractBoard {
|
|||||||
return moves.iterator();
|
return moves.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne une copie “sûre” de l’état du plateau.
|
||||||
|
* Ici, la grille est recopiée case par case (copie des références Tower).
|
||||||
|
*
|
||||||
|
* @return copie du plateau (IBoard)
|
||||||
|
*/
|
||||||
@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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,44 +4,43 @@ import fr.iut_fbleau.GameAPI.AbstractPly;
|
|||||||
import fr.iut_fbleau.GameAPI.Player;
|
import fr.iut_fbleau.GameAPI.Player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Représente un coup (ply) dans le jeu Avalam.
|
* La classe <code>AvalamPly</code>
|
||||||
*
|
*
|
||||||
* Un coup est défini par :
|
* Représente un coup (ply) dans le jeu Avalam.
|
||||||
* <ul>
|
* Un coup est défini par :
|
||||||
* <li>un joueur (PLAYER1 ou PLAYER2)</li>
|
* - un joueur (PLAYER1 ou PLAYER2)
|
||||||
* <li>une position source (xFrom, yFrom)</li>
|
* - une position source (xFrom, yFrom)
|
||||||
* <li>une position destination (xTo, yTo)</li>
|
* - une position destination (xTo, yTo)
|
||||||
* </ul>
|
*
|
||||||
*
|
* Cette classe ne vérifie pas la légalité : tout est délégué à <code>AvalamBoard</code>.
|
||||||
* Ces coordonnées seront utilisées par <code>AvalamBoard</code> pour :
|
*/
|
||||||
* <ul>
|
|
||||||
* <li>vérifier la légalité du coup</li>
|
|
||||||
* <li>fusionner les tours concernées</li>
|
|
||||||
* <li>mettre à jour la grille</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* Cette classe ne contient aucune logique de vérification : tout est délégué
|
|
||||||
* à <code>AvalamBoard.isLegal()</code> et <code>AvalamBoard.doPly()</code>.
|
|
||||||
*/
|
|
||||||
public class AvalamPly extends AbstractPly {
|
public class AvalamPly extends AbstractPly {
|
||||||
|
|
||||||
/** Coordonnées source */
|
//Attributs
|
||||||
|
|
||||||
|
/** Coordonnée ligne de la case source. */
|
||||||
private final int xFrom;
|
private final int xFrom;
|
||||||
|
|
||||||
|
/** Coordonnée colonne de la case source. */
|
||||||
private final int yFrom;
|
private final int yFrom;
|
||||||
|
|
||||||
/** Coordonnées destination */
|
/** Coordonnée ligne de la case destination. */
|
||||||
private final int xTo;
|
private final int xTo;
|
||||||
|
|
||||||
|
/** Coordonnée colonne de la case destination. */
|
||||||
private final int yTo;
|
private final int yTo;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur principal.
|
* Construit un coup Avalam.
|
||||||
*
|
*
|
||||||
* @param player joueur qui joue le coup
|
* @param player joueur qui joue le coup
|
||||||
* @param xFrom ligne d'origine
|
* @param xFrom ligne d'origine
|
||||||
* @param yFrom colonne d'origine
|
* @param yFrom colonne d'origine
|
||||||
* @param xTo ligne de destination
|
* @param xTo ligne de destination
|
||||||
* @param yTo colonne de destination
|
* @param yTo colonne de destination
|
||||||
*/
|
*/
|
||||||
public AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo) {
|
public AvalamPly(Player player, int xFrom, int yFrom, int xTo, int yTo) {
|
||||||
super(player);
|
super(player);
|
||||||
this.xFrom = xFrom;
|
this.xFrom = xFrom;
|
||||||
@@ -50,26 +49,51 @@ public class AvalamPly extends AbstractPly {
|
|||||||
this.yTo = yTo;
|
this.yTo = yTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ligne d'origine */
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la ligne d'origine.
|
||||||
|
*
|
||||||
|
* @return ligne source
|
||||||
|
*/
|
||||||
public int getXFrom() {
|
public int getXFrom() {
|
||||||
return xFrom;
|
return xFrom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Colonne d'origine */
|
/**
|
||||||
|
* Retourne la colonne d'origine.
|
||||||
|
*
|
||||||
|
* @return colonne source
|
||||||
|
*/
|
||||||
public int getYFrom() {
|
public int getYFrom() {
|
||||||
return yFrom;
|
return yFrom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ligne de destination */
|
/**
|
||||||
|
* Retourne la ligne de destination.
|
||||||
|
*
|
||||||
|
* @return ligne destination
|
||||||
|
*/
|
||||||
public int getXTo() {
|
public int getXTo() {
|
||||||
return xTo;
|
return xTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Colonne de destination */
|
/**
|
||||||
|
* Retourne la colonne de destination.
|
||||||
|
*
|
||||||
|
* @return colonne destination
|
||||||
|
*/
|
||||||
public int getYTo() {
|
public int getYTo() {
|
||||||
return yTo;
|
return yTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Affichage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne une représentation textuelle du coup.
|
||||||
|
*
|
||||||
|
* @return chaîne décrivant le coup
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AvalamPly{" +
|
return "AvalamPly{" +
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.logic.BoardLoader;
|
import fr.iut_fbleau.Bot.AlphaBetaBot;
|
||||||
import fr.iut_fbleau.Avalam.ui.BoardView;
|
// A FAIRE PLUS TARD (PVGOD)
|
||||||
import fr.iut_fbleau.Avalam.ui.ScoreView;
|
import fr.iut_fbleau.Bot.DivineBot;
|
||||||
import fr.iut_fbleau.Avalam.ui.TurnView;
|
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;
|
||||||
|
|
||||||
@@ -11,42 +12,117 @@ import javax.swing.*;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fenêtre principale du jeu Avalam.
|
* La classe <code>AvalamWindow</code>
|
||||||
*
|
*
|
||||||
|
* Fenêtre principale (interface graphique) du jeu Avalam.
|
||||||
* Elle contient :
|
* Elle contient :
|
||||||
* - le plateau (BoardView)
|
* - le plateau (BoardView)
|
||||||
* - l'affichage du score
|
* - l’affichage du score (ScoreView)
|
||||||
* - l'affichage du joueur courant
|
* - l’affichage du joueur courant (TurnView)
|
||||||
*
|
*
|
||||||
* Elle interagit directement avec AvalamBoard (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 {
|
||||||
|
|
||||||
/** Moteur du jeu (API GameAPI) */
|
//Attributs
|
||||||
|
|
||||||
|
/** Moteur du jeu (état + règles). */
|
||||||
private AvalamBoard board;
|
private AvalamBoard board;
|
||||||
|
|
||||||
/** Vues graphiques */
|
/** Vue affichant le score. */
|
||||||
private ScoreView scoreView;
|
private ScoreView scoreView;
|
||||||
|
|
||||||
|
/** Vue affichant le joueur courant. */
|
||||||
private TurnView turnView;
|
private TurnView turnView;
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la fenêtre en mode joueur vs joueur.
|
||||||
|
*/
|
||||||
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
|
||||||
// Chargement du plateau initial depuis Plateau.txt
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
|
Tower[][] initialGrid = BoardLoader.loadFromFile("fr/iut_fbleau/Res/Plateau.txt");
|
||||||
// debug TEMP !!!!!!!!!
|
|
||||||
System.out.println("DEBUG Plateau: Grid[0][0] = " + initialGrid[0][0]);
|
|
||||||
board = new AvalamBoard(initialGrid); // PLAYER1 commence
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// Initialisation du moteur (PLAYER1 commence)
|
||||||
// PANNEAU SCORE + TOUR
|
board = new AvalamBoard(initialGrid);
|
||||||
// ----------------------------------------------------------
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
@@ -62,29 +138,27 @@ public class AvalamWindow extends JFrame {
|
|||||||
|
|
||||||
add(topPanel, BorderLayout.NORTH);
|
add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// Plateau
|
||||||
// PLATEAU (BoardView)
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
boardView = new BoardView(board, this::onBoardUpdated);
|
boardView = new BoardView(board, this::onBoardUpdated);
|
||||||
|
|
||||||
add(boardView, BorderLayout.CENTER);
|
add(boardView, BorderLayout.CENTER);
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
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
|
||||||
* MISES À JOUR D’APRÈS LE MOTEUR
|
|
||||||
* ================================================================ */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appelé automatiquement après chaque coup via BoardView → controller → board.
|
* Appelée après chaque coup (humain ou bot).
|
||||||
|
* Met à jour score, tour, et affiche la fin de partie.
|
||||||
*/
|
*/
|
||||||
public void onBoardUpdated() {
|
public void onBoardUpdated() {
|
||||||
|
|
||||||
// Mise à jour du score et du joueur courant
|
|
||||||
scoreView.updateScores(
|
scoreView.updateScores(
|
||||||
computeScore(Color.YELLOW),
|
computeScore(Color.YELLOW),
|
||||||
computeScore(Color.RED)
|
computeScore(Color.RED)
|
||||||
@@ -92,31 +166,130 @@ public class AvalamWindow extends JFrame {
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
switch (res) {
|
switch (res) {
|
||||||
case WIN : msg = "Le joueur jaune a gagné !";
|
case WIN:
|
||||||
case LOSS : msg = "Le joueur rouge a gagné !";
|
msg = "Le joueur jaune a gagné !";
|
||||||
case DRAW : msg = "Égalité !";
|
break;
|
||||||
default : msg = "Fin de partie.";
|
case LOSS:
|
||||||
|
msg = "Le joueur rouge a gagné !";
|
||||||
|
break;
|
||||||
|
case DRAW:
|
||||||
|
msg = "Égalité !";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg = "Fin de partie.";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(this, msg, "Partie terminée",
|
JOptionPane.showMessageDialog(
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
this,
|
||||||
|
msg,
|
||||||
|
"Partie terminée",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si on est contre un bot et que c’est son tour, on déclenche son animation.
|
||||||
|
maybePlayBotTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fait jouer le bot (idiot / alpha / divin) en deux étapes visibles :
|
||||||
|
* 1) sélection de la tour (affiche les coups légaux)
|
||||||
|
* 2) attente 1 seconde
|
||||||
|
* 3) déplacement vers la destination
|
||||||
|
*
|
||||||
|
* Le tout sans bloquer l'interface (Timer Swing).
|
||||||
|
*/
|
||||||
|
private void maybePlayBotTurn() {
|
||||||
|
|
||||||
/* ================================================================
|
// Mode joueur vs joueur : aucun bot
|
||||||
* OUTILS
|
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 d’un 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.
|
* Calcule le score d'une couleur : nombre de tours contrôlées.
|
||||||
|
*
|
||||||
|
* @param c couleur à compter
|
||||||
|
* @return nombre de tours appartenant à la couleur c
|
||||||
*/
|
*/
|
||||||
private int computeScore(Color c) {
|
private int computeScore(Color c) {
|
||||||
int score = 0;
|
int score = 0;
|
||||||
@@ -132,10 +305,14 @@ public class AvalamWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message du joueur dont c'est le tour.
|
* Retourne le message affiché pour le joueur courant.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
}
|
}
|
||||||
|
|||||||
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,4 +1,4 @@
|
|||||||
package fr.iut_fbleau.Avalam.logic;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.Color;
|
import fr.iut_fbleau.Avalam.Color;
|
||||||
import fr.iut_fbleau.Avalam.Tower;
|
import fr.iut_fbleau.Avalam.Tower;
|
||||||
@@ -8,8 +8,32 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>BoardLoader</code>
|
||||||
|
*
|
||||||
|
* Permet de charger un plateau Avalam à partir d’un fichier texte.
|
||||||
|
* Le fichier contient une grille 9x9 de valeurs numériques :
|
||||||
|
* - 0 : case vide
|
||||||
|
* - 1 : tour jaune
|
||||||
|
* - 2 : tour rouge
|
||||||
|
*
|
||||||
|
* Cette classe fournit une méthode statique pour construire
|
||||||
|
* une grille de tours (<code>Tower[][]</code>) à partir d’une ressource.
|
||||||
|
*/
|
||||||
public class BoardLoader {
|
public class BoardLoader {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge un plateau Avalam depuis un fichier de ressources.
|
||||||
|
*
|
||||||
|
* @param resourcePath chemin du fichier de plateau (dans les ressources)
|
||||||
|
* @return une grille 9x9 de tours (Tower) ou null pour les cases vides
|
||||||
|
*/
|
||||||
public static Tower[][] loadFromFile(String resourcePath) {
|
public static Tower[][] loadFromFile(String resourcePath) {
|
||||||
|
|
||||||
Tower[][] grid = new Tower[9][9];
|
Tower[][] grid = new Tower[9][9];
|
||||||
@@ -17,7 +41,7 @@ public class BoardLoader {
|
|||||||
InputStream in = BoardLoader.class.getResourceAsStream("/" + resourcePath);
|
InputStream in = BoardLoader.class.getResourceAsStream("/" + resourcePath);
|
||||||
|
|
||||||
if (in == null) {
|
if (in == null) {
|
||||||
System.err.println("❌ Ressource introuvable : /" + resourcePath);
|
System.err.println("Ressource introuvable : /" + resourcePath);
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +52,7 @@ public class BoardLoader {
|
|||||||
|
|
||||||
while ((line = reader.readLine()) != null && row < 9) {
|
while ((line = reader.readLine()) != null && row < 9) {
|
||||||
|
|
||||||
// 🔥 Accepte SOIT les espaces, SOIT les virgules
|
// Accepte SOIT les espaces, SOIT les virgules
|
||||||
line = line.replace(",", " ");
|
line = line.replace(",", " ");
|
||||||
String[] parts = line.trim().split("\\s+");
|
String[] parts = line.trim().split("\\s+");
|
||||||
|
|
||||||
@@ -37,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;
|
||||||
159
fr/iut_fbleau/Avalam/BoardView.java
Normal file
159
fr/iut_fbleau/Avalam/BoardView.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>BoardView</code>
|
||||||
|
*
|
||||||
|
* Représente la vue principale du plateau Avalam.
|
||||||
|
* Elle gère :
|
||||||
|
* - l’affichage des tours (PieceLayer)
|
||||||
|
* - l’affichage des coups possibles (HighlightLayer)
|
||||||
|
* - l’affichage du fond graphique (BackgroundLayer)
|
||||||
|
* - les clics via InteractionController
|
||||||
|
*
|
||||||
|
* Cette classe ne contient aucune logique de règles du jeu.
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* Date :
|
||||||
|
* Licence :
|
||||||
|
*/
|
||||||
|
public class BoardView extends JLayeredPane {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Référence au moteur Avalam. */
|
||||||
|
private AvalamBoard board;
|
||||||
|
|
||||||
|
/** Couche d’affichage du fond. */
|
||||||
|
private BackgroundLayer backgroundLayer;
|
||||||
|
|
||||||
|
/** Couche d’affichage des coups possibles. */
|
||||||
|
private HighlightLayer highlightLayer;
|
||||||
|
|
||||||
|
/** Couche d’affichage des pièces. */
|
||||||
|
private PieceLayer pieceLayer;
|
||||||
|
|
||||||
|
/** Contrôleur des interactions. */
|
||||||
|
private InteractionController controller;
|
||||||
|
|
||||||
|
/** Taille d’un pion en pixels. */
|
||||||
|
private final int size = 50;
|
||||||
|
|
||||||
|
/** Espacement entre les cases. */
|
||||||
|
private final int spacing = 70;
|
||||||
|
|
||||||
|
/** Décalage horizontal du plateau. */
|
||||||
|
private final int xBase = 60;
|
||||||
|
|
||||||
|
/** Décalage vertical du plateau. */
|
||||||
|
private final int yBase = 60;
|
||||||
|
|
||||||
|
/** Callback vers AvalamWindow pour mise à jour (score, tour, fin). */
|
||||||
|
private Runnable boardUpdateCallback;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la vue du plateau.
|
||||||
|
*
|
||||||
|
* @param board moteur du jeu
|
||||||
|
* @param boardUpdateCallback callback à appeler après un coup
|
||||||
|
*/
|
||||||
|
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
|
||||||
|
this.board = board;
|
||||||
|
this.boardUpdateCallback = boardUpdateCallback;
|
||||||
|
|
||||||
|
setLayout(null);
|
||||||
|
|
||||||
|
// Contrôleur
|
||||||
|
this.controller = new InteractionController(board, this);
|
||||||
|
|
||||||
|
// Couche fond
|
||||||
|
backgroundLayer = new BackgroundLayer("fr/iut_fbleau/Res/BackgroundAvalam.png");
|
||||||
|
backgroundLayer.setBounds(0, 0, 725, 725);
|
||||||
|
add(backgroundLayer, JLayeredPane.FRAME_CONTENT_LAYER);
|
||||||
|
|
||||||
|
// Couche highlight
|
||||||
|
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
|
||||||
|
add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
|
||||||
|
|
||||||
|
// Couche pièces
|
||||||
|
pieceLayer = new PieceLayer();
|
||||||
|
add(pieceLayer, JLayeredPane.PALETTE_LAYER);
|
||||||
|
|
||||||
|
setPreferredSize(new Dimension(725, 725));
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le contrôleur d'interactions (utile pour simuler les clics du bot).
|
||||||
|
*
|
||||||
|
* @return contrôleur
|
||||||
|
*/
|
||||||
|
public InteractionController getController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active/désactive les interactions utilisateur sur le plateau.
|
||||||
|
* Utile pendant l'animation du bot pour éviter des clics concurrents.
|
||||||
|
*
|
||||||
|
* @param enabled true pour activer, false pour désactiver
|
||||||
|
*/
|
||||||
|
public void setInteractionEnabled(boolean enabled) {
|
||||||
|
// Désactive la couche des pièces (boutons) principalement
|
||||||
|
pieceLayer.setEnabled(enabled);
|
||||||
|
for (Component c : pieceLayer.getComponents()) {
|
||||||
|
c.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appelé par le contrôleur après un coup.
|
||||||
|
*/
|
||||||
|
public void onBoardUpdated() {
|
||||||
|
if (boardUpdateCallback != null) {
|
||||||
|
boardUpdateCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rafraîchit les couches visuelles.
|
||||||
|
*/
|
||||||
|
public void refresh() {
|
||||||
|
|
||||||
|
pieceLayer.displayGrid(
|
||||||
|
boardGrid(),
|
||||||
|
xBase, yBase, spacing, size,
|
||||||
|
(r, c) -> controller.onPieceClicked(r, c)
|
||||||
|
);
|
||||||
|
|
||||||
|
highlightLayer.setLegalMoves(controller.getLegalMoves());
|
||||||
|
|
||||||
|
backgroundLayer.repaint();
|
||||||
|
highlightLayer.repaint();
|
||||||
|
pieceLayer.repaint();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la grille depuis le moteur.
|
||||||
|
*
|
||||||
|
* @return grille 9x9 de tours
|
||||||
|
*/
|
||||||
|
private Tower[][] boardGrid() {
|
||||||
|
Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
|
||||||
|
|
||||||
|
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
||||||
|
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
||||||
|
grid[r][c] = board.getTowerAt(r, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,57 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L'énumération <code>Color</code>
|
||||||
|
*
|
||||||
|
* Représente les deux couleurs utilisées dans Avalam :
|
||||||
|
* - YELLOW (jaune)
|
||||||
|
* - RED (rouge)
|
||||||
|
*
|
||||||
|
* Chaque valeur est associée à une couleur Swing (<code>java.awt.Color</code>)
|
||||||
|
* pour l’affichage graphique et peut être convertie en <code>Player</code> (GameAPI).
|
||||||
|
*/
|
||||||
public enum Color {
|
public enum Color {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Couleur jaune (associée à PLAYER1). */
|
||||||
YELLOW(255, 220, 0),
|
YELLOW(255, 220, 0),
|
||||||
|
|
||||||
|
/** Couleur rouge (associée à PLAYER2). */
|
||||||
RED(200, 40, 40);
|
RED(200, 40, 40);
|
||||||
|
|
||||||
|
/** Couleur Swing utilisée pour l'affichage dans l'interface graphique. */
|
||||||
private final java.awt.Color swingColor;
|
private final java.awt.Color swingColor;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit une couleur Avalam en initialisant sa couleur Swing associée.
|
||||||
|
*
|
||||||
|
* @param r composante rouge [0..255]
|
||||||
|
* @param g composante verte [0..255]
|
||||||
|
* @param b composante bleue [0..255]
|
||||||
|
*/
|
||||||
Color(int r, int g, int b) {
|
Color(int r, int g, int b) {
|
||||||
this.swingColor = new java.awt.Color(r, g, b);
|
this.swingColor = new java.awt.Color(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la couleur Swing associée (pour l'affichage).
|
||||||
|
*
|
||||||
|
* @return java.awt.Color
|
||||||
|
*/
|
||||||
public java.awt.Color getSwingColor() {
|
public java.awt.Color getSwingColor() {
|
||||||
return swingColor;
|
return swingColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit la couleur Avalam en joueur GameAPI.
|
||||||
|
*
|
||||||
|
* @return PLAYER1 si YELLOW, sinon PLAYER2
|
||||||
|
*/
|
||||||
public fr.iut_fbleau.GameAPI.Player toPlayer() {
|
public fr.iut_fbleau.GameAPI.Player toPlayer() {
|
||||||
return (this == YELLOW ?
|
return (this == YELLOW ?
|
||||||
fr.iut_fbleau.GameAPI.Player.PLAYER1 :
|
fr.iut_fbleau.GameAPI.Player.PLAYER1 :
|
||||||
|
|||||||
12
fr/iut_fbleau/Avalam/GameMode.java
Normal file
12
fr/iut_fbleau/Avalam/GameMode.java
Normal 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)
|
||||||
|
}
|
||||||
94
fr/iut_fbleau/Avalam/HighlightLayer.java
Normal file
94
fr/iut_fbleau/Avalam/HighlightLayer.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>HighlightLayer</code>
|
||||||
|
*
|
||||||
|
* Gère l'affichage graphique des cases jouables sur le plateau Avalam.
|
||||||
|
* Les cases autorisées sont représentées par des cercles verts semi-transparents.
|
||||||
|
*
|
||||||
|
* Cette classe ne contient aucune logique de jeu.
|
||||||
|
*/
|
||||||
|
public class HighlightLayer extends JPanel {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Position X de base du plateau. */
|
||||||
|
private int xBase;
|
||||||
|
|
||||||
|
/** Position Y de base du plateau. */
|
||||||
|
private int yBase;
|
||||||
|
|
||||||
|
/** Espacement entre les cases du plateau. */
|
||||||
|
private int spacing;
|
||||||
|
|
||||||
|
/** Taille d’un pion en pixels. */
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
/** Liste des positions jouables (cases en vert). */
|
||||||
|
private List<Point> legalMoves;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la couche d'affichage des coups légaux.
|
||||||
|
*
|
||||||
|
* @param xBase position X de base du plateau
|
||||||
|
* @param yBase position Y de base du plateau
|
||||||
|
* @param spacing espacement entre cases
|
||||||
|
* @param size taille des pions
|
||||||
|
*/
|
||||||
|
public HighlightLayer(int xBase, int yBase, int spacing, int size) {
|
||||||
|
this.xBase = xBase;
|
||||||
|
this.yBase = yBase;
|
||||||
|
this.spacing = spacing;
|
||||||
|
this.size = size;
|
||||||
|
|
||||||
|
setOpaque(false);
|
||||||
|
setBounds(0, 0, 800, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit la liste des cases légales à afficher.
|
||||||
|
*
|
||||||
|
* @param moves liste des positions jouables
|
||||||
|
*/
|
||||||
|
public void setLegalMoves(List<Point> moves) {
|
||||||
|
this.legalMoves = moves;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Affichage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dessine les cercles verts autour des cases autorisées.
|
||||||
|
*
|
||||||
|
* @param g contexte graphique
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
|
||||||
|
if (legalMoves == null) return;
|
||||||
|
|
||||||
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
|
g2.setColor(new java.awt.Color(0, 255, 0, 120));
|
||||||
|
|
||||||
|
for (Point p : legalMoves) {
|
||||||
|
|
||||||
|
int r = p.x;
|
||||||
|
int c = p.y;
|
||||||
|
|
||||||
|
int x = xBase + c * spacing;
|
||||||
|
int y = yBase + r * spacing;
|
||||||
|
|
||||||
|
int highlight = size + 20; // Cercle plus grand que le pion
|
||||||
|
g2.fillOval(x - 10, y - 10, highlight, highlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.AvalamBoard;
|
|
||||||
import fr.iut_fbleau.Avalam.AvalamPly;
|
|
||||||
import fr.iut_fbleau.Avalam.Tower;
|
|
||||||
|
|
||||||
import fr.iut_fbleau.GameAPI.AbstractPly;
|
import fr.iut_fbleau.GameAPI.AbstractPly;
|
||||||
import fr.iut_fbleau.GameAPI.Player;
|
import fr.iut_fbleau.GameAPI.Player;
|
||||||
@@ -13,48 +9,71 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Le contrôleur gère toute l'interaction entre l'utilisateur et le moteur Avalam.
|
* La classe <code>InteractionController</code>
|
||||||
*
|
*
|
||||||
* Son rôle :
|
* Gère l'interaction entre l'utilisateur et le moteur du jeu Avalam.
|
||||||
* - gérer la sélection d’une tour
|
* Elle permet :
|
||||||
* - générer les coups légaux via l’API (iterator)
|
* - la sélection d'une tour
|
||||||
* - valider un déplacement (isLegal)
|
* - le calcul des coups légaux
|
||||||
* - appliquer un coup (doPly)
|
* - la validation d'un déplacement
|
||||||
* - mettre à jour le plateau (via refresh demandé au BoardView)
|
* - l'application d'un coup
|
||||||
*
|
* - la mise à jour de l'affichage via BoardView
|
||||||
* IMPORTANT : ce contrôleur n’affiche rien. Il envoie les infos à BoardView.
|
*
|
||||||
*/
|
* Cette classe ne réalise aucun affichage direct.
|
||||||
|
*/
|
||||||
public class InteractionController {
|
public class InteractionController {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Référence au moteur du jeu Avalam. */
|
||||||
private AvalamBoard board;
|
private AvalamBoard board;
|
||||||
|
|
||||||
/** Position sélectionnée (-1 si aucune) */
|
/** Ligne de la tour sélectionnée (-1 si aucune). */
|
||||||
private int selectedRow = -1;
|
private int selectedRow = -1;
|
||||||
|
|
||||||
|
/** Colonne de la tour sélectionnée (-1 si aucune). */
|
||||||
private int selectedCol = -1;
|
private int selectedCol = -1;
|
||||||
|
|
||||||
/** Liste des coups légaux (en Point) autour de la sélection */
|
/** Liste des coups légaux (sous forme de points). */
|
||||||
private List<Point> legalMoves = new ArrayList<>();
|
private List<Point> legalMoves = new ArrayList<>();
|
||||||
|
|
||||||
/** Référence à la vue pour la rafraîchir après déplacements */
|
/** Référence à la vue du plateau pour rafraîchir l'affichage. */
|
||||||
private BoardView view;
|
private BoardView view;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit le contrôleur d'interactions.
|
||||||
|
*
|
||||||
|
* @param board moteur du jeu Avalam
|
||||||
|
* @param view vue du plateau
|
||||||
|
*/
|
||||||
public InteractionController(AvalamBoard board, BoardView view) {
|
public InteractionController(AvalamBoard board, BoardView view) {
|
||||||
this.board = board;
|
this.board = board;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retourne les cases jouables (pour HighlightLayer). */
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la liste des cases jouables.
|
||||||
|
*
|
||||||
|
* @return liste des coups légaux
|
||||||
|
*/
|
||||||
public List<Point> getLegalMoves() {
|
public List<Point> getLegalMoves() {
|
||||||
return legalMoves;
|
return legalMoves;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appelé lorsqu’un pion est cliqué dans BoardView.
|
* Appelé lorsqu’un pion est cliqué dans BoardView.
|
||||||
* Gère :
|
* Gère :
|
||||||
* - sélection d’une tour
|
* - la sélection d’une tour
|
||||||
* - désélection
|
* - la désélection
|
||||||
* - tentative de déplacement (si clique sur un highlight)
|
* - la tentative de déplacement
|
||||||
*/
|
*
|
||||||
|
* @param r ligne cliquée
|
||||||
|
* @param c colonne cliquée
|
||||||
|
*/
|
||||||
public void onPieceClicked(int r, int c) {
|
public void onPieceClicked(int r, int c) {
|
||||||
|
|
||||||
// Si on clique la même case ⇒ désélection
|
// Si on clique la même case ⇒ désélection
|
||||||
@@ -79,16 +98,21 @@ public class InteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/**
|
||||||
* SÉLECTION D’UNE TOUR
|
* Sélectionne une tour à la position (r,c).
|
||||||
* ---------------------------------------------------------------------- */
|
*
|
||||||
|
* @param r ligne
|
||||||
|
* @param c colonne
|
||||||
|
*/
|
||||||
private void selectTower(int r, int c) {
|
private void selectTower(int r, int c) {
|
||||||
selectedRow = r;
|
selectedRow = r;
|
||||||
selectedCol = c;
|
selectedCol = c;
|
||||||
computeLegalMoves();
|
computeLegalMoves();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annule la sélection actuelle.
|
||||||
|
*/
|
||||||
private void clearSelection() {
|
private void clearSelection() {
|
||||||
selectedRow = -1;
|
selectedRow = -1;
|
||||||
selectedCol = -1;
|
selectedCol = -1;
|
||||||
@@ -96,9 +120,9 @@ public class InteractionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifie les destinations possibles depuis la tour sélectionnée.
|
* Calcule les coups légaux à partir de la tour sélectionnée.
|
||||||
* Utilise l’API officielle : board.iterator()
|
* Utilise l'itérateur fourni par AvalamBoard.
|
||||||
*/
|
*/
|
||||||
private void computeLegalMoves() {
|
private void computeLegalMoves() {
|
||||||
legalMoves.clear();
|
legalMoves.clear();
|
||||||
|
|
||||||
@@ -117,13 +141,12 @@ public class InteractionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
|
||||||
* TENTATIVE DE DÉPLACEMENT
|
|
||||||
* ---------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tente d’exécuter un déplacement vers (r,c) si c’est un coup légal.
|
* Tente d’exécuter un déplacement vers la case (r,c).
|
||||||
*/
|
*
|
||||||
|
* @param r ligne de destination
|
||||||
|
* @param c colonne de destination
|
||||||
|
*/
|
||||||
private void tryMove(int r, int c) {
|
private void tryMove(int r, int c) {
|
||||||
// Vérifier si (r,c) est une destination légale
|
// Vérifier si (r,c) est une destination légale
|
||||||
boolean isLegalDest = false;
|
boolean isLegalDest = false;
|
||||||
@@ -152,11 +175,11 @@ public class InteractionController {
|
|||||||
// Réinitialiser la sélection
|
// Réinitialiser la sélection
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
|
||||||
// Recalcul du score + joueur courant + fin de partie (handled in window)
|
// Mise à jour de l'interface
|
||||||
view.onBoardUpdated();
|
view.onBoardUpdated();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Coup impossible (rare, mais pour sécurité)
|
// Coup impossible
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,82 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point d’entrée : propose un menu de sélection de mode, puis lance la fenêtre Avalam.
|
||||||
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
new AvalamWindow();
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
showModeSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche le menu de sélection du mode de jeu.
|
||||||
|
* Peut être appelé depuis d'autres fenêtres pour revenir au menu.
|
||||||
|
*/
|
||||||
|
public static void showModeSelection() {
|
||||||
|
String[] options = {
|
||||||
|
"joueur vs joueur",
|
||||||
|
"joueur vs botidiot",
|
||||||
|
"joueur vs bot alpha",
|
||||||
|
"joueur vs bot divin (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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,47 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>PieceButton</code> représente graphiquement une tour Avalam.
|
* La classe <code>PieceButton</code>
|
||||||
* Il s'agit d'un bouton rond :
|
*
|
||||||
*
|
* Représente graphiquement une tour Avalam sous forme d’un bouton rond.
|
||||||
* <ul>
|
* Chaque bouton :
|
||||||
* <li>coloré selon le joueur</li>
|
* - possède une couleur correspondant au joueur,
|
||||||
* <li>affichant sa hauteur</li>
|
* - affiche la hauteur de la tour,
|
||||||
* <li>avec un effet de survol visuel</li>
|
* - réagit au survol de la souris.
|
||||||
* </ul>
|
*
|
||||||
*
|
* Ce composant permet l’interaction avec les pièces du plateau.
|
||||||
* Ce composant sert d'interaction principale pour cliquer les pions.
|
*/
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class PieceButton extends JButton {
|
public class PieceButton extends JButton {
|
||||||
|
|
||||||
private Color color;
|
//Attributs
|
||||||
|
|
||||||
|
/** Couleur graphique du pion. */
|
||||||
|
private java.awt.Color color;
|
||||||
|
|
||||||
|
/** Hauteur de la tour affichée. */
|
||||||
private int height;
|
private int height;
|
||||||
|
|
||||||
|
/** Indique si la souris survole le bouton. */
|
||||||
private boolean hover = false;
|
private boolean hover = false;
|
||||||
|
|
||||||
/** Position de la tour sur la grille. */
|
/** Position de la tour sur la grille (ligne). */
|
||||||
public final int row, col;
|
public final int row;
|
||||||
|
|
||||||
|
/** Position de la tour sur la grille (colonne). */
|
||||||
|
public final int col;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur.
|
* Construit un bouton représentant une tour Avalam.
|
||||||
*
|
*
|
||||||
* @param c couleur graphique du pion
|
* @param c couleur graphique du pion
|
||||||
* @param height hauteur de la tour
|
* @param height hauteur de la tour
|
||||||
* @param row ligne du pion
|
* @param row ligne du pion
|
||||||
* @param col colonne du pion
|
* @param col colonne du pion
|
||||||
*/
|
*/
|
||||||
public PieceButton(java.awt.Color c, int height, int row, int col) {
|
public PieceButton(java.awt.Color c, int height, int row, int col) {
|
||||||
this.color = c;
|
this.color = c;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
@@ -52,9 +59,13 @@ public class PieceButton extends JButton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dessine le pion rond ainsi que son chiffre au centre.
|
* Dessine le pion rond ainsi que sa hauteur au centre.
|
||||||
*/
|
*
|
||||||
|
* @param g contexte graphique
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
Graphics2D g2 = (Graphics2D) g.create();
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
@@ -78,9 +89,15 @@ public class PieceButton extends JButton {
|
|||||||
g2.dispose();
|
g2.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Affichage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rend le bouton réellement rond (zone cliquable circulaire).
|
* Rend le bouton réellement rond (zone cliquable circulaire).
|
||||||
*/
|
*
|
||||||
|
* @param x coordonnée X
|
||||||
|
* @param y coordonnée Y
|
||||||
|
* @return true si le point est dans le cercle
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(int x, int y) {
|
public boolean contains(int x, int y) {
|
||||||
double dx = x - getWidth()/2.0;
|
double dx = x - getWidth()/2.0;
|
||||||
@@ -1,48 +1,44 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.Tower;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La classe <code>PieceLayer</code> gère l'affichage des pions
|
* La classe <code>PieceLayer</code>
|
||||||
* (sous forme de <code>PieceButton</code>) sur la grille.
|
*
|
||||||
*
|
* Gère l'affichage des tours du plateau Avalam sous forme de boutons.
|
||||||
* Elle s'occupe uniquement :
|
* Elle :
|
||||||
* <ul>
|
* - affiche les pièces,
|
||||||
* <li>d'afficher les pièces</li>
|
* - positionne les boutons sur la grille,
|
||||||
* <li>de positionner correctement les boutons</li>
|
* - associe une action de clic à chaque pièce.
|
||||||
* <li>d'attacher un callback à chaque clic</li>
|
*
|
||||||
* </ul>
|
* Aucune logique de jeu n'est implémentée ici.
|
||||||
*
|
*/
|
||||||
* Aucune logique de jeu n'est réalisée ici.
|
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class PieceLayer extends JPanel {
|
public class PieceLayer extends JPanel {
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructeur.
|
* Construit la couche graphique des pièces.
|
||||||
* Initialise un panneau transparent prêt à recevoir des pions.
|
*/
|
||||||
*/
|
|
||||||
public PieceLayer() {
|
public PieceLayer() {
|
||||||
setLayout(null);
|
setLayout(null);
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
setBounds(0, 0, 800, 800);
|
setBounds(0, 0, 800, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Affiche la grille de tours sous forme de boutons.
|
* Affiche la grille de tours sous forme de boutons interactifs.
|
||||||
*
|
*
|
||||||
* @param grid grille 9×9 des tours
|
* @param grid grille 9×9 des tours
|
||||||
* @param xBase offset X du plateau
|
* @param xBase décalage horizontal du plateau
|
||||||
* @param yBase offset Y du plateau
|
* @param yBase décalage vertical du plateau
|
||||||
* @param spacing espacement entre cases
|
* @param spacing espacement entre cases
|
||||||
* @param size taille d'un pion
|
* @param size taille d'un pion
|
||||||
* @param clickCallback fonction appelée lors d’un clic sur un pion
|
* @param clickCallback fonction appelée lors d’un clic sur un pion
|
||||||
*/
|
*/
|
||||||
public void displayGrid(Tower[][] grid, int xBase, int yBase,
|
public void displayGrid(Tower[][] grid, int xBase, int yBase,
|
||||||
int spacing, int size,
|
int spacing, int size,
|
||||||
java.util.function.BiConsumer<Integer, Integer> clickCallback) {
|
java.util.function.BiConsumer<Integer, Integer> clickCallback) {
|
||||||
57
fr/iut_fbleau/Avalam/ScoreView.java
Normal file
57
fr/iut_fbleau/Avalam/ScoreView.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>ScoreView</code>
|
||||||
|
*
|
||||||
|
* Affiche les scores des deux joueurs du jeu Avalam.
|
||||||
|
* Cette classe est purement graphique : elle ne calcule pas les scores.
|
||||||
|
*/
|
||||||
|
public class ScoreView extends JPanel {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Label affichant le score du joueur jaune. */
|
||||||
|
private JLabel scoreY;
|
||||||
|
|
||||||
|
/** Label affichant le score du joueur rouge. */
|
||||||
|
private JLabel scoreR;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la vue des scores.
|
||||||
|
*
|
||||||
|
* @param y score initial du joueur jaune
|
||||||
|
* @param r score initial du joueur rouge
|
||||||
|
*/
|
||||||
|
public ScoreView(int y, int r) {
|
||||||
|
setBackground(new java.awt.Color(200,200,200));
|
||||||
|
setLayout(new FlowLayout());
|
||||||
|
|
||||||
|
scoreY = new JLabel("Score Jaune : " + y);
|
||||||
|
scoreR = new JLabel("Score Rouge : " + r);
|
||||||
|
|
||||||
|
scoreY.setFont(new Font("Arial", Font.BOLD, 18));
|
||||||
|
scoreR.setFont(new Font("Arial", Font.BOLD, 18));
|
||||||
|
|
||||||
|
add(scoreY);
|
||||||
|
add(new JLabel(" | "));
|
||||||
|
add(scoreR);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour l'affichage des scores.
|
||||||
|
*
|
||||||
|
* @param y nouveau score du joueur jaune
|
||||||
|
* @param r nouveau score du joueur rouge
|
||||||
|
*/
|
||||||
|
public void updateScores(int y, int r) {
|
||||||
|
scoreY.setText("Score Jaune : " + y);
|
||||||
|
scoreR.setText("Score Rouge : " + r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +1,88 @@
|
|||||||
package fr.iut_fbleau.Avalam;
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Représente une tour dans le jeu Avalam.
|
* La classe <code>Tower</code>
|
||||||
*
|
*
|
||||||
* Une tour possède :
|
* Représente une tour dans le jeu Avalam.
|
||||||
* - la couleur de son sommet
|
* Une tour est caractérisée par :
|
||||||
* - sa hauteur (nombre de pions)
|
* - une couleur (couleur du sommet)
|
||||||
*/
|
* - une hauteur (nombre de pions empilés)
|
||||||
|
*
|
||||||
|
* Cette version est volontairement compatible avec le reste du projet :
|
||||||
|
* - constructeur Tower(int, Color) utilisé dans d'autres parties possibles
|
||||||
|
* - usine createTower(Color) utilisé par BoardLoader
|
||||||
|
* - méthode mergeTower(Tower) utilisée par AvalamBoard
|
||||||
|
*/
|
||||||
public class Tower {
|
public class Tower {
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Hauteur de la tour (nombre de pions empilés). */
|
||||||
|
private byte height;
|
||||||
|
|
||||||
|
/** Couleur du sommet de la tour (propriétaire actuel). */
|
||||||
private Color color;
|
private Color color;
|
||||||
private int height;
|
|
||||||
|
|
||||||
/** Nouvelle tour de hauteur 1 */
|
//Constructeur
|
||||||
public Tower(Color color) {
|
|
||||||
|
/**
|
||||||
|
* Construit une tour avec une hauteur et une couleur données.
|
||||||
|
*
|
||||||
|
* @param height hauteur initiale
|
||||||
|
* @param color couleur du sommet
|
||||||
|
*/
|
||||||
|
public Tower(int height, Color color) {
|
||||||
|
this.height = (byte) height;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.height = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tour avec couleur et hauteur existantes */
|
/**
|
||||||
public Tower(Color color, int height) {
|
* Construit une tour de hauteur 1 avec la couleur donnée.
|
||||||
this.color = color;
|
* (Constructeur attendu par BoardLoader dans le projet.)
|
||||||
this.height = height;
|
*
|
||||||
|
* @param color couleur du sommet
|
||||||
|
*/
|
||||||
|
public static Tower createTower(Color color) {
|
||||||
|
return new Tower(1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getColor() {
|
//Méthodes
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la hauteur de la tour.
|
||||||
|
*
|
||||||
|
* @return hauteur
|
||||||
|
*/
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fusionne this (destination) avec src (source).
|
* Retourne la couleur du sommet.
|
||||||
* La source monte sur la destination →
|
*
|
||||||
* - la couleur du sommet devient celle de src
|
* @return couleur
|
||||||
* - la hauteur s’additionne
|
*/
|
||||||
*/
|
public Color getColor() {
|
||||||
public void mergeTower(Tower src) {
|
return color;
|
||||||
this.color = src.color;
|
|
||||||
this.height = this.height + src.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fusionne la tour <code>src</code> sur la tour courante (destination).
|
||||||
|
* La couleur du sommet devient celle de <code>src</code>.
|
||||||
|
*
|
||||||
|
* @param src tour source empilée sur la destination
|
||||||
|
*/
|
||||||
|
public void mergeTower(Tower src) {
|
||||||
|
this.height += src.height;
|
||||||
|
this.color = src.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Affichage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Représentation textuelle de la tour.
|
||||||
|
*
|
||||||
|
* @return chaîne représentant la tour
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return color + "(" + height + ")";
|
return color + "(" + height + ")";
|
||||||
|
|||||||
45
fr/iut_fbleau/Avalam/TurnView.java
Normal file
45
fr/iut_fbleau/Avalam/TurnView.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package fr.iut_fbleau.Avalam;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La classe <code>TurnView</code>
|
||||||
|
*
|
||||||
|
* Affiche le joueur dont c'est le tour dans le jeu Avalam.
|
||||||
|
* Cette classe est uniquement graphique.
|
||||||
|
*/
|
||||||
|
public class TurnView extends JPanel {
|
||||||
|
|
||||||
|
//Attributs
|
||||||
|
|
||||||
|
/** Label affichant le joueur courant. */
|
||||||
|
private JLabel text;
|
||||||
|
|
||||||
|
//Constructeur
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit la vue du tour de jeu.
|
||||||
|
*
|
||||||
|
* @param initial message initial à afficher
|
||||||
|
*/
|
||||||
|
public TurnView(String initial) {
|
||||||
|
setBackground(new java.awt.Color(220,220,220));
|
||||||
|
|
||||||
|
text = new JLabel(initial);
|
||||||
|
text.setFont(new Font("Arial", Font.BOLD, 20));
|
||||||
|
|
||||||
|
add(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Méthodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le texte affichant le joueur courant.
|
||||||
|
*
|
||||||
|
* @param s message à afficher
|
||||||
|
*/
|
||||||
|
public void setTurn(String s) {
|
||||||
|
text.setText(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
|
||||||
|
|
||||||
import fr.iut_fbleau.Avalam.AvalamBoard;
|
|
||||||
import fr.iut_fbleau.Avalam.Tower;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BoardView est la vue principale du plateau Avalam.
|
|
||||||
*
|
|
||||||
* Elle gère :
|
|
||||||
* - l’affichage des tours (PieceLayer)
|
|
||||||
* - l’affichage des coups possibles (HighlightLayer)
|
|
||||||
* - les clics via InteractionController
|
|
||||||
*
|
|
||||||
* Toute la logique de jeu est déléguée au moteur AvalamBoard
|
|
||||||
* et au contrôleur InteractionController.
|
|
||||||
*/
|
|
||||||
public class BoardView extends JLayeredPane {
|
|
||||||
|
|
||||||
/** Référence au moteur Avalam */
|
|
||||||
private AvalamBoard board;
|
|
||||||
|
|
||||||
/** Couche d’affichage des rond verts */
|
|
||||||
private HighlightLayer highlightLayer;
|
|
||||||
|
|
||||||
/** Couche d’affichage des tours */
|
|
||||||
private PieceLayer pieceLayer;
|
|
||||||
|
|
||||||
/** Contrôleur des interactions */
|
|
||||||
private InteractionController controller;
|
|
||||||
|
|
||||||
/** Décalages et dimensions pour l'affichage */
|
|
||||||
private final int size = 50;
|
|
||||||
private final int spacing = 70;
|
|
||||||
private final int xBase = 60;
|
|
||||||
private final int yBase = 60;
|
|
||||||
|
|
||||||
/** Callback vers AvalamWindow pour mises à jour (score, tour, fin de partie) */
|
|
||||||
private Runnable boardUpdateCallback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructeur de la vue du plateau.
|
|
||||||
*
|
|
||||||
* @param board moteur Avalam utilisé pour afficher la grille
|
|
||||||
* @param boardUpdateCallback callback appelé après chaque coup
|
|
||||||
*/
|
|
||||||
public BoardView(AvalamBoard board, Runnable boardUpdateCallback) {
|
|
||||||
this.board = board;
|
|
||||||
this.boardUpdateCallback = boardUpdateCallback;
|
|
||||||
|
|
||||||
setLayout(null);
|
|
||||||
|
|
||||||
// Contrôleur
|
|
||||||
this.controller = new InteractionController(board, this);
|
|
||||||
|
|
||||||
// Couche Highlight
|
|
||||||
highlightLayer = new HighlightLayer(xBase, yBase, spacing, size);
|
|
||||||
add(highlightLayer, JLayeredPane.DEFAULT_LAYER);
|
|
||||||
|
|
||||||
// Couche des pièces
|
|
||||||
pieceLayer = new PieceLayer();
|
|
||||||
add(pieceLayer, JLayeredPane.PALETTE_LAYER);
|
|
||||||
|
|
||||||
setPreferredSize(new Dimension(800, 800));
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appelée par InteractionController quand un coup est joué.
|
|
||||||
* Permet à AvalamWindow de rafraîchir :
|
|
||||||
* - scores
|
|
||||||
* - affichage du joueur courant
|
|
||||||
* - détection fin de partie
|
|
||||||
*/
|
|
||||||
public void onBoardUpdated() {
|
|
||||||
if (boardUpdateCallback != null) {
|
|
||||||
boardUpdateCallback.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Met à jour toutes les couches visuelles.
|
|
||||||
*/
|
|
||||||
public void refresh() {
|
|
||||||
|
|
||||||
// Mise à jour des pièces
|
|
||||||
pieceLayer.displayGrid(
|
|
||||||
boardGrid(),
|
|
||||||
xBase, yBase, spacing, size,
|
|
||||||
(r, c) -> controller.onPieceClicked(r, c)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mise à jour des highlights
|
|
||||||
highlightLayer.setLegalMoves(controller.getLegalMoves());
|
|
||||||
|
|
||||||
highlightLayer.repaint();
|
|
||||||
pieceLayer.repaint();
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renvoie la grille de tours depuis AvalamBoard.
|
|
||||||
*/
|
|
||||||
private Tower[][] boardGrid() {
|
|
||||||
Tower[][] grid = new Tower[AvalamBoard.SIZE][AvalamBoard.SIZE];
|
|
||||||
|
|
||||||
for (int r = 0; r < AvalamBoard.SIZE; r++) {
|
|
||||||
for (int c = 0; c < AvalamBoard.SIZE; c++) {
|
|
||||||
grid[r][c] = board.getTowerAt(r, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* La classe <code>HighlightLayer</code> est responsable de l'affichage des
|
|
||||||
* cases de déplacement autorisées sous forme de cercles verts.
|
|
||||||
*
|
|
||||||
* Elle n'interagit pas directement avec les pièces, mais se contente
|
|
||||||
* de dessiner en arrière-plan selon une liste de positions légales.
|
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class HighlightLayer extends JPanel {
|
|
||||||
|
|
||||||
private int xBase, yBase, spacing, size;
|
|
||||||
|
|
||||||
/** Liste des positions jouables (cases en vert). */
|
|
||||||
private List<Point> legalMoves;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructeur.
|
|
||||||
*
|
|
||||||
* @param xBase position X de base du plateau
|
|
||||||
* @param yBase position Y de base du plateau
|
|
||||||
* @param spacing espacement entre cases
|
|
||||||
* @param size taille des pions
|
|
||||||
*/
|
|
||||||
public HighlightLayer(int xBase, int yBase, int spacing, int size) {
|
|
||||||
this.xBase = xBase;
|
|
||||||
this.yBase = yBase;
|
|
||||||
this.spacing = spacing;
|
|
||||||
this.size = size;
|
|
||||||
|
|
||||||
setOpaque(false);
|
|
||||||
setBounds(0, 0, 800, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Définit la liste des cases légales à afficher.
|
|
||||||
*/
|
|
||||||
public void setLegalMoves(List<Point> moves) {
|
|
||||||
this.legalMoves = moves;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dessine les cercles verts autour des cases autorisées.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void paintComponent(Graphics g) {
|
|
||||||
super.paintComponent(g);
|
|
||||||
|
|
||||||
if (legalMoves == null) return;
|
|
||||||
|
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
|
||||||
g2.setColor(new Color(0, 255, 0, 120));
|
|
||||||
|
|
||||||
for (Point p : legalMoves) {
|
|
||||||
|
|
||||||
int r = p.x;
|
|
||||||
int c = p.y;
|
|
||||||
|
|
||||||
int x = xBase + c * spacing;
|
|
||||||
int y = yBase + r * spacing;
|
|
||||||
|
|
||||||
int highlight = size + 20; // Cercle plus grand que le pion
|
|
||||||
g2.fillOval(x - 10, y - 10, highlight, highlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* La classe <code>ScoreView</code> affiche les scores actuels des deux joueurs.
|
|
||||||
*
|
|
||||||
* Elle est purement graphique : aucune logique de calcul n'est présente.
|
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class ScoreView extends JPanel {
|
|
||||||
|
|
||||||
private JLabel scoreY, scoreR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructeur.
|
|
||||||
*
|
|
||||||
* @param y score initial du joueur jaune
|
|
||||||
* @param r score initial du joueur rouge
|
|
||||||
*/
|
|
||||||
public ScoreView(int y, int r) {
|
|
||||||
setBackground(new Color(200,200,200));
|
|
||||||
setLayout(new FlowLayout());
|
|
||||||
|
|
||||||
scoreY = new JLabel("Score Jaune : " + y);
|
|
||||||
scoreR = new JLabel("Score Rouge : " + r);
|
|
||||||
|
|
||||||
scoreY.setFont(new Font("Arial", Font.BOLD, 18));
|
|
||||||
scoreR.setFont(new Font("Arial", Font.BOLD, 18));
|
|
||||||
|
|
||||||
add(scoreY);
|
|
||||||
add(new JLabel(" | "));
|
|
||||||
add(scoreR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Met à jour l'affichage des scores.
|
|
||||||
*/
|
|
||||||
public void updateScores(int y, int r) {
|
|
||||||
scoreY.setText("Score Jaune : " + y);
|
|
||||||
scoreR.setText("Score Rouge : " + r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package fr.iut_fbleau.Avalam.ui;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* La classe <code>TurnView</code> affiche le joueur à qui c'est le tour.
|
|
||||||
*
|
|
||||||
* Elle agit comme une simple bannière d’information,
|
|
||||||
* mise à jour par la logique du jeu.
|
|
||||||
*
|
|
||||||
* @author
|
|
||||||
* @version 1.0
|
|
||||||
*/
|
|
||||||
public class TurnView extends JPanel {
|
|
||||||
|
|
||||||
private JLabel text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructeur.
|
|
||||||
*
|
|
||||||
* @param initial message initial à afficher
|
|
||||||
*/
|
|
||||||
public TurnView(String initial) {
|
|
||||||
setBackground(new Color(220,220,220));
|
|
||||||
|
|
||||||
text = new JLabel(initial);
|
|
||||||
text.setFont(new Font("Arial", Font.BOLD, 20));
|
|
||||||
|
|
||||||
add(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Met à jour le texte affichant le joueur courant.
|
|
||||||
*
|
|
||||||
* @param s message à afficher
|
|
||||||
*/
|
|
||||||
public void setTurn(String s) {
|
|
||||||
text.setText(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
217
fr/iut_fbleau/Bot/AlphaBetaBot.java
Normal file
217
fr/iut_fbleau/Bot/AlphaBetaBot.java
Normal 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
|
||||||
|
}
|
||||||
185
fr/iut_fbleau/Bot/DivineBot.java
Normal file
185
fr/iut_fbleau/Bot/DivineBot.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
fr/iut_fbleau/Bot/IdiotBot.java
Normal file
43
fr/iut_fbleau/Bot/IdiotBot.java
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
fr/iut_fbleau/Res/BackgroundAvalam.png
Normal file
BIN
fr/iut_fbleau/Res/BackgroundAvalam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
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