5 Commits

Author SHA1 Message Date
db6db8e6c2 Update README.md 2026-02-02 13:16:28 +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
34 changed files with 14 additions and 10947 deletions

View File

@@ -105,6 +105,7 @@ Voici un récapitulatif des commandes Git que vous utiliserez fréquemment :
git commit -m "Ajout de [...] #numeroticket" git commit -m "Ajout de [...] #numeroticket"
## 4. Pousser la branche ## 4. Pousser la branche
git push -set-upstream origin <nom-de-la-branche-#numeroticket> git push -set-upstream origin <nom-de-la-branche-#numeroticket>
@@ -113,3 +114,8 @@ Voici un récapitulatif des commandes Git que vous utiliserez fréquemment :
## 5. Supprimer une branche ## 5. Supprimer une branche
git branch -d <nom_de_la_branche> git branch -d <nom_de_la_branche>
## 6. Lancer des parties avec bots aleatoires
javac -d build $(find . -name "*.java")
java -cp build fr.iut_fbleau.HexGame.HexSimMain --games 10000 --size 11 --seed 123 --csv results.csv

View File

@@ -1 +0,0 @@
,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

@@ -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,93 +0,0 @@
package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*;
public class MiniMaxBot extends AbstractGamePlayer {
private int MAXDEPTH = 5;
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;
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, MAXDEPTH, -Float.MAX_VALUE, Float.MAX_VALUE, true);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
hb.undoPly();
}
}
}
return bestMove;
}
private float minimax(HexBoard board, int depth, float alpha, float beta, boolean isMaximizing) {
if (depth == 0 || board.isGameOver()) {
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);
float score = minimax(board, depth - 1, alpha, beta, false);
bestScore = Math.max(bestScore, score);
alpha = Math.max(alpha, bestScore);
if (beta <= alpha) 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);
float score = minimax(board, depth - 1, alpha, beta, true);
bestScore = Math.min(bestScore, score);
beta = Math.min(beta, bestScore);
if (beta <= alpha) break; // Pruning
board.undoPly();
}
}
}
return bestScore;
}
}
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;
}
}

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

@@ -1,44 +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 Boolean jesuisMinimax(){
return false;
}
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,48 +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;
@@ -110,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;
@@ -146,95 +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.");
@@ -242,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;
} }
@@ -267,27 +141,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();
}
} }

10001
results.csv

File diff suppressed because it is too large Load Diff