14 Commits

Author SHA1 Message Date
e8f083424b Merge master into Arbre (resolve conflicts) 2026-02-15 22:04:50 +01:00
942462994b Keep README from master 2026-02-15 21:46:10 +01:00
55f6492655 nattoyage 2026-02-15 21:18:16 +01:00
fafebb2648 Arbre 2026-02-13 16:39:48 +01:00
vaisse
fe13b946b1 truc fonctionnel mais avec un retouchage dans l'API. dsl 2026-02-06 12:12:46 +01:00
fc6e6b9fa6 Merge pull request 'Readme' (#18) from Readme into master
Reviewed-on: #18
Reviewed-by: Clement JANNAIRE <clement.jannaire@etu.u-pec.fr>
Reviewed-by: Alistair VAISSE <alistair.vaisse@etu.u-pec.fr>
2026-02-06 01:50:08 +01:00
8cad839e4d Nettoyage 2026-02-06 01:38:23 +01:00
d2f34577e2 Rapport 2026-02-06 01:36:09 +01:00
ebfc2df29b Nettoyage 2026-02-06 01:33:39 +01:00
9a1ae37130 README 2026-02-06 01:32:24 +01:00
2dfc6014e0 Merge pull request 'AUTOPLAY' (#13) from AUTOPLAY into master
Reviewed-on: #13
Reviewed-by: Clement JANNAIRE <clement.jannaire@etu.u-pec.fr>
Reviewed-by: Riad KARA-MOSTEFA <riad.kara-mostefa@etu.u-pec.fr>
2026-01-30 09:37:05 +01:00
3aec1d3f6e AUTOPLAY une nouvellle fois 2026-01-30 09:32:17 +01:00
a7d3e9d138 implémentation de l'algo fonctionnelle. Reste à faire un code qui évalue une position 2026-01-21 17:20:06 +01:00
f207da0e2b Merge pull request 'Algo Victoire + Console Player + Main + Javadoc' (#12) from riad-kara-mostefa into master
Reviewed-on: #12
Reviewed-by: Alistair VAISSE <alistair.vaisse@etu.u-pec.fr>
Reviewed-by: Clemence DUCREUX <clemence.ducreux@etu.u-pec.fr>
2026-01-14 16:16:49 +01:00
40 changed files with 9951 additions and 10120 deletions

131
README.md
View File

@@ -1,115 +1,56 @@
# Instructions de Travail sur les Tickets # BUT3 Projet Jeu : Hex
Ce document présente la procédure à suivre lors de la création et de la gestion des tickets de développement. Veuillez suivre chaque étape avec attention.
## 1. Création du Ticket
### Titre du Ticket
Le titre doit décrire de manière générale la tâche à réaliser. Soyez précis, mais sans entrer dans les détails techniques. Par exemple :
Ajout d'une nouvelle fonctionnalité de recherche dans l'application
Correction du bug d'affichage sur la page d'accueil
### Description du Ticket
La description doit fournir une explication légèrement détaillée des tâches à réaliser. Elle doit inclure les éléments suivants :
Objectif global de la tâche
Étapes spécifiques ou parties du projet concernées
Comportement attendu une fois la tâche accomplie
## 2. Création de la Branche Il sagit dune implémentation du jeu **Hex** en Java, développée à partir de lAPI fournie par le Monsieur Madelaine.
Le projet comprend un moteur de jeu fonctionnel, un affichage console pour le debug, ainsi que des bots permettant de jouer automatiquement.
Lorsque vous commencez à travailler sur un ticket, créez une nouvelle branche avec un nom particulier qui reflète le ticket en cours. Le format de la branche doit être :
nom-de-la-feature-#numeroduticket ## Compilation
### Pour créer une branche : Depuis la racine du projet, compiler lensemble des fichiers Java avec la commande suivante :
git checkout -b feature-recherche-#123 ```bash
javac -d build $(find javaAPI -name "*.java")
## 3. Commit des Changements
Les commits doivent suivre la convention suivante :
- Le message de commit doit décrire brièvement le changement effectué.
- À la fin du message de commit, vous devez toujours ajouter le numéro du ticket pour faciliter le suivi des tâches.
Exemple de message de commit :
Ajout du champ de recherche sur la page d'accueil #123
## 4. Push de la Branche
Après avoir effectué vos changements et effectué vos commits, vous devrez pousser la branche sur le dépôt distant. Lors de votre premier git push, vous recevrez un message pour définir l'upstream de la branche.
Exemple de message affiché :
```
fatal: The upstream branch 'origin/feature-recherche-#123' does not exist
To push the branch and set the upstream, use the following command:
git push --set-upstream origin nom-de-la-feature-#numero
``` ```
Les fichiers compilés (`.class`) sont générés dans le dossier `bin`.
Vous devez copier et coller la commande dans votre terminal pour effectuer le push. Une fois cette commande exécutée, votre branche sera poussée vers le dépôt distant.
## 5. Création d'une Pull Request (PR)
Une fois que vous avez poussé votre branche sur Gitea, vous devez ouvrir une pull request pour demander la révision de votre code.
Voici les étapes pour créer une pull request correctement :
- Allez sur Gitea et naviguez vers le projet concerné.
- Cliquez sur "Branches" et vous devriez voir la branche que vous venez de pousser.
- Cliquez sur le bouton "Create Pull Request" à côté de votre branche.
Remplissez les informations nécessaires :
- Titre de la PR : Utilisez le même titre que celui du ticket.
- Description de la PR : Décrivez brièvement ce que votre PR accomplit. Vous pouvez vous baser sur la description du ticket.
- Revues : Assurez-vous de demander une révision par deux membres de léquipe.
- Cliquez sur "Create Pull Request" pour soumettre.
Une fois la PR ouverte, vous devrez attendre la révision et lapprobation de léquipe avant de pouvoir fusionner la branche dans main ou develop selon le flux de travail de votre projet.
# Résumé des Commandes Git : ## Lancer une démonstration
Voici un récapitulatif des commandes Git que vous utiliserez fréquemment : ### Partie automatique (bot)
## 1. Créer une branche ```bash
java -cp build fr.iut_fbleau.HexGame.HexMain 3 autoplay
```
git checkout -b feature-recherche-#123 Ce mode permet de lancer une partie entièrement automatique en utilisant le bot implémenté dans la classe `Simulation`.
### Partie interactive (joueur humain)
```bash
java -cp build fr.iut_fbleau.HexGame.HexMain
```
Le plateau saffiche dans le terminal et les coups sont entrés sous forme de coordonnées.
## 2. Ajouter les fichiers modifiés : ## Tests et validation
git add . Les tests sont réalisés sous forme de **tests fonctionnels** via des méthodes `main` et des modes de démonstration :
git add * - vérification de la validité des coups,
git add <nom_du_fichier> - alternance correcte des joueurs,
- détection des conditions de fin de partie,
- exécution de parties complètes en mode automatique.
Laffichage console du plateau, fourni par la méthode `HexBoard.toString()`, est utilisé comme outil de debug pour visualiser létat du jeu à chaque tour.
## 3. Commit des changements : ## Organisation du projet
git commit -m "Ajout de [...] #numeroticket" - `HexBoard` : représentation du plateau et gestion des règles du jeu
- `HexPly` : représentation dun coup
- `Simulation` : bot basé sur une recherche Minimax à profondeur limitée
- `HexMain` : point dentrée du programme
Les classes principales sont documentées à laide de **Javadoc**.
## 4. Pousser la branche
git push -set-upstream origin <nom-de-la-branche-#numeroticket>
## 5. Supprimer une branche
git branch -d <nom_de_la_branche>

9784
Rapport Hex.pdf Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
,vaisse,salle235-12,06.02.2026 11:29,file:///export/home/an23/vaisse/.config/libreoffice/4;

View File

@@ -1,7 +0,0 @@
Bot 1, Bot 2, Winner
RandomBot,MiniMaxBot,WIN
RandomBot,HeuristicBot,WIN
RandomBot,MonteCarloBot,WIN
MiniMaxBot,HeuristicBot,WIN
MiniMaxBot,MonteCarloBot,WIN
HeuristicBot,MonteCarloBot,WIN
1 Bot 1 Bot 2 Winner
2 RandomBot MiniMaxBot WIN
3 RandomBot HeuristicBot WIN
4 RandomBot MonteCarloBot WIN
5 MiniMaxBot HeuristicBot WIN
6 MiniMaxBot MonteCarloBot WIN
7 HeuristicBot MonteCarloBot WIN

View File

@@ -34,8 +34,8 @@ public abstract class AbstractGame {
// constructeur à appeler dans le constructeur d'un fils concret avec super. // constructeur à appeler dans le constructeur d'un fils concret avec super.
public AbstractGame(IBoard b, EnumMap<Player,AbstractGamePlayer> m){ public AbstractGame(IBoard b, EnumMap<Player,AbstractGamePlayer> m){
this.currentBoard=b; this.currentBoard=b;
this.mapPlayers=m; this.mapPlayers=m;
} }
/** /**

View File

@@ -26,5 +26,6 @@ public abstract class AbstractGamePlayer {
* @throws IllegalStateException if the Situation is already in the bookmarks * @throws IllegalStateException if the Situation is already in the bookmarks
*/ */
public abstract AbstractPly giveYourMove(IBoard p); public abstract AbstractPly giveYourMove(IBoard p);
public abstract Boolean jesuisMinimax();
} }

View File

@@ -59,6 +59,6 @@ public class Arena {
players.put(Player.PLAYER2, bot2); players.put(Player.PLAYER2, bot2);
Simulation simulation = new Simulation(board, players); Simulation simulation = new Simulation(board, players);
return simulation.run(); return simulation.runForArena();
} }
} }

View File

@@ -9,7 +9,7 @@ public class ArenaMain {
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {} try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
} }
Arena arena = new Arena(size); Arena arena = new Arena(size);
arena.addBot(new RandomBot(Player.PLAYER1, 12345L)); // Correct constructor usage arena.addBot(new RandomBot(Player.PLAYER1, 24015L)); // Correct constructor usage
arena.addBot(new MiniMaxBot(Player.PLAYER2)); arena.addBot(new MiniMaxBot(Player.PLAYER2));
arena.addBot(new HeuristicBot(Player.PLAYER1)); arena.addBot(new HeuristicBot(Player.PLAYER1));
arena.addBot(new MonteCarloBot(Player.PLAYER2)); // Correct constructor usage arena.addBot(new MonteCarloBot(Player.PLAYER2)); // Correct constructor usage

View File

@@ -8,6 +8,10 @@ public class HeuristicBot extends AbstractGamePlayer {
super(me); // Correct constructor usage super(me); // Correct constructor usage
} }
public Boolean jesuisMinimax(){
return false;
}
@Override @Override
public AbstractPly giveYourMove(IBoard board) { public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board; HexBoard hb = (HexBoard) board;

View File

@@ -18,6 +18,10 @@ public class HumanConsolePlayer extends AbstractGamePlayer {
this.in = in; this.in = in;
} }
public Boolean jesuisMinimax(){
return false;
}
@Override @Override
public AbstractPly giveYourMove(IBoard board) { public AbstractPly giveYourMove(IBoard board) {
if (!(board instanceof HexBoard)) { if (!(board instanceof HexBoard)) {

View File

@@ -6,22 +6,40 @@ public class MiniMaxBot extends AbstractGamePlayer {
private int MAXDEPTH = 5; private int MAXDEPTH = 5;
/**
* En dessous (ou égal) à ce nombre de coups restants (cases vides),
* on considère que l'arbre est "petit" et on fait un alpha-bêta simple :
* pas de cut-off, on explore jusqu'aux positions terminales (isGameOver()).
*/
private int SMALL_TREE_MOVE_LIMIT = 6;
public MiniMaxBot(Player me) { public MiniMaxBot(Player me) {
super(me); // Correct constructor usage super(me); // Correct constructor usage
} }
public Boolean jesuisMinimax(){
return true;
}
@Override @Override
public AbstractPly giveYourMove(IBoard board) { public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board; HexBoard hb = (HexBoard) board;
float bestScore = -Float.MAX_VALUE; float bestScore = -Float.MAX_VALUE;
HexPly bestMove = null; HexPly bestMove = null;
// Détermine si l'arbre est petit ou grand
int movesLeft = countLegalMoves(hb);
boolean useCutoff = (movesLeft > SMALL_TREE_MOVE_LIMIT);
int depthToUse = useCutoff ? MAXDEPTH : 0; // 0 car en "simple" on ne coupe pas sur depth
for (int i = 0; i < hb.getSize(); i++) { for (int i = 0; i < hb.getSize(); i++) {
for (int j = 0; j < hb.getSize(); j++) { for (int j = 0; j < hb.getSize(); j++) {
HexPly move = new HexPly(hb.getCurrentPlayer(), i, j); HexPly move = new HexPly(hb.getCurrentPlayer(), i, j);
if (hb.isLegal(move)) { if (hb.isLegal(move)) {
hb.doPly(move); hb.doPly(move);
float score = minimax(hb, MAXDEPTH, -Float.MAX_VALUE, Float.MAX_VALUE, true);
float score = minimax(hb, depthToUse, -Float.MAX_VALUE, Float.MAX_VALUE, true, useCutoff);
if (score > bestScore) { if (score > bestScore) {
bestScore = score; bestScore = score;
bestMove = move; bestMove = move;
@@ -33,8 +51,19 @@ public class MiniMaxBot extends AbstractGamePlayer {
return bestMove; return bestMove;
} }
private float minimax(HexBoard board, int depth, float alpha, float beta, boolean isMaximizing) { /**
if (depth == 0 || board.isGameOver()) { * Minimax + alpha-bêta
* - useCutoff = false : alpha-bêta "simple" -> on s'arrête uniquement sur isGameOver()
* - useCutoff = true : alpha-bêta avec cut-off -> arrêt si depth == 0 (heuristique)
*/
private float minimax(HexBoard board, int depth, float alpha, float beta, boolean isMaximizing, boolean useCutoff) {
// Toujours prioritaire : position terminale
if (board.isGameOver()) {
return terminalScore(board);
}
// Cut-off uniquement si demandé
if (useCutoff && depth == 0) {
return evaluateBoard(board); return evaluateBoard(board);
} }
@@ -45,10 +74,18 @@ public class MiniMaxBot extends AbstractGamePlayer {
HexPly move = new HexPly(board.getCurrentPlayer(), i, j); HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
if (board.isLegal(move)) { if (board.isLegal(move)) {
board.doPly(move); board.doPly(move);
float score = minimax(board, depth - 1, alpha, beta, false);
int nextDepth = useCutoff ? (depth - 1) : depth;
float score = minimax(board, nextDepth, alpha, beta, false, useCutoff);
bestScore = Math.max(bestScore, score); bestScore = Math.max(bestScore, score);
alpha = Math.max(alpha, bestScore); alpha = Math.max(alpha, bestScore);
if (beta <= alpha) break; // Pruning
if (beta <= alpha) {
board.undoPly();
break; // Pruning
}
board.undoPly(); board.undoPly();
} }
} }
@@ -61,10 +98,18 @@ public class MiniMaxBot extends AbstractGamePlayer {
HexPly move = new HexPly(board.getCurrentPlayer(), i, j); HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
if (board.isLegal(move)) { if (board.isLegal(move)) {
board.doPly(move); board.doPly(move);
float score = minimax(board, depth - 1, alpha, beta, true);
int nextDepth = useCutoff ? (depth - 1) : depth;
float score = minimax(board, nextDepth, alpha, beta, true, useCutoff);
bestScore = Math.min(bestScore, score); bestScore = Math.min(bestScore, score);
beta = Math.min(beta, bestScore); beta = Math.min(beta, bestScore);
if (beta <= alpha) break; // Pruning
if (beta <= alpha) {
board.undoPly();
break; // Pruning
}
board.undoPly(); board.undoPly();
} }
} }
@@ -73,6 +118,22 @@ public class MiniMaxBot extends AbstractGamePlayer {
} }
} }
/**
* Score terminal (feuille) du point de vue de PLAYER1 :
* - WIN : PLAYER1 gagne
* - LOSS : PLAYER1 perd
*/
private float terminalScore(HexBoard board) {
Result r = board.getResult();
if (r == null) return 0f;
if (r == Result.WIN) return 1000000f;
return -1000000f;
}
/**
* Heuristique actuelle (inchangée) : distance au centre des pions PLAYER1.
* (Je ne la modifie pas pour ne pas toucher à la logique existante.)
*/
private float evaluateBoard(HexBoard board) { private float evaluateBoard(HexBoard board) {
int size = board.getSize(); int size = board.getSize();
int center = size / 2; int center = size / 2;
@@ -86,4 +147,19 @@ public class MiniMaxBot extends AbstractGamePlayer {
} }
return score; return score;
} }
}
/**
* Compte les coups légaux (cases vides) sur le plateau courant.
*/
private int countLegalMoves(HexBoard board) {
int count = 0;
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
if (board.getCellPlayer(i, j) == null) {
count++;
}
}
}
return count;
}
}

View File

@@ -12,6 +12,10 @@ public class MonteCarloBot extends AbstractGamePlayer {
super(me); // Correct constructor usage super(me); // Correct constructor usage
} }
public Boolean jesuisMinimax(){
return false;
}
@Override @Override
public AbstractPly giveYourMove(IBoard board) { public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board; HexBoard hb = (HexBoard) board;

View File

@@ -19,6 +19,10 @@ public class RandomBot extends AbstractGamePlayer {
this.rng = rng; this.rng = rng;
} }
public Boolean jesuisMinimax(){
return false;
}
public RandomBot(Player me, long seed) { public RandomBot(Player me, long seed) {
this(me, new Random(seed)); this(me, new Random(seed));
} }

View File

@@ -57,8 +57,8 @@ public class Simulation extends AbstractGame {
ctaken = taken; ctaken = taken;
count = 0; count = 0;
} }
System.out.println(" wins : "+wins+"/losses : "+losses); //System.out.println(" wins : "+wins+"/losses : "+losses);
System.out.println(" eval : "+(wins-losses)/EVALDEPTH); //System.out.println(" eval : "+(wins-losses)/EVALDEPTH);
return (wins-losses)/EVALDEPTH; return (wins-losses)/EVALDEPTH;
} }
@@ -232,9 +232,7 @@ public class Simulation extends AbstractGame {
} }
return bestcase; return bestcase;
} }
} }
private AbstractPly GiveBestMove(IBoard board) { private AbstractPly GiveBestMove(IBoard board) {
@@ -269,5 +267,27 @@ public class Simulation extends AbstractGame {
return simCurrentBoard.getResult(); return simCurrentBoard.getResult();
} }
public Result runForArena(){
while(!simCurrentBoard.isGameOver()){
AbstractGamePlayer player = simmapPlayers.get(simCurrentBoard.getCurrentPlayer());
IBoard board = simCurrentBoard.safeCopy();
AbstractPly ply;
if(player.jesuisMinimax()){
ply = GiveBestMove(board);
} else {
ply = player.giveYourMove(board);
}
HexPly concretePly = (HexPly) ply;
if (simCurrentBoard.isLegal(ply)) {
simCurrentBoard.doPly(ply);
taken.add(new Integer[]{concretePly.getRow(), concretePly.getCol()});
System.out.println("Player "+player+" goes ("+concretePly.getRow()+","+concretePly.getCol()+")");
}
else throw new IllegalStateException("Player "+ player + " is a bloody cheat. He tried playing : "+concretePly.getRow()+","+concretePly.getCol()+" I give up.");
}
return simCurrentBoard.getResult();
}
} }

10001
results.csv

File diff suppressed because it is too large Load Diff