4 Commits

Author SHA1 Message Date
f59f9975fa c'est le désespoir 2026-02-06 01:27:28 +01:00
aa2dbc0c87 ajout bot 2026-02-05 18:03:16 +01:00
0df6b74d54 corrections monte_carlo 2026-02-05 17:59:42 +01:00
vaisse
4b2b37a04a j'ai écrit des trucs, mais il faut encore tester 2026-02-03 11:54:49 +01:00
28 changed files with 107 additions and 10573 deletions

130
README.md
View File

@@ -1,55 +1,115 @@
# BUT3 Projet Jeu : Hex # Instructions de Travail sur les Tickets
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
Il sagit dune implémentation du jeu **Hex** en Java, développée à partir de lAPI fournie par le Monsieur Madelaine. ## 2. Création de la Branche
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 :
## Compilation nom-de-la-feature-#numeroduticket
Depuis la racine du projet, compiler lensemble des fichiers Java avec la commande suivante : ### Pour créer une branche :
```bash git checkout -b feature-recherche-#123
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.
## Lancer une démonstration # Résumé des Commandes Git :
### Partie automatique (bot) Voici un récapitulatif des commandes Git que vous utiliserez fréquemment :
```bash ## 1. Créer une branche
java -cp build fr.iut_fbleau.HexGame.HexMain 3 autoplay
```
Ce mode permet de lancer une partie entièrement automatique en utilisant le bot implémenté dans la classe `Simulation`. git checkout -b feature-recherche-#123
### 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.
### Partie arène (bot) ## 2. Ajouter les fichiers modifiés :
```bash git add .
java -cp build fr.iut_fbleau.HexGame.ArenaMain git add *
``` git add <nom_du_fichier>
Créer un fichier arena_result.csv à la fin avec les résultats des combats
## Tests et validation ## 3. Commit des changements :
Les tests sont réalisés sous forme de **tests fonctionnels** via des méthodes `main` et des modes de démonstration : git commit -m "Ajout de [...] #numeroticket"
- 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.
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.
## 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>

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.

View File

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

View File

@@ -26,6 +26,5 @@ 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

@@ -1,64 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
public class Arena {
private List<AbstractGamePlayer> bots = new ArrayList<>();
private FileWriter csvWriter;
private int board_size;
public Arena(int size) {
try {
csvWriter = new FileWriter("arena_results.csv");
csvWriter.append("Bot 1, Bot 2, Winner\n");
this.board_size = size;
} catch (IOException e) {
e.printStackTrace();
}
}
public void addBot(AbstractGamePlayer bot) {
bots.add(bot);
}
public void run() {
for (int i = 0; i < bots.size(); i++) {
for (int j = i + 1; j < bots.size(); j++) {
AbstractGamePlayer bot1 = bots.get(i);
AbstractGamePlayer bot2 = bots.get(j);
System.out.println("Running match: " + bot1.getClass().getSimpleName() + " vs " + bot2.getClass().getSimpleName());
Result result = playMatch(bot1, bot2);
try {
csvWriter.append(bot1.getClass().getSimpleName() + "," + bot2.getClass().getSimpleName() + "," + result + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
csvWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private Result playMatch(AbstractGamePlayer bot1, AbstractGamePlayer bot2) {
IBoard board = new HexBoard(this.board_size);
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
players.put(Player.PLAYER1, bot1);
players.put(Player.PLAYER2, bot2);
Simulation simulation = new Simulation(board, players);
return simulation.runForArena();
}
}

View File

@@ -1,19 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.Player;
public class ArenaMain {
public static void main(String[] args) {
int size = 7;
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 HeuristicBot(Player.PLAYER1));
arena.addBot(new MonteCarloBot(Player.PLAYER2)); // Correct constructor usage
arena.run();
}
}

View File

@@ -1,53 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
public class HeuristicBot extends AbstractGamePlayer {
public HeuristicBot(Player me) {
super(me); // Correct constructor usage
}
public Boolean jesuisMinimax(){
return false;
}
@Override
public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board;
float bestScore = -Float.MAX_VALUE;
HexPly bestMove = null;
for (int i = 0; i < hb.getSize(); i++) {
for (int j = 0; j < hb.getSize(); j++) {
HexPly move = new HexPly(hb.getCurrentPlayer(), i, j);
if (hb.isLegal(move)) {
hb.doPly(move);
float score = evaluateBoard(hb);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
hb.undoPly();
}
}
}
return bestMove;
}
private float evaluateBoard(HexBoard board) {
int size = board.getSize();
int center = size / 2;
float score = 0;
//HexBoard simBoard = (HexBoard) board.safeCopy();
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (board.getCellPlayer(i, j) == Player.PLAYER1) {
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
}
}
}
return score;
}
}

View File

@@ -308,10 +308,4 @@ public class HexBoard extends AbstractBoard {
sb.append("Current player: ").append(getCurrentPlayer()).append("\n"); sb.append("Current player: ").append(getCurrentPlayer()).append("\n");
return sb.toString(); return sb.toString();
} }
public Player getCellPlayer(int r, int c) {
return cells[r][c];
}
} }

View File

@@ -1,52 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.Player;
import fr.iut_fbleau.GameAPI.Result;
import javax.swing.*;
import java.awt.*;
public class HexFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
int size = 11;
if (args.length >= 1) {
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
}
HexBoard board = new HexBoard(size);
JFrame frame = new JFrame("Hex - " + size + "x" + size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JLabel statusLabel = new JLabel("", SwingConstants.CENTER);
statusLabel.setFont(statusLabel.getFont().deriveFont(Font.BOLD, 18f));
statusLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
HexPanel panel = new HexPanel(board, statusLabel);
frame.add(statusLabel, BorderLayout.NORTH);
frame.add(panel, BorderLayout.CENTER);
// Taille confortable
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// Message initial
updateStatus(board, statusLabel);
});
}
static void updateStatus(HexBoard board, JLabel statusLabel) {
if (board.isGameOver()) {
Result r = board.getResult(); // résultat du point de vue PLAYER1
Player winner = (r == Result.WIN) ? Player.PLAYER1 : Player.PLAYER2;
statusLabel.setText("" + winner + " a gagné !");
} else {
statusLabel.setText("C'est à " + board.getCurrentPlayer() + " de jouer");
}
}
}

View File

@@ -1,166 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.Player;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
/**
* Panel Swing qui dessine un plateau Hex en hexagones et gère les clics.
*
* Grille "flat-top" (hexagones à sommet plat en haut),
* avec décalage vertical d'une demi-hauteur une colonne sur deux.
*/
public class HexPanel extends JPanel {
private final HexBoard board;
private final JLabel statusLabel;
// Rayon (distance centre -> sommet)
private final int s = 26;
private final int margin = 40;
// pointy-top : largeur = sqrt(3)*s, hauteur = 2*s
private final double hexW = Math.sqrt(3) * s;
private final double hexVStep = 1.5 * s; // distance verticale entre centres
private Shape[][] hexShapes;
public HexPanel(HexBoard board, JLabel statusLabel) {
this.board = board;
this.statusLabel = statusLabel;
this.hexShapes = new Shape[board.getSize()][board.getSize()];
setBackground(Color.WHITE);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
handleClick(e.getX(), e.getY());
}
});
}
@Override
public Dimension getPreferredSize() {
int n = board.getSize();
// largeur : n * hexW + décalage max (hexW/2) + marges
int w = margin * 2 + (int) (n * hexW + hexW / 2);
// hauteur : (n-1)*1.5*s + 2*s + marges
int h = margin * 2 + (int) ((n - 1) * hexVStep + 2 * s);
return new Dimension(w, h);
}
private void handleClick(int x, int y) {
if (board.isGameOver()) return;
int n = board.getSize();
for (int row = 0; row < n; row++) {
for (int col = 0; col < n; col++) {
Shape sh = hexShapes[row][col];
if (sh != null && sh.contains(x, y)) {
HexPly ply = new HexPly(board.getCurrentPlayer(), row, col);
if (board.isLegal(ply)) {
board.doPly(ply);
HexFrame.updateStatus(board, statusLabel);
repaint();
} else {
Toolkit.getDefaultToolkit().beep();
}
return;
}
}
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Bordures objectifs (bleu gauche/droite, rouge haut/bas)
drawGoalBorders(g2);
int n = board.getSize();
// IMPORTANT : boucles cohérentes -> row puis col
for (int row = 0; row < n; row++) {
for (int col = 0; col < n; col++) {
Shape hex = createHexShape(row, col);
hexShapes[row][col] = hex;
Player p = board.getCellPlayer(row, col);
g2.setColor(colorForCell(p));
g2.fill(hex);
g2.setColor(new Color(120, 120, 120));
g2.setStroke(new BasicStroke(1.2f));
g2.draw(hex);
}
}
g2.dispose();
}
private Color colorForCell(Player p) {
if (p == Player.PLAYER1) return new Color(30, 90, 160); // bleu
if (p == Player.PLAYER2) return new Color(220, 50, 50); // rouge
return new Color(190, 190, 190); // gris
}
/**
* Pointy-top + décalage par ligne :
*
* centreX = margin + hexW/2 + col*hexW + (row%2)*(hexW/2)
* centreY = margin + s + row*(1.5*s)
*/
private Shape createHexShape(int row, int col) {
double cx = margin + (hexW / 2.0) + col * hexW + ((row % 2) * (hexW / 2.0));
double cy = margin + s + row * hexVStep;
Path2D.Double path = new Path2D.Double();
for (int i = 0; i < 6; i++) {
double angle = Math.toRadians(i * 60); // pointy-top
double x = cx + s * Math.cos(angle);
double y = cy + s * Math.sin(angle);
if (i == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
path.closePath();
return path;
}
private void drawGoalBorders(Graphics2D g2) {
int n = board.getSize();
double leftX = margin - 12;
double rightX = margin + (hexW / 2.0) + (n - 1) * hexW + (hexW / 2.0) + (hexW / 2.0) + 12;
// explication: largeur n colonnes + potentiel décalage d'une demi-largeur
double topY = margin - 12;
double bottomY = margin + s + (n - 1) * hexVStep + s + 12;
g2.setStroke(new BasicStroke(6f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
// Bleu: gauche / droite (objectif PLAYER1)
g2.setColor(new Color(30, 90, 160));
g2.drawLine((int) leftX, (int) topY, (int) leftX, (int) bottomY);
g2.drawLine((int) rightX, (int) topY, (int) rightX, (int) bottomY);
// Rouge: haut / bas (objectif PLAYER2)
g2.setColor(new Color(220, 50, 50));
g2.drawLine((int) leftX, (int) topY, (int) rightX, (int) topY);
g2.drawLine((int) leftX, (int) bottomY, (int) rightX, (int) bottomY);
}
}

View File

@@ -1,217 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Random;
/**
* Lance un grand nombre de parties Hex entre 2 bots aléatoires et affiche des stats.
*
* Exemples :
* java fr.iut_fbleau.HexGame.HexSimMain
* java fr.iut_fbleau.HexGame.HexSimMain --games 10000 --size 7 --seed 123
* java fr.iut_fbleau.HexGame.HexSimMain --games 5000 --size 11 --csv results.csv
*
* À seed identique, la suite de nombres
* pseudo-aléatoires générée est identique, donc les bots "aléatoires" joueront les mêmes coups
* dans le même ordre (tant que le code et l'ordre des appels à Random ne changent pas).</p>
*
* Intérêt :
*
* Reproductibilité</b> : relancer exactement la même simulation pour déboguer / analyser.</li>
* Comparaison équitable</b> : comparer 2 bots sur les mêmes tirages aléatoires.</li>
* Si aucun seed n'est fourni, on utilise généralement l'heure courante, ce qui rend chaque exécution différente.</p>
*
* long seed;
*
*/
public class HexSimMain {
private static class Stats {
long win = 0;
long draw = 0;
long loss = 0;
long totalMoves = 0;
long minMoves = Long.MAX_VALUE;
long maxMoves = Long.MIN_VALUE;
void record(Result r, int moves) {
if (r == Result.WIN) win++;
else if (r == Result.DRAW) draw++;
else if (r == Result.LOSS) loss++;
totalMoves += moves;
minMoves = Math.min(minMoves, moves);
maxMoves = Math.max(maxMoves, moves);
}
long games() { return win + draw + loss; }
double winRate() { return games() == 0 ? 0.0 : (double) win / games(); }
double drawRate() { return games() == 0 ? 0.0 : (double) draw / games(); }
double lossRate() { return games() == 0 ? 0.0 : (double) loss / games(); }
double avgMoves() { return games() == 0 ? 0.0 : (double) totalMoves / games(); }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Games: ").append(games()).append("\n");
sb.append("WIN: ").append(win).append(String.format(" (%.2f%%)\n", 100.0 * winRate()));
sb.append("DRAW: ").append(draw).append(String.format(" (%.2f%%)\n", 100.0 * drawRate()));
sb.append("LOSS: ").append(loss).append(String.format(" (%.2f%%)\n", 100.0 * lossRate()));
sb.append(String.format("Moves: avg=%.2f, min=%d, max=%d\n", avgMoves(), minMoves, maxMoves));
return sb.toString();
}
}
private static class Args {
int size = 7;
int games = 1000;
long seed = System.currentTimeMillis();
int progressEvery = 0; // 0 = pas de progress
String csvPath = null; // si non null, export par partie
}
public static void main(String[] args) {
Args a = parseArgs(args);
System.out.println("Hex random-vs-random simulation");
System.out.println(" size=" + a.size + " games=" + a.games + " seed=" + a.seed +
(a.csvPath != null ? " csv=" + a.csvPath : ""));
Random master = new Random(a.seed);
Stats stats = new Stats();
BufferedWriter csv = null;
try {
if (a.csvPath != null) {
csv = new BufferedWriter(new FileWriter(a.csvPath));
csv.write("game_index,result_p1,moves\n");
}
for (int i = 0; i < a.games; i++) {
// Nouveau plateau, nouveaux bots (seeds dérivés du seed principal)
HexBoard board = new HexBoard(a.size);
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
players.put(Player.PLAYER1, new RandomBot(Player.PLAYER1, new Random(master.nextLong())));
players.put(Player.PLAYER2, new RandomBot(Player.PLAYER2, new Random(master.nextLong())));
int moves = runOneGame(board, players);
Result res = board.getResult();
stats.record(res, moves);
if (csv != null) {
csv.write(i + "," + res + "," + moves + "\n");
}
if (a.progressEvery > 0 && (i + 1) % a.progressEvery == 0) {
System.out.println("Progress: " + (i + 1) + "/" + a.games);
}
}
System.out.println("\n=== SUMMARY (Result is from PLAYER1 perspective) ===");
System.out.println(stats);
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
e.printStackTrace();
} finally {
if (csv != null) {
try { csv.close(); } catch (IOException ignored) {}
}
}
}
/**
* Boucle de jeu (même logique que AbstractGame.run, mais on compte les coups).
* On ne modifie pas GameAPI.
*/
private static int runOneGame(IBoard board, EnumMap<Player, AbstractGamePlayer> players) {
int moves = 0;
int guardMaxMoves = ((HexBoard) board).getSize() * ((HexBoard) board).getSize(); // au pire : plateau rempli
while (!board.isGameOver()) {
AbstractGamePlayer p = players.get(board.getCurrentPlayer());
IBoard safe = board.safeCopy();
AbstractPly ply = p.giveYourMove(safe);
if (!board.isLegal(ply)) {
throw new IllegalStateException("Illegal move: " + ply + " by " + board.getCurrentPlayer());
}
board.doPly(ply);
moves++;
if (moves > guardMaxMoves) {
throw new IllegalStateException("Too many moves (" + moves + "), something is wrong.");
}
}
return moves;
}
private static Args parseArgs(String[] args) {
Args a = new Args();
for (int i = 0; i < args.length; i++) {
String s = args[i];
switch (s) {
case "--size":
a.size = Integer.parseInt(nextArg(args, ++i, "--size requires a value"));
break;
case "--games":
a.games = Integer.parseInt(nextArg(args, ++i, "--games requires a value"));
break;
case "--seed":
a.seed = Long.parseLong(nextArg(args, ++i, "--seed requires a value"));
break;
case "--progress":
a.progressEvery = Integer.parseInt(nextArg(args, ++i, "--progress requires a value"));
break;
case "--csv":
a.csvPath = nextArg(args, ++i, "--csv requires a value");
break;
case "--help":
case "-h":
printHelpAndExit();
break;
default:
// compat: si l'utilisateur donne juste un nombre, on l'interprète comme size ou games
// ex: "7 10000"
if (isInt(s)) {
int v = Integer.parseInt(s);
if (a.size == 11) a.size = v;
else a.games = v;
} else {
System.err.println("Unknown arg: " + s);
printHelpAndExit();
}
}
}
return a;
}
private static String nextArg(String[] args, int idx, String errMsg) {
if (idx < 0 || idx >= args.length) throw new IllegalArgumentException(errMsg);
return args[idx];
}
private static boolean isInt(String s) {
try { Integer.parseInt(s); return true; } catch (NumberFormatException e) { return false; }
}
private static void printHelpAndExit() {
System.out.println("Usage: java fr.iut_fbleau.HexGame.HexSimMain [options]\n" +
"Options:\n" +
" --size N Board size (default 7)\n" +
" --games N Number of games (default 1000)\n" +
" --seed N Random seed (default current time)\n" +
" --progress N Print progress every N games (default 0)\n" +
" --csv FILE Write per-game results to CSV\n" +
" -h, --help Show this help\n");
System.exit(0);
}
}

View File

@@ -18,10 +18,6 @@ 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

@@ -1,165 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
public class MiniMaxBot extends AbstractGamePlayer {
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) {
super(me); // Correct constructor usage
}
public Boolean jesuisMinimax(){
return true;
}
@Override
public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board;
float bestScore = -Float.MAX_VALUE;
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 j = 0; j < hb.getSize(); j++) {
HexPly move = new HexPly(hb.getCurrentPlayer(), i, j);
if (hb.isLegal(move)) {
hb.doPly(move);
float score = minimax(hb, depthToUse, -Float.MAX_VALUE, Float.MAX_VALUE, true, useCutoff);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
hb.undoPly();
}
}
}
return bestMove;
}
/**
* 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);
}
if (isMaximizing) {
float bestScore = -Float.MAX_VALUE;
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
if (board.isLegal(move)) {
board.doPly(move);
int nextDepth = useCutoff ? (depth - 1) : depth;
float score = minimax(board, nextDepth, alpha, beta, false, useCutoff);
bestScore = Math.max(bestScore, score);
alpha = Math.max(alpha, bestScore);
if (beta <= alpha) {
board.undoPly();
break; // Pruning
}
board.undoPly();
}
}
}
return bestScore;
} else {
float bestScore = Float.MAX_VALUE;
for (int i = 0; i < board.getSize(); i++) {
for (int j = 0; j < board.getSize(); j++) {
HexPly move = new HexPly(board.getCurrentPlayer(), i, j);
if (board.isLegal(move)) {
board.doPly(move);
int nextDepth = useCutoff ? (depth - 1) : depth;
float score = minimax(board, nextDepth, alpha, beta, true, useCutoff);
bestScore = Math.min(bestScore, score);
beta = Math.min(beta, bestScore);
if (beta <= alpha) {
board.undoPly();
break; // Pruning
}
board.undoPly();
}
}
}
return bestScore;
}
}
/**
* 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) {
int size = board.getSize();
int center = size / 2;
int score = 0;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (board.getCellPlayer(i, j) == Player.PLAYER1) {
score += Math.abs(i - center) + Math.abs(j - center); // Distance from center
}
}
}
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

@@ -1,63 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
import java.util.Random;
public class MonteCarloBot extends AbstractGamePlayer {
private static final int SIMULATION_COUNT = 1000;
public MonteCarloBot(Player me) {
super(me); // Correct constructor usage
}
public Boolean jesuisMinimax(){
return false;
}
@Override
public AbstractPly giveYourMove(IBoard board) {
HexBoard hb = (HexBoard) board;
float bestScore = -Float.MAX_VALUE;
HexPly bestMove = null;
for (int i = 0; i < hb.getSize(); i++) {
for (int j = 0; j < hb.getSize(); j++) {
HexPly move = new HexPly(hb.getCurrentPlayer(), i, j);
if (hb.isLegal(move)) {
hb.doPly(move);
float score = monteCarloSimulation(hb);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
hb.undoPly();
}
}
}
return bestMove;
}
private float monteCarloSimulation(HexBoard board) {
RandomBot simBot = new RandomBot(Player.PLAYER1, new Random().nextLong());
HexBoard simBoard = (HexBoard) board.safeCopy();
int wins = 0;
int simulations = 0;
for (int i = 0; i < SIMULATION_COUNT; i++) {
while (!simBoard.isGameOver()) {
AbstractPly move = simBot.giveYourMove(simBoard);
simBoard.doPly(move);
}
if (simBoard.getResult() == Result.WIN) {
wins++;
}
simulations++;
simBoard = (HexBoard) board.safeCopy(); // Reset the board for the next simulation
}
return (float) wins / simulations;
}
}

View File

@@ -19,10 +19,6 @@ 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

@@ -36,6 +36,7 @@ public class Simulation extends AbstractGame {
float wins = 0; float wins = 0;
float losses = 0; float losses = 0;
int count = 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++; count++;
@@ -57,9 +58,12 @@ public class Simulation extends AbstractGame {
ctaken = taken; ctaken = taken;
count = 0; count = 0;
} }
//System.out.println(" wins : "+wins+"/losses : "+losses);
//System.out.println(" eval : "+(wins-losses)/EVALDEPTH); System.out.println(" wins : "+wins+"/losses : "+losses);
System.out.println(" eval : "+(wins-losses)/EVALDEPTH);
return (wins-losses)/EVALDEPTH; return (wins-losses)/EVALDEPTH;
} }
private float explMAX(HexBoard position, int depth){ private float explMAX(HexBoard position, int depth){
@@ -189,6 +193,7 @@ public class Simulation extends AbstractGame {
return bestcase; return bestcase;
} }
} }
private float explMINAB(HexBoard position, int depth, float A, float B){ private float explMINAB(HexBoard position, int depth, float A, float B){
if (position.getResult()==Result.LOSS) { if (position.getResult()==Result.LOSS) {
return -1.0f; return -1.0f;
@@ -235,6 +240,7 @@ public class Simulation extends AbstractGame {
} }
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.");
@@ -267,27 +273,5 @@ 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();
}
} }