Compare commits
21 Commits
05871232bd
...
FIN
| Author | SHA1 | Date | |
|---|---|---|---|
| 3444e6b010 | |||
| 98ffd5a653 | |||
| bcd60ff3ef | |||
| e8f083424b | |||
| 942462994b | |||
| 55f6492655 | |||
| fafebb2648 | |||
|
|
fe13b946b1 | ||
|
|
c278b18872 | ||
|
|
e0a2c2642a | ||
|
|
c9e559fe12 | ||
|
|
98c6b4678e | ||
| fc6e6b9fa6 | |||
| 8cad839e4d | |||
| d2f34577e2 | |||
| ebfc2df29b | |||
| 9a1ae37130 | |||
| 2dfc6014e0 | |||
| 3aec1d3f6e | |||
| a7d3e9d138 | |||
| f207da0e2b |
130
README.md
130
README.md
@@ -1,115 +1,55 @@
|
|||||||
# 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 s’agit d’une implémentation du jeu **Hex** en Java, développée à partir de l’API 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 l’ensemble 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 l’approbation 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 s’affiche dans le terminal et les coups sont entrés sous forme de coordonnées.
|
||||||
|
|
||||||
|
|
||||||
## 2. Ajouter les fichiers modifiés :
|
### Partie arène (bot)
|
||||||
|
|
||||||
git add .
|
```bash
|
||||||
git add *
|
java -cp build fr.iut_fbleau.HexGame.ArenaMain
|
||||||
git add <nom_du_fichier>
|
```
|
||||||
|
|
||||||
|
Créer un fichier arena_result.csv à la fin avec les résultats des combats
|
||||||
|
|
||||||
|
|
||||||
## 3. Commit des changements :
|
## Tests et validation
|
||||||
|
|
||||||
git commit -m "Ajout de [...] #numeroticket"
|
Les tests sont réalisés sous forme de **tests fonctionnels** via des méthodes `main` et des modes de démonstration :
|
||||||
|
- vérification de la validité des coups,
|
||||||
|
- alternance correcte des joueurs,
|
||||||
|
- détection des conditions de fin de partie,
|
||||||
|
- exécution de parties complètes en mode automatique.
|
||||||
|
|
||||||
|
L’affichage console du plateau, fourni par la méthode `HexBoard.toString()`, est utilisé comme outil de debug pour visualiser l’état du jeu à chaque tour.
|
||||||
## 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>
|
|
||||||
9695
Rapport Hex.pdf
Normal file
9695
Rapport Hex.pdf
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
javaAPI/.~lock.arena_results.csv#
Normal file
1
javaAPI/.~lock.arena_results.csv#
Normal file
@@ -0,0 +1 @@
|
|||||||
|
,vaisse,salle235-12,06.02.2026 11:29,file:///export/home/an23/vaisse/.config/libreoffice/4;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ public class Arena {
|
|||||||
|
|
||||||
private List<AbstractGamePlayer> bots = new ArrayList<>();
|
private List<AbstractGamePlayer> bots = new ArrayList<>();
|
||||||
private FileWriter csvWriter;
|
private FileWriter csvWriter;
|
||||||
|
private int board_size;
|
||||||
|
|
||||||
public Arena() {
|
public Arena(int size) {
|
||||||
try {
|
try {
|
||||||
csvWriter = new FileWriter("arena_results.csv");
|
csvWriter = new FileWriter("arena_results.csv");
|
||||||
csvWriter.append("Bot 1, Bot 2, Winner\n");
|
csvWriter.append("Bot 1, Bot 2, Winner\n");
|
||||||
|
this.board_size = size;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -51,12 +53,12 @@ public class Arena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Result playMatch(AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
|
private Result playMatch(AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
|
||||||
IBoard board = new HexBoard(11);
|
IBoard board = new HexBoard(this.board_size);
|
||||||
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
|
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
|
||||||
players.put(Player.PLAYER1, bot1);
|
players.put(Player.PLAYER1, bot1);
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ import fr.iut_fbleau.GameAPI.Player;
|
|||||||
|
|
||||||
public class ArenaMain {
|
public class ArenaMain {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Arena arena = new Arena();
|
int size = 7;
|
||||||
arena.addBot(new RandomBot(Player.PLAYER1, 12345L)); // Correct constructor usage
|
if (args.length >= 1) {
|
||||||
|
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
Arena arena = new Arena(size);
|
||||||
|
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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -35,10 +39,11 @@ public class HeuristicBot extends AbstractGamePlayer {
|
|||||||
int size = board.getSize();
|
int size = board.getSize();
|
||||||
int center = size / 2;
|
int center = size / 2;
|
||||||
float score = 0;
|
float score = 0;
|
||||||
|
//HexBoard simBoard = (HexBoard) board.safeCopy();
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
for (int j = 0; j < size; j++) {
|
for (int j = 0; j < size; j++) {
|
||||||
if (board.getPlayerAt(i, j) == Player.PLAYER1) {
|
if (board.getCellPlayer(i, j) == Player.PLAYER1) {
|
||||||
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
|
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fr.iut_fbleau.HexGame;
|
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.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ import java.util.Scanner;
|
|||||||
public class HexMain {
|
public class HexMain {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
int size = 11;
|
int size = 7;
|
||||||
if (args.length >= 1) {
|
if (args.length >= 1) {
|
||||||
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
|
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
|
||||||
}
|
}
|
||||||
@@ -19,12 +18,19 @@ public class HexMain {
|
|||||||
HexBoard board = new HexBoard(size);
|
HexBoard board = new HexBoard(size);
|
||||||
|
|
||||||
Scanner sc = new Scanner(System.in);
|
Scanner sc = new Scanner(System.in);
|
||||||
|
Result res;
|
||||||
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
|
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
|
||||||
players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, sc));
|
players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, sc));
|
||||||
players.put(Player.PLAYER2, new HumanConsolePlayer(Player.PLAYER2, sc));
|
players.put(Player.PLAYER2, new HumanConsolePlayer(Player.PLAYER2, sc));
|
||||||
|
|
||||||
AbstractGame game = new AbstractGame(board, players) {};
|
|
||||||
Result res = game.run();
|
if (args.length>=2 && args[1].equals("autoplay")) {
|
||||||
|
Simulation sim = new Simulation(board, players);
|
||||||
|
res = sim.run();
|
||||||
|
} else {
|
||||||
|
AbstractGame game = new AbstractGame(board, players) {};
|
||||||
|
res = game.run();
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println(board);
|
System.out.println(board);
|
||||||
System.out.println("Résultat (du point de vue de PLAYER1) : " + res);
|
System.out.println("Résultat (du point de vue de PLAYER1) : " + res);
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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,17 +118,48 @@ 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;
|
||||||
int score = 0;
|
int score = 0;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
for (int j = 0; j < size; j++) {
|
for (int j = 0; j < size; j++) {
|
||||||
if (board.getPlayerAt(i, j) == Player.PLAYER1) {
|
if (board.getCellPlayer(i, j) == Player.PLAYER1) {
|
||||||
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
|
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -37,7 +41,7 @@ public class MonteCarloBot extends AbstractGamePlayer {
|
|||||||
|
|
||||||
private float monteCarloSimulation(HexBoard board) {
|
private float monteCarloSimulation(HexBoard board) {
|
||||||
RandomBot simBot = new RandomBot(Player.PLAYER1, new Random().nextLong());
|
RandomBot simBot = new RandomBot(Player.PLAYER1, new Random().nextLong());
|
||||||
HexBoard simBoard = board.safeCopy();
|
HexBoard simBoard = (HexBoard) board.safeCopy();
|
||||||
int wins = 0;
|
int wins = 0;
|
||||||
int simulations = 0;
|
int simulations = 0;
|
||||||
|
|
||||||
@@ -51,7 +55,7 @@ public class MonteCarloBot extends AbstractGamePlayer {
|
|||||||
wins++;
|
wins++;
|
||||||
}
|
}
|
||||||
simulations++;
|
simulations++;
|
||||||
simBoard = board.safeCopy(); // Reset the board for the next simulation
|
simBoard = (HexBoard) board.safeCopy(); // Reset the board for the next simulation
|
||||||
}
|
}
|
||||||
|
|
||||||
return (float) wins / simulations;
|
return (float) wins / simulations;
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 {
|
||||||
@@ -27,18 +28,19 @@ 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*/
|
/*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){
|
private float MonteCarlo(HexBoard position, Player current){
|
||||||
RandomBot simplay = new RandomBot();
|
RandomBot simplay = new RandomBot(current, new Random().nextLong());
|
||||||
HexBoard simpos = position.safeCopy();
|
HexBoard simpos = position;
|
||||||
LinkedList<Integer[]> ctaken = taken;
|
LinkedList<Integer[]> ctaken = taken;
|
||||||
HexPly testmove;
|
HexPly testmove;
|
||||||
float wins = 0;
|
float wins = 0;
|
||||||
float losses = 0;
|
float losses = 0;
|
||||||
|
int count = 0;
|
||||||
for(int i=0; i<EVALDEPTH; i++){
|
for(int i=0; i<EVALDEPTH; i++){
|
||||||
while(!simpos.isGameOver()){
|
while(!simpos.isGameOver()){
|
||||||
|
count++;
|
||||||
testmove = (HexPly) simplay.giveYourMove(simpos);
|
testmove = (HexPly) simplay.giveYourMove(simpos);
|
||||||
if(!ctaken.contains(t) && simpos.isLegal(testmove)){
|
if(!ctaken.contains(new Integer[]{testmove.getRow(), testmove.getCol()}) && simpos.isLegal(testmove)){
|
||||||
ctaken.add(new Integer[]{testmove.getRow(), testmove.getCol()});
|
ctaken.add(new Integer[]{testmove.getRow(), testmove.getCol()});
|
||||||
simpos.doPly(testmove);
|
simpos.doPly(testmove);
|
||||||
if(simpos.getResult()==Result.LOSS){
|
if(simpos.getResult()==Result.LOSS){
|
||||||
@@ -48,16 +50,16 @@ public class Simulation extends AbstractGame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
simpos = position.safeCopy();
|
//System.out.println("count:"+count);
|
||||||
|
for (int j=0; j<count; j++) {
|
||||||
|
simpos.undoPly();
|
||||||
|
}
|
||||||
ctaken = taken;
|
ctaken = taken;
|
||||||
|
count = 0;
|
||||||
}
|
}
|
||||||
|
//System.out.println(" wins : "+wins+"/losses : "+losses);
|
||||||
if(wins>=losses){
|
//System.out.println(" eval : "+(wins-losses)/EVALDEPTH);
|
||||||
return losses/wins;
|
return (wins-losses)/EVALDEPTH;
|
||||||
} else {
|
|
||||||
return -(wins/losses);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float explMAX(HexBoard position, int depth){
|
private float explMAX(HexBoard position, int depth){
|
||||||
@@ -66,7 +68,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);
|
return MonteCarlo(position, Player.PLAYER1);
|
||||||
} else {
|
} else {
|
||||||
float bestcase = -1.0f;
|
float bestcase = -1.0f;
|
||||||
HexPly bestcasemove;
|
HexPly bestcasemove;
|
||||||
@@ -108,7 +110,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);
|
return MonteCarlo(position, Player.PLAYER2);
|
||||||
} else {
|
} else {
|
||||||
float bestcase = 1.0f;
|
float bestcase = 1.0f;
|
||||||
HexPly bestcasemove;
|
HexPly bestcasemove;
|
||||||
@@ -145,96 +147,92 @@ public class Simulation extends AbstractGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private float explMAXAB(HexBoard position, int depth, float A, float B){
|
private float explMAXAB(HexBoard position, int depth, float A, float B){
|
||||||
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);
|
return MonteCarlo(position, Player.PLAYER1);
|
||||||
} else {
|
} else {
|
||||||
float bestcase = A;
|
float bestcase = A;
|
||||||
HexPly bestcasemove;
|
HexPly bestcasemove;
|
||||||
HexPly testmove;
|
HexPly testmove;
|
||||||
for (int i = 0; i < position.getSize(); i++) {
|
for (int i=0; i<position.getSize(); i++) {
|
||||||
for (int j = 0; j < position.getSize(); j++) {
|
for (int j=0; j<position.getSize(); j++) {
|
||||||
if (depth == 0) {
|
if(depth==0){
|
||||||
//System.out.println("MAX New Line :");
|
//System.out.println("MAX New Line :");
|
||||||
}
|
}
|
||||||
Integer[] t = new Integer[]{i, j};
|
Integer[] t = new Integer[]{i, j};
|
||||||
testmove = new HexPly(Player.PLAYER1, i, j);
|
testmove = new HexPly(Player.PLAYER1, i, j);
|
||||||
if (!taken.contains(t) && position.isLegal(testmove)) {
|
if(!taken.contains(t) && position.isLegal(testmove)){
|
||||||
//System.out.println(" MAX test move : "+Integer.toString(i)+","+Integer.toString(j));
|
//System.out.println(" MAX test move : "+Integer.toString(i)+","+Integer.toString(j));
|
||||||
taken.add(t);
|
taken.add(t);
|
||||||
position.doPly(testmove);
|
position.doPly(testmove);
|
||||||
float val = explMINAB(position, depth + 1, bestcase, B);
|
float val = explMINAB(position, depth+1, bestcase, B);
|
||||||
if (val >= bestcase) {
|
if (val >= bestcase) {
|
||||||
//System.out.println(" MAX new best case");
|
//System.out.println(" MAX new best case");
|
||||||
bestcase = val;
|
bestcase = val;
|
||||||
bestcasemove = testmove;
|
bestcasemove = testmove;
|
||||||
if (depth == 0) {
|
if (depth==0) {
|
||||||
this.bestoutcome = bestcase;
|
this.bestoutcome = bestcase;
|
||||||
this.bestmove = bestcasemove;
|
this.bestmove = bestcasemove;
|
||||||
}
|
}
|
||||||
if (bestcase >= B) {
|
if(bestcase>=B){
|
||||||
return bestcase;
|
return bestcase;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
position.undoPly();
|
||||||
|
taken.remove(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
position.undoPly();
|
|
||||||
taken.remove(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
return bestcase;
|
||||||
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();
|
||||||
private float explMINAB(HexBoard position, int depth, float A, float B){
|
taken.remove(t);
|
||||||
if (position.getResult() == Result.LOSS) {
|
}
|
||||||
return -1.0f;
|
|
||||||
} else if (position.getResult() == Result.WIN) {
|
|
||||||
return 1.0f;
|
|
||||||
} else if (depth == MAXDEPTH) {
|
|
||||||
return MonteCarlo(position);
|
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
return bestcase;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private AbstractPly GiveBestMove(IBoard board) {
|
private AbstractPly GiveBestMove(IBoard board) {
|
||||||
@@ -269,5 +267,27 @@ private float explMINAB(HexBoard position, int depth, float A, float B){
|
|||||||
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
10001
results.csv
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user