4 Commits

Author SHA1 Message Date
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
16 changed files with 9825 additions and 272 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

@@ -1,40 +0,0 @@
package fr.iut_fbleau.HexGame;
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;
public class RandomBot extends AbstractGamePlayer {
private final Random rng;
public RandomBot(Player me, Random rng) {
super(me);
this.rng = rng;
}
public RandomBot(Player me, long seed) {
this(me, new Random(seed));
}
@Override
public AbstractPly giveYourMove(IBoard board) {
List<AbstractPly> legal = new ArrayList<>();
Iterator<AbstractPly> it = board.iterator();
while (it.hasNext()) {
legal.add(it.next());
}
if (legal.isEmpty()) {
throw new IllegalStateException("No legal move available (board is full?)");
}
return legal.get(rng.nextInt(legal.size()));
}
}

View File

@@ -3,7 +3,6 @@ package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*; import fr.iut_fbleau.GameAPI.*;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Random;
public class Simulation extends AbstractGame { public class Simulation extends AbstractGame {
@@ -11,8 +10,7 @@ public class Simulation extends AbstractGame {
//ATTRIBUTS //ATTRIBUTS
private HexPly bestmove; private HexPly bestmove;
private float bestoutcome; private float bestoutcome;
private int MAXDEPTH = 9; private int MAXDEPTH = 6;
private int EVALDEPTH = 10;
private LinkedList<Integer[]> taken = new LinkedList<Integer[]>(); private LinkedList<Integer[]> taken = new LinkedList<Integer[]>();
//ATTRIBUTS QUE JE NE VOUDRAIS PAS CRÉER IDÉALEMENT //ATTRIBUTS QUE JE NE VOUDRAIS PAS CRÉER IDÉALEMENT
@@ -27,52 +25,13 @@ public class Simulation extends AbstractGame {
} }
//METHODES //METHODES
/*Le jeu de Hex ne peut jamais finir avec le résultat null. En utilisant cette propriété, on peut avoir cet algorithme simplifié du monte-carlo*/
private float MonteCarlo(HexBoard position, Player current){
RandomBot simplay = new RandomBot(current, new Random().nextLong());
HexBoard simpos = position;
LinkedList<Integer[]> ctaken = taken;
HexPly testmove;
float wins = 0;
float losses = 0;
int count = 0;
for(int i=0; i<EVALDEPTH; i++){
while(!simpos.isGameOver()){
count++;
testmove = (HexPly) simplay.giveYourMove(simpos);
if(!ctaken.contains(new Integer[]{testmove.getRow(), testmove.getCol()}) && simpos.isLegal(testmove)){
ctaken.add(new Integer[]{testmove.getRow(), testmove.getCol()});
simpos.doPly(testmove);
if(simpos.getResult()==Result.LOSS){
losses++;
} else if(simpos.getResult()==Result.WIN){
wins++;
}
}
}
//System.out.println("count:"+count);
for (int j=0; j<count; j++) {
simpos.undoPly();
}
ctaken = taken;
count = 0;
}
System.out.println(" wins : "+wins+"/losses : "+losses);
System.out.println(" eval : "+(wins-losses)/EVALDEPTH);
return (wins-losses)/EVALDEPTH;
}
private float explMAX(HexBoard position, int depth){ private float explMAX(HexBoard position, int depth){
if (position.getResult()==Result.LOSS) { if (position.getResult()==Result.LOSS) {
return -1.0f; return -1.0f;
} else if (position.getResult()==Result.WIN){ } else if (position.getResult()==Result.WIN){
return 1.0f; return 1.0f;
} else if (depth==MAXDEPTH) { } else if (depth==MAXDEPTH) {
return MonteCarlo(position, Player.PLAYER1); return 0f;
} else { } else {
float bestcase = -1.0f; float bestcase = -1.0f;
HexPly bestcasemove; HexPly bestcasemove;
@@ -114,7 +73,7 @@ public class Simulation extends AbstractGame {
} else if (position.getResult()==Result.WIN){ } else if (position.getResult()==Result.WIN){
return 1.0f; return 1.0f;
} else if (depth==MAXDEPTH) { } else if (depth==MAXDEPTH) {
return MonteCarlo(position, Player.PLAYER2); return 0f;
} else { } else {
float bestcase = 1.0f; float bestcase = 1.0f;
HexPly bestcasemove; HexPly bestcasemove;
@@ -150,97 +109,6 @@ public class Simulation extends AbstractGame {
} }
} }
private float explMAXAB(HexBoard position, int depth, float A, float B){
if (position.getResult()==Result.LOSS) {
return -1.0f;
} else if (position.getResult()==Result.WIN){
return 1.0f;
} else if (depth==MAXDEPTH) {
return MonteCarlo(position, Player.PLAYER1);
} else {
float bestcase = A;
HexPly bestcasemove;
HexPly testmove;
for (int i=0; i<position.getSize(); i++) {
for (int j=0; j<position.getSize(); j++) {
if(depth==0){
//System.out.println("MAX New Line :");
}
Integer[] t = new Integer[]{i, j};
testmove = new HexPly(Player.PLAYER1, i, j);
if(!taken.contains(t) && position.isLegal(testmove)){
//System.out.println(" MAX test move : "+Integer.toString(i)+","+Integer.toString(j));
taken.add(t);
position.doPly(testmove);
float val = explMINAB(position, depth+1, bestcase, B);
if (val >= bestcase) {
//System.out.println(" MAX new best case");
bestcase = val;
bestcasemove = testmove;
if (depth==0) {
this.bestoutcome = bestcase;
this.bestmove = bestcasemove;
}
if(bestcase>=B){
return bestcase;
}
}
position.undoPly();
taken.remove(t);
}
}
}
return bestcase;
}
}
private float explMINAB(HexBoard position, int depth, float A, float B){
if (position.getResult()==Result.LOSS) {
return -1.0f;
} else if (position.getResult()==Result.WIN){
return 1.0f;
} else if (depth==MAXDEPTH) {
return MonteCarlo(position, Player.PLAYER2);
} else {
float bestcase = B;
HexPly bestcasemove;
HexPly testmove;
for (int i=0; i<position.getSize(); i++) {
for (int j=0; j<position.getSize(); j++) {
if(depth==0){
//System.out.println("MIN New Line :");
}
Integer[] t = new Integer[]{i, j};
testmove = new HexPly(Player.PLAYER2, i, j);
if(!taken.contains(t) && position.isLegal(testmove)){
//System.out.println(" MIN test move : "+Integer.toString(i)+","+Integer.toString(j));
taken.add(t);
position.doPly(testmove);
float val = explMAXAB(position, depth+1, A, bestcase);
if (val <= bestcase) {
//System.out.println(" MIN new best case");
bestcase = val;
bestcasemove = testmove;
if (depth==0) {
this.bestoutcome = bestcase;
this.bestmove = bestcasemove;
}
if(bestcase<=A){
return bestcase;
}
}
position.undoPly();
taken.remove(t);
}
}
}
return bestcase;
}
}
private AbstractPly GiveBestMove(IBoard board) { private AbstractPly GiveBestMove(IBoard board) {
if (!(board instanceof HexBoard)) { if (!(board instanceof HexBoard)) {
throw new IllegalArgumentException("Ce joueur attend un HexBoard."); throw new IllegalArgumentException("Ce joueur attend un HexBoard.");
@@ -248,9 +116,9 @@ public class Simulation extends AbstractGame {
HexBoard hb = (HexBoard) board; HexBoard hb = (HexBoard) board;
float bestcase; float bestcase;
if(hb.getCurrentPlayer()==Player.PLAYER1){ if(hb.getCurrentPlayer()==Player.PLAYER1){
bestcase = explMAXAB(hb, 0, -1.0f, 1.0f); bestcase = explMAX(hb, 0);
} else { } else {
bestcase = explMINAB(hb, 0, -1.0f, 1.0f); bestcase = explMIN(hb, 0);
} }
return this.bestmove; return this.bestmove;
} }